Merge pull request #3993 from xyflow/next
* fix(react): ReactFlow comp ref type * chore(changelog): update * Refactor(types): unify node and edge handling (#3978) * refactor(types): unify node and edge type behaviour * chore(changelogs): update * chore(examples): cleanup * chore(svelte): update deps * refactor(useNodesData): improve selector equality function (#3976) * improved selector equality function for useNodesData * fixed selector & useNodesData example * added shallow-node-data as system function * fix(safari): prevent selection of viewport (#3973) * prevent selection of viewport * added user-select=none to viewport-portal and moved to be over nodes * chore(changelogs): update * fix(react): allow edges to start from 0,0 (#3980) * test(playwright): fix edge selection and auto pan tests * fix(react): select nodes with selection in uncontrolled flows * text(react): cleanup tests, add onNodesChange tests * react(pane): position fixed * chore(test): cleanup * chore(react): dont use position fixed --------- Co-authored-by: peterkogo <7165378+peterkogo@users.noreply.github.com>
This commit is contained in:
@@ -6,7 +6,7 @@ import * as simpleflow from '../../fixtures/simpleflow';
|
||||
describe('<ReactFlow />: Basic Props', () => {
|
||||
describe('uses defaultNodes and defaultEdges', () => {
|
||||
beforeEach(() => {
|
||||
cy.mount(<ReactFlow defaultNodes={simpleflow.nodes} defaultEdges={simpleflow.edges} />);
|
||||
cy.mount(<ReactFlow defaultNodes={simpleflow.nodes} defaultEdges={simpleflow.edges} nodeDragThreshold={0} />);
|
||||
});
|
||||
|
||||
it('mounts nodes and edges', () => {
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { OnNodesChange, Panel, ReactFlow, ReactFlowProps, applyNodeChanges, useReactFlow, Node } from '@xyflow/react';
|
||||
|
||||
const nodes = [
|
||||
{
|
||||
id: '1',
|
||||
data: { label: 'Node 1' },
|
||||
position: { x: 0, y: 0 },
|
||||
style: { width: 100, height: 40 },
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
data: { label: 'Node 2' },
|
||||
position: { x: 200, y: 200 },
|
||||
style: { width: 80, height: 50 },
|
||||
},
|
||||
];
|
||||
|
||||
describe('<ReactFlow />: onNodesChange', () => {
|
||||
beforeEach(() => {
|
||||
const onNodesChange = cy.spy().as('onNodesChange');
|
||||
cy.mount(<Comp nodes={nodes} onNodesChange={onNodesChange} />).wait(500);
|
||||
});
|
||||
|
||||
it('receive initial dimension change', () => {
|
||||
const expectedChanges = nodes.map((n) => ({
|
||||
type: 'dimensions',
|
||||
id: n.id,
|
||||
dimensions: n.style,
|
||||
}));
|
||||
|
||||
cy.get('@onNodesChange').should('have.callCount', 1);
|
||||
cy.get('@onNodesChange').should('have.calledWith', expectedChanges);
|
||||
});
|
||||
|
||||
it('receive replace and dimension change', () => {
|
||||
const expectedDimensionChanges = [
|
||||
{
|
||||
type: 'dimensions',
|
||||
id: '1',
|
||||
dimensions: { width: 200, height: 100 },
|
||||
},
|
||||
];
|
||||
|
||||
const expectedReplaceChanges = [
|
||||
{
|
||||
type: 'replace',
|
||||
id: '1',
|
||||
item: {
|
||||
...nodes[0],
|
||||
computed: { positionAbsolute: nodes[0].position, width: 200, height: 100 },
|
||||
style: { width: 200, height: 100 },
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
cy.get('[data-id=update-btn]').click();
|
||||
|
||||
cy.get('@onNodesChange').should('have.callCount', 3);
|
||||
cy.get('@onNodesChange').then((s) => {
|
||||
// dimension change (already checked above)
|
||||
// const firstCall = s.getCall(0);
|
||||
// replace change
|
||||
// @ts-ignore
|
||||
const secondChangeCallArgs = s.getCall(1).args[0];
|
||||
// dimension change
|
||||
// @ts-ignore
|
||||
const thirdChangeCallArgs = s.getCall(2).args[0];
|
||||
|
||||
expect(secondChangeCallArgs).to.be.deep.eq(expectedReplaceChanges);
|
||||
expect(thirdChangeCallArgs).to.be.deep.eq(expectedDimensionChanges);
|
||||
});
|
||||
});
|
||||
|
||||
it('receive select and unselect change', () => {
|
||||
const expectedSelectChanges = [{ id: '1', type: 'select', selected: true }];
|
||||
const expectedUnselectChanges = [{ id: '1', type: 'select', selected: false }];
|
||||
|
||||
cy.get('.react-flow__node').first().click();
|
||||
cy.get('@onNodesChange').should('have.calledWith', expectedSelectChanges);
|
||||
|
||||
cy.get('.react-flow__pane').first().click({ force: true });
|
||||
cy.get('@onNodesChange').should('have.calledWith', expectedUnselectChanges);
|
||||
});
|
||||
|
||||
it('receive position change', () => {
|
||||
const endPosition = { x: 200, y: 25 };
|
||||
const expectedChanges = [
|
||||
{ id: '1', type: 'position', dragging: false, position: endPosition, positionAbsolute: endPosition },
|
||||
];
|
||||
|
||||
cy.drag('.react-flow__node:first', endPosition).then(() => {
|
||||
cy.get('@onNodesChange').should('have.calledWith', expectedChanges);
|
||||
});
|
||||
});
|
||||
|
||||
it('receive remove change', () => {
|
||||
const expectedChanges = [{ id: '1', type: 'remove' }];
|
||||
|
||||
cy.get('.react-flow__node').first().click();
|
||||
cy.realPress('Backspace');
|
||||
|
||||
cy.get('@onNodesChange').should('have.calledWith', expectedChanges);
|
||||
});
|
||||
});
|
||||
|
||||
// test specific helpers
|
||||
|
||||
function UpdateButton() {
|
||||
const { updateNode } = useReactFlow();
|
||||
const updateNodeDimensions = () => {
|
||||
updateNode('1', { style: { width: 200, height: 100 } });
|
||||
};
|
||||
|
||||
return (
|
||||
<Panel position="top-right">
|
||||
<button data-id="update-btn" onClick={updateNodeDimensions} id="btn">
|
||||
update
|
||||
</button>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
||||
type CompProps = ReactFlowProps & { onNodesChange: (changes: any) => void };
|
||||
|
||||
function Comp(props: CompProps) {
|
||||
const [nodes, setNodes] = useState(props.nodes || []);
|
||||
const onNodesChange: OnNodesChange<Node> = useCallback((changes) => {
|
||||
props.onNodesChange(changes);
|
||||
setNodes((nds) => applyNodeChanges(changes, nds));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ReactFlow nodes={nodes} onNodesChange={onNodesChange} nodeDragThreshold={0}>
|
||||
<UpdateButton />
|
||||
</ReactFlow>
|
||||
);
|
||||
}
|
||||
@@ -49,21 +49,16 @@ describe('Basic Flow Rendering', { testIsolation: false }, () => {
|
||||
|
||||
it('selects one node with a selection', () => {
|
||||
cy.get('body')
|
||||
.type('{shift}', { release: false })
|
||||
.wait(50)
|
||||
.type('{Shift}', { release: false })
|
||||
.get('.react-flow__pane')
|
||||
.trigger('mousedown', 1, 10, { button: 0, force: true })
|
||||
.trigger('mousemove', 1000, 450, { button: 0 })
|
||||
.wait(50)
|
||||
.trigger('mouseup', 1000, 450, { button: 0, force: true });
|
||||
.trigger('mousemove', 1000, 200, { button: 0 })
|
||||
.trigger('mouseup', 1000, 200, { button: 0 });
|
||||
|
||||
cy.wait(200);
|
||||
|
||||
cy.get('.react-flow__node').eq(1).should('have.class', 'selected');
|
||||
cy.get('.react-flow__node').eq(0).should('have.class', 'selected');
|
||||
cy.get('.react-flow__node').eq(3).should('have.not.class', 'selected');
|
||||
|
||||
cy.get('.react-flow__nodesselection-rect');
|
||||
|
||||
cy.get('body').type('{shift}', { release: true, force: true });
|
||||
});
|
||||
|
||||
@@ -97,19 +92,19 @@ describe('Basic Flow Rendering', { testIsolation: false }, () => {
|
||||
it('drags a node', () => {
|
||||
const styleBeforeDrag = Cypress.$('.react-flow__node:first').css('transform');
|
||||
|
||||
cy.drag('.react-flow__node:first', { x: 500, y: 25 }).then(($el: any) => {
|
||||
cy.drag('.react-flow__node:first', { x: 10, y: 10 }).then(($el: any) => {
|
||||
const styleAfterDrag = $el.css('transform');
|
||||
expect(styleBeforeDrag).to.not.equal(styleAfterDrag);
|
||||
});
|
||||
});
|
||||
|
||||
// @TODO: why does this fail since react18?
|
||||
// it('removes a node', () => {
|
||||
// cy.get('.react-flow__node').contains('Node 1').click().should('have.class', 'selected');
|
||||
// cy.get('html').type('{backspace}').wait(100);
|
||||
// cy.get('.react-flow__node').should('have.length', 3);
|
||||
// cy.get('.react-flow__edge').should('have.length', 1);
|
||||
// });
|
||||
it('removes a node', () => {
|
||||
cy.get('.react-flow__node').contains('Node 1').click().should('have.class', 'selected');
|
||||
cy.get('html').realPress('Backspace');
|
||||
|
||||
cy.get('.react-flow__node').should('have.length', 3);
|
||||
cy.get('.react-flow__edge').should('have.length', 0);
|
||||
});
|
||||
|
||||
it('connects nodes', () => {
|
||||
cy.get('.react-flow__node')
|
||||
@@ -125,16 +120,15 @@ describe('Basic Flow Rendering', { testIsolation: false }, () => {
|
||||
.trigger('mouseup', { force: true, button: 0 });
|
||||
|
||||
cy.get('.react-flow__edge').as('edge');
|
||||
cy.get('@edge').should('have.length', 3);
|
||||
cy.get('@edge').should('have.length', 1);
|
||||
});
|
||||
|
||||
// @TODO: why does this fail since react18?
|
||||
// it('removes an edge', () => {
|
||||
// cy.get('.react-flow__edge:first').click();
|
||||
// cy.get('body').type('{backspace}');
|
||||
it('removes an edge', () => {
|
||||
cy.get('.react-flow__edge:first').click();
|
||||
cy.get('html').realPress('Backspace');
|
||||
|
||||
// cy.get('.react-flow__edge').should('have.length', 1);
|
||||
// });
|
||||
cy.get('.react-flow__edge').should('have.length', 0);
|
||||
});
|
||||
|
||||
it('drags the pane', () => {
|
||||
const styleBeforeDrag = Cypress.$('.react-flow__viewport').css('transform');
|
||||
|
||||
@@ -59,7 +59,7 @@ function ControlledFlow({
|
||||
handlers.onConnect = onConnect;
|
||||
}
|
||||
|
||||
return <ReactFlow nodes={nodes} edges={edges} {...handlers} {...rest} />;
|
||||
return <ReactFlow nodes={nodes} edges={edges} {...handlers} {...rest} nodeDragThreshold={0} />;
|
||||
}
|
||||
|
||||
export default ControlledFlow;
|
||||
|
||||
@@ -9,7 +9,7 @@ Cypress.Commands.add('drag', (selector, { x, y }) =>
|
||||
const nextY: number = centerY + y;
|
||||
|
||||
return elementToDrag
|
||||
.trigger('mousedown', { view: window })
|
||||
.trigger('mousedown', { view: window, force: true })
|
||||
.trigger('mousemove', nextX, nextY, { force: true })
|
||||
.wait(50)
|
||||
.trigger('mouseup', { view: window, force: true });
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands';
|
||||
import 'cypress-real-events/support';
|
||||
|
||||
const resizeObserverLoopErrRe = /^[^(ResizeObserver loop limit exceeded)]/;
|
||||
|
||||
|
||||
@@ -30,8 +30,8 @@
|
||||
"@types/react-dom": "^18.2.14",
|
||||
"@vitejs/plugin-react": "4.1.1",
|
||||
"@vitejs/plugin-react-swc": "^3.4.1",
|
||||
"cypress": "12.17.3",
|
||||
"cypress-real-events": "1.11.0",
|
||||
"cypress": "13.6.6",
|
||||
"cypress-real-events": "1.12.0",
|
||||
"start-server-and-test": "^2.0.2",
|
||||
"typescript": "5.2.2",
|
||||
"vite": "4.5.0"
|
||||
|
||||
@@ -45,7 +45,6 @@ const initialNodes: Node[] = [
|
||||
data: { label: 'Node 4' },
|
||||
position: { x: 400, y: 200 },
|
||||
className: 'light',
|
||||
connectable: false,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -129,6 +128,7 @@ const BasicFlow = () => {
|
||||
selectNodesOnDrag={false}
|
||||
elevateEdgesOnSelect
|
||||
elevateNodesOnSelect={false}
|
||||
nodeDragThreshold={0}
|
||||
>
|
||||
<Background variant={BackgroundVariant.Dots} />
|
||||
<MiniMap />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { memo, FC, CSSProperties, useCallback } from 'react';
|
||||
import React, { memo, CSSProperties, useCallback } from 'react';
|
||||
import { Handle, Position, NodeProps, Connection, Edge, useOnViewportChange, Viewport } from '@xyflow/react';
|
||||
|
||||
import type { ColorSelectorNode } from '.';
|
||||
@@ -13,7 +13,7 @@ const sourceHandleStyleB: CSSProperties = {
|
||||
|
||||
const onConnect = (params: Connection | Edge) => console.log('handle onConnect', params);
|
||||
|
||||
const ColorSelectorNode: FC<NodeProps<ColorSelectorNode['data']>> = ({ data, isConnectable }) => {
|
||||
function ColorSelectorNode({ data, isConnectable }: NodeProps<ColorSelectorNode>) {
|
||||
const onStart = useCallback((viewport: Viewport) => console.log('onStart', viewport), []);
|
||||
const onChange = useCallback((viewport: Viewport) => console.log('onChange', viewport), []);
|
||||
const onEnd = useCallback((viewport: Viewport) => console.log('onEnd', viewport), []);
|
||||
@@ -44,6 +44,6 @@ const ColorSelectorNode: FC<NodeProps<ColorSelectorNode['data']>> = ({ data, isC
|
||||
<Handle type="source" position={Position.Right} id="b" style={sourceHandleStyleB} isConnectable={isConnectable} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default memo(ColorSelectorNode);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect, MouseEvent, ChangeEvent, useCallback } from 'react';
|
||||
import { useState, useEffect, MouseEvent, ChangeEvent, useCallback, useRef } from 'react';
|
||||
import {
|
||||
ReactFlow,
|
||||
MiniMap,
|
||||
@@ -17,6 +17,8 @@ import {
|
||||
OnBeforeDelete,
|
||||
BuiltInNode,
|
||||
BuiltInEdge,
|
||||
NodeTypes,
|
||||
ReactFlowProvider,
|
||||
} from '@xyflow/react';
|
||||
|
||||
import ColorSelectorNode from './ColorSelectorNode';
|
||||
@@ -40,11 +42,12 @@ const initBgColor = '#1A192B';
|
||||
const connectionLineStyle = { stroke: '#fff' };
|
||||
const snapGrid: SnapGrid = [16, 16];
|
||||
|
||||
const nodeTypes = {
|
||||
const nodeTypes: NodeTypes = {
|
||||
selectorNode: ColorSelectorNode,
|
||||
};
|
||||
|
||||
const CustomNodeFlow = () => {
|
||||
const ref = useRef(null);
|
||||
const [nodes, setNodes] = useState<MyNode[]>([]);
|
||||
const onNodesChange: OnNodesChange<MyNode> = useCallback(
|
||||
(changes) =>
|
||||
@@ -165,6 +168,7 @@ const CustomNodeFlow = () => {
|
||||
minZoom={0.3}
|
||||
maxZoom={2}
|
||||
onBeforeDelete={onBeforeDelete}
|
||||
ref={ref}
|
||||
>
|
||||
<MiniMap<MyNode>
|
||||
nodeStrokeColor={(n: MyNode): string => {
|
||||
@@ -186,4 +190,8 @@ const CustomNodeFlow = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomNodeFlow;
|
||||
export default () => (
|
||||
<ReactFlowProvider>
|
||||
<CustomNodeFlow />
|
||||
</ReactFlowProvider>
|
||||
);
|
||||
|
||||
@@ -33,6 +33,7 @@ const DragHandleFlow = () => {
|
||||
edges={edges}
|
||||
nodeTypes={nodeTypes}
|
||||
onNodeClick={onNodeClick}
|
||||
nodeDragThreshold={0}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -36,7 +36,7 @@ const onNodeClick = (_: ReactMouseEvent, node: Node) => console.log('click', nod
|
||||
const onEdgeClick = (_: ReactMouseEvent, edge: Edge) => console.log('click', edge);
|
||||
const onPaneClick = (event: ReactMouseEvent) => console.log('onPaneClick', event);
|
||||
const onPaneScroll = (event?: WheelEvent) => console.log('onPaneScroll', event);
|
||||
const onPaneContextMenu = (event: ReactMouseEvent) => console.log('onPaneContextMenu', event);
|
||||
const onPaneContextMenu = (event: ReactMouseEvent | MouseEvent) => console.log('onPaneContextMenu', event);
|
||||
const onMoveEnd = (_: TouchEvent | MouseEvent | null, viewport: Viewport) => console.log('onMoveEnd', viewport);
|
||||
|
||||
const InteractionFlow = () => {
|
||||
@@ -80,6 +80,7 @@ const InteractionFlow = () => {
|
||||
onPaneClick={captureZoomClick ? onPaneClick : undefined}
|
||||
onPaneScroll={captureZoomScroll ? onPaneScroll : undefined}
|
||||
onPaneContextMenu={captureZoomClick ? onPaneContextMenu : undefined}
|
||||
nodeDragThreshold={0}
|
||||
onMoveEnd={onMoveEnd}
|
||||
>
|
||||
<MiniMap />
|
||||
|
||||
@@ -20,8 +20,8 @@ export type ResultNode = Node<{}, 'result'>;
|
||||
export type UppercaseNode = Node<{ text: string }, 'uppercase'>;
|
||||
export type MyNode = TextNode | ResultNode | UppercaseNode;
|
||||
|
||||
export function isTextNode(node: any): node is TextNode {
|
||||
return node.type === 'text';
|
||||
export function isTextNode(node: any): node is TextNode | UppercaseNode {
|
||||
return node.type === 'text' || node.type === 'uppercase';
|
||||
}
|
||||
|
||||
const nodeTypes = {
|
||||
|
||||
@@ -27,18 +27,21 @@ const initialNodes: Node[] = [
|
||||
data: { label: 'Node 2' },
|
||||
position: { x: 100, y: 100 },
|
||||
className: 'light',
|
||||
type: 'default',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
data: { label: 'Node 3' },
|
||||
position: { x: 400, y: 100 },
|
||||
className: 'light',
|
||||
type: 'default',
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
data: { label: 'Node 4' },
|
||||
position: { x: 400, y: 200 },
|
||||
className: 'light',
|
||||
type: 'default',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -85,6 +88,7 @@ const UseZoomPanHelperFlow = () => {
|
||||
data: {
|
||||
label: `${projectedPosition.x}-${projectedPosition.y}`,
|
||||
},
|
||||
type: 'default',
|
||||
})
|
||||
);
|
||||
},
|
||||
@@ -106,6 +110,7 @@ const UseZoomPanHelperFlow = () => {
|
||||
data: {
|
||||
label: 'New Node',
|
||||
},
|
||||
type: 'default',
|
||||
};
|
||||
|
||||
addNodes(newNode);
|
||||
@@ -139,8 +144,8 @@ const UseZoomPanHelperFlow = () => {
|
||||
|
||||
const onSetNodes = () => {
|
||||
setNodes([
|
||||
{ id: 'a', position: { x: 0, y: 0 }, data: { label: 'Node a' } },
|
||||
{ id: 'b', position: { x: 0, y: 150 }, data: { label: 'Node b' } },
|
||||
{ id: 'a', type: 'default', position: { x: 0, y: 0 }, data: { label: 'Node a' } },
|
||||
{ id: 'b', type: 'default', position: { x: 0, y: 150 }, data: { label: 'Node b' } },
|
||||
]);
|
||||
|
||||
setEdges([{ id: 'a-b', source: 'a', target: 'b' }]);
|
||||
|
||||
@@ -3,6 +3,7 @@ import { MarkerType } from '@xyflow/react';
|
||||
export default {
|
||||
flowProps: {
|
||||
fitView: true,
|
||||
multiSelectionKeyCode: 's',
|
||||
nodes: [
|
||||
{
|
||||
id: '1',
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"types": ["cypress", "cypress-real-events"]
|
||||
"types": ["cypress", "node", "cypress-real-events"]
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.tsx"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
"format": "prettier --plugin-search-dir . --write ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^2.1.1",
|
||||
"@sveltejs/kit": "^1.27.3",
|
||||
"@sveltejs/adapter-auto": "^3.1.1",
|
||||
"@sveltejs/kit": "^2.5.2",
|
||||
"@typescript-eslint/eslint-plugin": "^6.10.0",
|
||||
"@typescript-eslint/parser": "^6.10.0",
|
||||
"eslint": "^8.53.0",
|
||||
@@ -21,8 +21,8 @@
|
||||
"eslint-plugin-svelte": "^2.35.0",
|
||||
"prettier": "^3.0.3",
|
||||
"prettier-plugin-svelte": "^3.0.3",
|
||||
"svelte": "^4.2.2",
|
||||
"svelte-check": "^3.5.2",
|
||||
"svelte": "^4.2.12",
|
||||
"svelte-check": "^3.6.6",
|
||||
"tslib": "^2.6.2",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^4.5.0"
|
||||
@@ -30,6 +30,7 @@
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@dagrejs/dagre": "^1.0.4",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.2",
|
||||
"@xyflow/svelte": "workspace:^"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script lang="ts">
|
||||
import type { ChangeEventHandler } from 'svelte/elements';
|
||||
import { writable } from 'svelte/store';
|
||||
import {
|
||||
SvelteFlow,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import type { Writable } from 'svelte/store';
|
||||
import { Handle, Position, type NodeProps } from '@xyflow/svelte';
|
||||
import { Handle, Position, type NodeProps, type Node } from '@xyflow/svelte';
|
||||
|
||||
type $$Props = NodeProps;
|
||||
type $$Props = NodeProps<Node<{ colorStore: Writable<string> }>>;
|
||||
|
||||
export let data: { colorStore: Writable<string> };
|
||||
export let data: $$Props['data'];
|
||||
|
||||
const { colorStore } = data;
|
||||
</script>
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
type UppercaseNodeType = Node<{ text: string }, 'uppercase'>;
|
||||
type ResultNodeType = Node<{}, 'result'>;
|
||||
|
||||
export function isTextNode(node: any): node is TextNodeType {
|
||||
return node.type === 'text';
|
||||
export function isTextNode(node: any): node is TextNodeType | UppercaseNode {
|
||||
return node.type === 'text' || node.type === 'uppercase';
|
||||
}
|
||||
|
||||
export type MyNode = TextNodeType | UppercaseNodeType | ResultNodeType;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import adapter from '@sveltejs/adapter-auto';
|
||||
import { vitePreprocess } from '@sveltejs/kit/vite';
|
||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# @xyflow/react
|
||||
|
||||
## 12.0.0-next.11
|
||||
|
||||
## Patch changes
|
||||
|
||||
- fix `ref` prop for `ReactFlow` and `Handle` component
|
||||
- unify `Edge` and `Node` type handling
|
||||
- fix safari: prevent selection of viewport
|
||||
- fix `useNodesData` hook to prevent re-renderings
|
||||
- fix edges: allow start at 0,0
|
||||
|
||||
## 12.0.0-next.10
|
||||
|
||||
## ⚠️ Breaking changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@xyflow/react",
|
||||
"version": "12.0.0-next.10",
|
||||
"version": "12.0.0-next.11",
|
||||
"description": "React Flow - A highly customizable React library for building node-based editors and interactive flow charts.",
|
||||
"keywords": [
|
||||
"react",
|
||||
|
||||
@@ -111,7 +111,7 @@ export function EdgeWrapper<EdgeType extends Edge = Edge>({
|
||||
[edge.markerEnd, rfId]
|
||||
);
|
||||
|
||||
if (edge.hidden || !sourceX || !sourceY || !targetX || !targetY) {
|
||||
if (edge.hidden || sourceX === null || sourceY === null || targetX === null || targetY === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -212,6 +212,7 @@ export function EdgeWrapper<EdgeType extends Edge = Edge>({
|
||||
id={id}
|
||||
source={edge.source}
|
||||
target={edge.target}
|
||||
type={edge.type}
|
||||
selected={edge.selected}
|
||||
animated={edge.animated}
|
||||
label={edge.label}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { ComponentType } from 'react';
|
||||
import type { EdgeProps, EdgeTypes } from '../../types';
|
||||
import type { EdgeTypes } from '../../types';
|
||||
import {
|
||||
BezierEdgeInternal,
|
||||
StraightEdgeInternal,
|
||||
@@ -9,11 +8,11 @@ import {
|
||||
} from '../Edges';
|
||||
|
||||
export const builtinEdgeTypes: EdgeTypes = {
|
||||
default: BezierEdgeInternal as ComponentType<EdgeProps>,
|
||||
straight: StraightEdgeInternal as ComponentType<EdgeProps>,
|
||||
step: StepEdgeInternal as ComponentType<EdgeProps>,
|
||||
smoothstep: SmoothStepEdgeInternal as ComponentType<EdgeProps>,
|
||||
simplebezier: SimpleBezierEdgeInternal as ComponentType<EdgeProps>,
|
||||
default: BezierEdgeInternal,
|
||||
straight: StraightEdgeInternal,
|
||||
step: StepEdgeInternal,
|
||||
smoothstep: SmoothStepEdgeInternal,
|
||||
simplebezier: SimpleBezierEdgeInternal,
|
||||
};
|
||||
|
||||
export const nullPosition = {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
/*
|
||||
* The Handle component is used to connect nodes. When the user mousedowns a handle, we start the connection process.
|
||||
* The user can then drag the connection to another handle or node. When the user releases the mouse, we check if the
|
||||
* connection is valid and if so, we call the onConnect callback.
|
||||
*/
|
||||
import { memo, HTMLAttributes, forwardRef, MouseEvent as ReactMouseEvent, TouchEvent as ReactTouchEvent } from 'react';
|
||||
import {
|
||||
type HTMLAttributes,
|
||||
type MouseEvent as ReactMouseEvent,
|
||||
type TouchEvent as ReactTouchEvent,
|
||||
type ForwardedRef,
|
||||
memo,
|
||||
forwardRef,
|
||||
} from 'react';
|
||||
import cc from 'classcat';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import {
|
||||
@@ -58,190 +60,186 @@ const connectingSelector =
|
||||
};
|
||||
};
|
||||
|
||||
const HandleComponent = forwardRef<HTMLDivElement, HandleComponentProps>(
|
||||
(
|
||||
{
|
||||
type = 'source',
|
||||
position = Position.Top,
|
||||
isValidConnection,
|
||||
isConnectable = true,
|
||||
isConnectableStart = true,
|
||||
isConnectableEnd = true,
|
||||
id,
|
||||
onConnect,
|
||||
children,
|
||||
className,
|
||||
onMouseDown,
|
||||
onTouchStart,
|
||||
...rest
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const handleId = id || null;
|
||||
const isTarget = type === 'target';
|
||||
const store = useStoreApi();
|
||||
const nodeId = useNodeId();
|
||||
const { connectOnClick, noPanClassName, rfId } = useStore(selector, shallow);
|
||||
const { connectingFrom, connectingTo, clickConnecting, isPossibleEndHandle, connectionInProcess, valid } = useStore(
|
||||
connectingSelector(nodeId, handleId, type),
|
||||
shallow
|
||||
);
|
||||
function HandleComponent(
|
||||
{
|
||||
type = 'source',
|
||||
position = Position.Top,
|
||||
isValidConnection,
|
||||
isConnectable = true,
|
||||
isConnectableStart = true,
|
||||
isConnectableEnd = true,
|
||||
id,
|
||||
onConnect,
|
||||
children,
|
||||
className,
|
||||
onMouseDown,
|
||||
onTouchStart,
|
||||
...rest
|
||||
}: HandleComponentProps,
|
||||
ref: ForwardedRef<HTMLDivElement>
|
||||
) {
|
||||
const handleId = id || null;
|
||||
const isTarget = type === 'target';
|
||||
const store = useStoreApi();
|
||||
const nodeId = useNodeId();
|
||||
const { connectOnClick, noPanClassName, rfId } = useStore(selector, shallow);
|
||||
const { connectingFrom, connectingTo, clickConnecting, isPossibleEndHandle, connectionInProcess, valid } = useStore(
|
||||
connectingSelector(nodeId, handleId, type),
|
||||
shallow
|
||||
);
|
||||
|
||||
if (!nodeId) {
|
||||
store.getState().onError?.('010', errorMessages['error010']());
|
||||
if (!nodeId) {
|
||||
store.getState().onError?.('010', errorMessages['error010']());
|
||||
}
|
||||
|
||||
const onConnectExtended = (params: Connection) => {
|
||||
const { defaultEdgeOptions, onConnect: onConnectAction, hasDefaultEdges } = store.getState();
|
||||
|
||||
const edgeParams = {
|
||||
...defaultEdgeOptions,
|
||||
...params,
|
||||
};
|
||||
if (hasDefaultEdges) {
|
||||
const { edges, setEdges } = store.getState();
|
||||
setEdges(addEdge(edgeParams, edges));
|
||||
}
|
||||
|
||||
const onConnectExtended = (params: Connection) => {
|
||||
const { defaultEdgeOptions, onConnect: onConnectAction, hasDefaultEdges } = store.getState();
|
||||
onConnectAction?.(edgeParams);
|
||||
onConnect?.(edgeParams);
|
||||
};
|
||||
|
||||
const edgeParams = {
|
||||
...defaultEdgeOptions,
|
||||
...params,
|
||||
};
|
||||
if (hasDefaultEdges) {
|
||||
const { edges, setEdges } = store.getState();
|
||||
setEdges(addEdge(edgeParams, edges));
|
||||
}
|
||||
const onPointerDown = (event: ReactMouseEvent<HTMLDivElement> | ReactTouchEvent<HTMLDivElement>) => {
|
||||
if (!nodeId) {
|
||||
return;
|
||||
}
|
||||
|
||||
onConnectAction?.(edgeParams);
|
||||
onConnect?.(edgeParams);
|
||||
};
|
||||
const isMouseTriggered = isMouseEvent(event.nativeEvent);
|
||||
|
||||
const onPointerDown = (event: ReactMouseEvent<HTMLDivElement> | ReactTouchEvent<HTMLDivElement>) => {
|
||||
if (!nodeId) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
isConnectableStart &&
|
||||
((isMouseTriggered && (event as ReactMouseEvent<HTMLDivElement>).button === 0) || !isMouseTriggered)
|
||||
) {
|
||||
const currentStore = store.getState();
|
||||
|
||||
const isMouseTriggered = isMouseEvent(event.nativeEvent);
|
||||
|
||||
if (
|
||||
isConnectableStart &&
|
||||
((isMouseTriggered && (event as ReactMouseEvent<HTMLDivElement>).button === 0) || !isMouseTriggered)
|
||||
) {
|
||||
const currentStore = store.getState();
|
||||
|
||||
XYHandle.onPointerDown(event.nativeEvent, {
|
||||
autoPanOnConnect: currentStore.autoPanOnConnect,
|
||||
connectionMode: currentStore.connectionMode,
|
||||
connectionRadius: currentStore.connectionRadius,
|
||||
domNode: currentStore.domNode,
|
||||
nodes: currentStore.nodes,
|
||||
lib: currentStore.lib,
|
||||
isTarget,
|
||||
handleId,
|
||||
nodeId,
|
||||
flowId: currentStore.rfId,
|
||||
panBy: currentStore.panBy,
|
||||
cancelConnection: currentStore.cancelConnection,
|
||||
onConnectStart: currentStore.onConnectStart,
|
||||
onConnectEnd: currentStore.onConnectEnd,
|
||||
updateConnection: currentStore.updateConnection,
|
||||
onConnect: onConnectExtended,
|
||||
isValidConnection: isValidConnection || currentStore.isValidConnection,
|
||||
getTransform: () => store.getState().transform,
|
||||
});
|
||||
}
|
||||
|
||||
if (isMouseTriggered) {
|
||||
onMouseDown?.(event as ReactMouseEvent<HTMLDivElement>);
|
||||
} else {
|
||||
onTouchStart?.(event as ReactTouchEvent<HTMLDivElement>);
|
||||
}
|
||||
};
|
||||
|
||||
const onClick = (event: ReactMouseEvent) => {
|
||||
const {
|
||||
onClickConnectStart,
|
||||
onClickConnectEnd,
|
||||
connectionClickStartHandle,
|
||||
connectionMode,
|
||||
isValidConnection: isValidConnectionStore,
|
||||
lib,
|
||||
rfId: flowId,
|
||||
} = store.getState();
|
||||
|
||||
if (!nodeId || (!connectionClickStartHandle && !isConnectableStart)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!connectionClickStartHandle) {
|
||||
onClickConnectStart?.(event.nativeEvent, { nodeId, handleId, handleType: type });
|
||||
store.setState({ connectionClickStartHandle: { nodeId, type, handleId } });
|
||||
return;
|
||||
}
|
||||
|
||||
const doc = getHostForElement(event.target as HTMLElement);
|
||||
const isValidConnectionHandler = isValidConnection || isValidConnectionStore;
|
||||
const { connection, isValid } = XYHandle.isValid(event.nativeEvent, {
|
||||
handle: {
|
||||
nodeId,
|
||||
id: handleId,
|
||||
type,
|
||||
},
|
||||
connectionMode,
|
||||
fromNodeId: connectionClickStartHandle.nodeId,
|
||||
fromHandleId: connectionClickStartHandle.handleId || null,
|
||||
fromType: connectionClickStartHandle.type,
|
||||
isValidConnection: isValidConnectionHandler,
|
||||
flowId,
|
||||
doc,
|
||||
lib,
|
||||
XYHandle.onPointerDown(event.nativeEvent, {
|
||||
autoPanOnConnect: currentStore.autoPanOnConnect,
|
||||
connectionMode: currentStore.connectionMode,
|
||||
connectionRadius: currentStore.connectionRadius,
|
||||
domNode: currentStore.domNode,
|
||||
nodes: currentStore.nodes,
|
||||
lib: currentStore.lib,
|
||||
isTarget,
|
||||
handleId,
|
||||
nodeId,
|
||||
flowId: currentStore.rfId,
|
||||
panBy: currentStore.panBy,
|
||||
cancelConnection: currentStore.cancelConnection,
|
||||
onConnectStart: currentStore.onConnectStart,
|
||||
onConnectEnd: currentStore.onConnectEnd,
|
||||
updateConnection: currentStore.updateConnection,
|
||||
onConnect: onConnectExtended,
|
||||
isValidConnection: isValidConnection || currentStore.isValidConnection,
|
||||
getTransform: () => store.getState().transform,
|
||||
});
|
||||
}
|
||||
|
||||
if (isValid && connection) {
|
||||
onConnectExtended(connection);
|
||||
}
|
||||
if (isMouseTriggered) {
|
||||
onMouseDown?.(event as ReactMouseEvent<HTMLDivElement>);
|
||||
} else {
|
||||
onTouchStart?.(event as ReactTouchEvent<HTMLDivElement>);
|
||||
}
|
||||
};
|
||||
|
||||
onClickConnectEnd?.(event as unknown as MouseEvent);
|
||||
const onClick = (event: ReactMouseEvent) => {
|
||||
const {
|
||||
onClickConnectStart,
|
||||
onClickConnectEnd,
|
||||
connectionClickStartHandle,
|
||||
connectionMode,
|
||||
isValidConnection: isValidConnectionStore,
|
||||
lib,
|
||||
rfId: flowId,
|
||||
} = store.getState();
|
||||
|
||||
store.setState({ connectionClickStartHandle: null });
|
||||
};
|
||||
if (!nodeId || (!connectionClickStartHandle && !isConnectableStart)) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
data-handleid={handleId}
|
||||
data-nodeid={nodeId}
|
||||
data-handlepos={position}
|
||||
data-id={`${rfId}-${nodeId}-${handleId}-${type}`}
|
||||
className={cc([
|
||||
'react-flow__handle',
|
||||
`react-flow__handle-${position}`,
|
||||
'nodrag',
|
||||
noPanClassName,
|
||||
className,
|
||||
{
|
||||
source: !isTarget,
|
||||
target: isTarget,
|
||||
connectable: isConnectable,
|
||||
connectablestart: isConnectableStart,
|
||||
connectableend: isConnectableEnd,
|
||||
clickconnecting: clickConnecting,
|
||||
connectingfrom: connectingFrom,
|
||||
connectingto: connectingTo,
|
||||
valid,
|
||||
// shows where you can start a connection from
|
||||
// and where you can end it while connecting
|
||||
connectionindicator:
|
||||
isConnectable &&
|
||||
(!connectionInProcess || isPossibleEndHandle) &&
|
||||
(connectionInProcess ? isConnectableEnd : isConnectableStart),
|
||||
},
|
||||
])}
|
||||
onMouseDown={onPointerDown}
|
||||
onTouchStart={onPointerDown}
|
||||
onClick={connectOnClick ? onClick : undefined}
|
||||
ref={ref}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
if (!connectionClickStartHandle) {
|
||||
onClickConnectStart?.(event.nativeEvent, { nodeId, handleId, handleType: type });
|
||||
store.setState({ connectionClickStartHandle: { nodeId, type, handleId } });
|
||||
return;
|
||||
}
|
||||
|
||||
HandleComponent.displayName = 'Handle';
|
||||
const doc = getHostForElement(event.target as HTMLElement);
|
||||
const isValidConnectionHandler = isValidConnection || isValidConnectionStore;
|
||||
const { connection, isValid } = XYHandle.isValid(event.nativeEvent, {
|
||||
handle: {
|
||||
nodeId,
|
||||
id: handleId,
|
||||
type,
|
||||
},
|
||||
connectionMode,
|
||||
fromNodeId: connectionClickStartHandle.nodeId,
|
||||
fromHandleId: connectionClickStartHandle.handleId || null,
|
||||
fromType: connectionClickStartHandle.type,
|
||||
isValidConnection: isValidConnectionHandler,
|
||||
flowId,
|
||||
doc,
|
||||
lib,
|
||||
});
|
||||
|
||||
if (isValid && connection) {
|
||||
onConnectExtended(connection);
|
||||
}
|
||||
|
||||
onClickConnectEnd?.(event as unknown as MouseEvent);
|
||||
|
||||
store.setState({ connectionClickStartHandle: null });
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
data-handleid={handleId}
|
||||
data-nodeid={nodeId}
|
||||
data-handlepos={position}
|
||||
data-id={`${rfId}-${nodeId}-${handleId}-${type}`}
|
||||
className={cc([
|
||||
'react-flow__handle',
|
||||
`react-flow__handle-${position}`,
|
||||
'nodrag',
|
||||
noPanClassName,
|
||||
className,
|
||||
{
|
||||
source: !isTarget,
|
||||
target: isTarget,
|
||||
connectable: isConnectable,
|
||||
connectablestart: isConnectableStart,
|
||||
connectableend: isConnectableEnd,
|
||||
clickconnecting: clickConnecting,
|
||||
connectingfrom: connectingFrom,
|
||||
connectingto: connectingTo,
|
||||
valid,
|
||||
// shows where you can start a connection from
|
||||
// and where you can end it while connecting
|
||||
connectionindicator:
|
||||
isConnectable &&
|
||||
(!connectionInProcess || isPossibleEndHandle) &&
|
||||
(connectionInProcess ? isConnectableEnd : isConnectableStart),
|
||||
},
|
||||
])}
|
||||
onMouseDown={onPointerDown}
|
||||
onTouchStart={onPointerDown}
|
||||
onClick={connectOnClick ? onClick : undefined}
|
||||
ref={ref}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Handle component is the part of a node that can be used to connect nodes.
|
||||
* The Handle component is a UI element that is used to connect nodes.
|
||||
*/
|
||||
export const Handle = memo(HandleComponent);
|
||||
export const Handle = memo(forwardRef(HandleComponent));
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { ComponentType } from 'react';
|
||||
import type { NodeProps, XYPosition } from '@xyflow/system';
|
||||
import type { XYPosition } from '@xyflow/system';
|
||||
|
||||
import { InputNode } from '../Nodes/InputNode';
|
||||
import { DefaultNode } from '../Nodes/DefaultNode';
|
||||
@@ -15,10 +14,10 @@ export const arrowKeyDiffs: Record<string, XYPosition> = {
|
||||
};
|
||||
|
||||
export const builtinNodeTypes: NodeTypes = {
|
||||
input: InputNode as ComponentType<NodeProps>,
|
||||
default: DefaultNode as ComponentType<NodeProps>,
|
||||
output: OutputNode as ComponentType<NodeProps>,
|
||||
group: GroupNode as ComponentType<NodeProps>,
|
||||
input: InputNode,
|
||||
default: DefaultNode,
|
||||
output: OutputNode,
|
||||
group: GroupNode,
|
||||
};
|
||||
|
||||
export function getNodeInlineStyleDimensions<NodeType extends Node = Node>(
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { Position, type NodeProps } from '@xyflow/system';
|
||||
import { Position } from '@xyflow/system';
|
||||
|
||||
import { Handle } from '../../components/Handle';
|
||||
import type { BuiltInNode, NodeProps } from '../../types/nodes';
|
||||
|
||||
export function DefaultNode({
|
||||
data,
|
||||
isConnectable,
|
||||
targetPosition = Position.Top,
|
||||
sourcePosition = Position.Bottom,
|
||||
}: NodeProps) {
|
||||
}: NodeProps<BuiltInNode>) {
|
||||
return (
|
||||
<>
|
||||
<Handle type="target" position={targetPosition} isConnectable={isConnectable} />
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Position, type NodeProps } from '@xyflow/system';
|
||||
import { Position } from '@xyflow/system';
|
||||
|
||||
import { Handle } from '../../components/Handle';
|
||||
import type { BuiltInNode, NodeProps } from '../../types/nodes';
|
||||
|
||||
export function InputNode({ data, isConnectable, sourcePosition = Position.Bottom }: NodeProps) {
|
||||
export function InputNode({ data, isConnectable, sourcePosition = Position.Bottom }: NodeProps<BuiltInNode>) {
|
||||
return (
|
||||
<>
|
||||
{data?.label}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Position, type NodeProps } from '@xyflow/system';
|
||||
import { Position } from '@xyflow/system';
|
||||
|
||||
import { Handle } from '../../components/Handle';
|
||||
import type { BuiltInNode, NodeProps } from '../../types/nodes';
|
||||
|
||||
export function OutputNode({ data, isConnectable, targetPosition = Position.Top }: NodeProps) {
|
||||
export function OutputNode({ data, isConnectable, targetPosition = Position.Top }: NodeProps<BuiltInNode>) {
|
||||
return (
|
||||
<>
|
||||
<Handle type="target" position={targetPosition} isConnectable={isConnectable} />
|
||||
|
||||
@@ -172,7 +172,6 @@ function GraphViewComponent<NodeType extends Node = Node, EdgeType extends Edge
|
||||
containerStyle={connectionLineContainerStyle}
|
||||
/>
|
||||
<div className="react-flow__edgelabel-renderer" />
|
||||
<div className="react-flow__viewport-portal" />
|
||||
<NodeRenderer<NodeType>
|
||||
nodeTypes={nodeTypes}
|
||||
onNodeClick={onNodeClick}
|
||||
@@ -189,6 +188,7 @@ function GraphViewComponent<NodeType extends Node = Node, EdgeType extends Edge
|
||||
nodeExtent={nodeExtent}
|
||||
rfId={rfId}
|
||||
/>
|
||||
<div className="react-flow__viewport-portal" />
|
||||
</Viewport>
|
||||
</FlowRenderer>
|
||||
);
|
||||
|
||||
@@ -128,7 +128,8 @@ export function Pane({
|
||||
};
|
||||
|
||||
const onMouseMove = (event: ReactMouseEvent): void => {
|
||||
const { userSelectionRect, edges, transform, nodeOrigin, nodes, onNodesChange, onEdgesChange } = store.getState();
|
||||
const { userSelectionRect, edges, transform, nodeOrigin, nodes, triggerNodeChanges, triggerEdgeChanges } =
|
||||
store.getState();
|
||||
if (!isSelecting || !containerBounds.current || !userSelectionRect) {
|
||||
return;
|
||||
}
|
||||
@@ -172,17 +173,13 @@ export function Pane({
|
||||
if (prevSelectedNodesCount.current !== selectedNodeIds.size) {
|
||||
prevSelectedNodesCount.current = selectedNodeIds.size;
|
||||
const changes = getSelectionChanges(nodes, selectedNodeIds, true) as NodeChange[];
|
||||
if (changes.length) {
|
||||
onNodesChange?.(changes);
|
||||
}
|
||||
triggerNodeChanges(changes);
|
||||
}
|
||||
|
||||
if (prevSelectedEdgesCount.current !== selectedEdgeIds.size) {
|
||||
prevSelectedEdgesCount.current = selectedEdgeIds.size;
|
||||
const changes = getSelectionChanges(edges, selectedEdgeIds) as EdgeChange[];
|
||||
if (changes.length) {
|
||||
onEdgesChange?.(changes);
|
||||
}
|
||||
triggerEdgeChanges(changes);
|
||||
}
|
||||
|
||||
store.setState({
|
||||
|
||||
@@ -9,7 +9,7 @@ import { StoreUpdater } from '../../components/StoreUpdater';
|
||||
import { useColorModeClass } from '../../hooks/useColorModeClass';
|
||||
import { GraphView } from '../GraphView';
|
||||
import { Wrapper } from './Wrapper';
|
||||
import type { Edge, Node, ReactFlowProps, ReactFlowRefType } from '../../types';
|
||||
import type { Edge, Node, ReactFlowProps } from '../../types';
|
||||
import { defaultViewport as initViewport, defaultNodeOrigin } from './init-values';
|
||||
|
||||
const wrapperStyle: CSSProperties = {
|
||||
@@ -142,7 +142,7 @@ function ReactFlow<NodeType extends Node = Node, EdgeType extends Edge = Edge>(
|
||||
debug,
|
||||
...rest
|
||||
}: ReactFlowProps<NodeType, EdgeType>,
|
||||
ref: ForwardedRef<ReactFlowRefType>
|
||||
ref: ForwardedRef<HTMLDivElement>
|
||||
) {
|
||||
const rfId = id || '1';
|
||||
const colorModeClassName = useColorModeClass(colorMode);
|
||||
@@ -286,4 +286,4 @@ function ReactFlow<NodeType extends Node = Node, EdgeType extends Edge = Edge>(
|
||||
);
|
||||
}
|
||||
|
||||
export default forwardRef(ReactFlow) as typeof ReactFlow;
|
||||
export default forwardRef(ReactFlow);
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
import { useCallback } from 'react';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { shallowNodeData } from '@xyflow/system';
|
||||
|
||||
import { useStore } from '../hooks/useStore';
|
||||
import type { Node } from '../types';
|
||||
|
||||
export interface NodeDataReturn<NodeType extends Node> {
|
||||
id: string;
|
||||
type: NodeType['type'];
|
||||
data: NodeType['data'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for receiving data of one or multiple nodes
|
||||
*
|
||||
@@ -46,7 +40,7 @@ export function useNodesData(nodeIds: any): any {
|
||||
},
|
||||
[nodeIds]
|
||||
),
|
||||
shallow
|
||||
shallowNodeData
|
||||
);
|
||||
|
||||
return nodesData;
|
||||
|
||||
@@ -64,7 +64,6 @@ export {
|
||||
SelectionMode,
|
||||
type SelectionRect,
|
||||
type OnError,
|
||||
type NodeProps,
|
||||
type NodeOrigin,
|
||||
type OnSelectionDrag,
|
||||
Position,
|
||||
|
||||
@@ -509,5 +509,3 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
||||
*/
|
||||
debug?: boolean;
|
||||
}
|
||||
|
||||
export type ReactFlowRefType = HTMLDivElement;
|
||||
|
||||
@@ -98,10 +98,10 @@ export type EdgeTextProps = HTMLAttributes<SVGElement> &
|
||||
* Custom edge component props
|
||||
* @public
|
||||
*/
|
||||
export type EdgeProps<
|
||||
EdgeData extends Record<string, unknown> = Record<string, unknown>,
|
||||
EdgeType extends string | undefined = string | undefined
|
||||
> = Pick<Edge<EdgeData, EdgeType>, 'id' | 'animated' | 'data' | 'style' | 'selected' | 'source' | 'target'> &
|
||||
export type EdgeProps<EdgeType extends Edge = Edge> = Pick<
|
||||
EdgeType,
|
||||
'id' | 'animated' | 'data' | 'style' | 'selected' | 'source' | 'target'
|
||||
> &
|
||||
EdgePosition &
|
||||
EdgeLabelOptions & {
|
||||
sourceHandleId?: string | null;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ComponentType } from 'react';
|
||||
import {
|
||||
FitViewParamsBase,
|
||||
FitViewOptionsBase,
|
||||
@@ -9,13 +10,19 @@ import {
|
||||
SetCenter,
|
||||
FitBounds,
|
||||
XYPosition,
|
||||
NodeProps,
|
||||
OnBeforeDeleteBase,
|
||||
Connection,
|
||||
} from '@xyflow/system';
|
||||
|
||||
import type { NodeChange, EdgeChange, Node, Edge, ReactFlowInstance, EdgeProps } from '.';
|
||||
import { ComponentType } from 'react';
|
||||
import type { NodeChange, EdgeChange, Node, Edge, ReactFlowInstance, EdgeProps, NodeProps } from '.';
|
||||
|
||||
// this is needed, to use generics + forwardRef
|
||||
declare module 'react' {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
function forwardRef<T, P = {}>(
|
||||
render: (props: P, ref: React.Ref<T>) => React.ReactElement | null
|
||||
): (props: P & React.RefAttributes<T>) => React.ReactElement | null;
|
||||
}
|
||||
|
||||
export type OnNodesChange<NodeType extends Node = Node> = (changes: NodeChange<NodeType>[]) => void;
|
||||
export type OnEdgesChange<EdgeType extends Edge = Edge> = (changes: EdgeChange<EdgeType>[]) => void;
|
||||
@@ -27,8 +34,28 @@ export type OnDelete<NodeType extends Node = Node, EdgeType extends Edge = Edge>
|
||||
edges: EdgeType[];
|
||||
}) => void;
|
||||
|
||||
export type NodeTypes = { [key: string]: ComponentType<NodeProps> };
|
||||
export type EdgeTypes = { [key: string]: ComponentType<EdgeProps> };
|
||||
export type NodeTypes = Record<
|
||||
string,
|
||||
ComponentType<
|
||||
NodeProps & {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
data: any;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type: any;
|
||||
}
|
||||
>
|
||||
>;
|
||||
export type EdgeTypes = Record<
|
||||
string,
|
||||
ComponentType<
|
||||
EdgeProps & {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
data: any;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type: any;
|
||||
}
|
||||
>
|
||||
>;
|
||||
|
||||
export type UnselectNodesAndEdgesParams = {
|
||||
nodes?: Node[];
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { CSSProperties, MouseEvent as ReactMouseEvent } from 'react';
|
||||
import type { CoordinateExtent, NodeBase, NodeOrigin, OnError } from '@xyflow/system';
|
||||
import type { CoordinateExtent, NodeBase, NodeOrigin, OnError, NodeProps as NodePropsBase } from '@xyflow/system';
|
||||
|
||||
import { NodeTypes } from './general';
|
||||
|
||||
@@ -9,7 +9,7 @@ import { NodeTypes } from './general';
|
||||
*/
|
||||
export type Node<
|
||||
NodeData extends Record<string, unknown> = Record<string, unknown>,
|
||||
NodeType extends string | undefined = string | undefined
|
||||
NodeType extends string = string
|
||||
> = NodeBase<NodeData, NodeType> & {
|
||||
style?: CSSProperties;
|
||||
className?: string;
|
||||
@@ -49,3 +49,5 @@ export type NodeWrapperProps<NodeType extends Node> = {
|
||||
};
|
||||
|
||||
export type BuiltInNode = Node<{ label: string }, 'input' | 'output' | 'default'>;
|
||||
|
||||
export type NodeProps<NodeType extends Node = Node> = NodePropsBase<NodeType>;
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# @xyflow/svelte
|
||||
|
||||
## 0.0.38
|
||||
|
||||
## ⚠️ Breaking changes
|
||||
|
||||
- `NodeProps` generic is a node and not only node data. `type $$Props = NodeProps<AppNode>`
|
||||
|
||||
## Patch changes
|
||||
|
||||
- unify `Edge` and `Node` type handling
|
||||
- fix safari: prevent selection of viewport
|
||||
|
||||
## 0.0.37
|
||||
|
||||
## ⚠️ Breaking changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@xyflow/svelte",
|
||||
"version": "0.0.37",
|
||||
"version": "0.0.38",
|
||||
"description": "Svelte Flow - A highly customizable Svelte library for building node-based editors, workflow systems, diagrams and more.",
|
||||
"keywords": [
|
||||
"svelte",
|
||||
@@ -46,9 +46,9 @@
|
||||
"classcat": "^5.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^2.1.0",
|
||||
"@sveltejs/kit": "^1.22.6",
|
||||
"@sveltejs/package": "^2.2.1",
|
||||
"@sveltejs/adapter-auto": "^3.1.1",
|
||||
"@sveltejs/kit": "^2.5.2",
|
||||
"@sveltejs/package": "^2.2.7",
|
||||
"@typescript-eslint/eslint-plugin": "^5.60.0",
|
||||
"@typescript-eslint/parser": "^5.60.0",
|
||||
"autoprefixer": "^10.4.15",
|
||||
@@ -65,8 +65,8 @@
|
||||
"postcss-rename": "^0.6.1",
|
||||
"prettier": "^2.8.8",
|
||||
"prettier-plugin-svelte": "^2.10.1",
|
||||
"svelte": "^4.2.0",
|
||||
"svelte-check": "^3.5.0",
|
||||
"svelte": "^4.2.12",
|
||||
"svelte-check": "^3.6.6",
|
||||
"svelte-eslint-parser": "^0.32.2",
|
||||
"tslib": "^2.5.3",
|
||||
"typescript": "5.1.3"
|
||||
|
||||
@@ -51,7 +51,8 @@
|
||||
edgecontextmenu: { edge: Edge; event: MouseEvent };
|
||||
}>();
|
||||
|
||||
$: edgeComponent = $edgeTypes[type!] || BezierEdgeInternal;
|
||||
const edgeType = type || 'default';
|
||||
$: edgeComponent = $edgeTypes[edgeType] || BezierEdgeInternal;
|
||||
$: markerStartUrl = markerStart ? `url(#${getMarkerId(markerStart, $flowId)})` : undefined;
|
||||
$: markerEndUrl = markerEnd ? `url(#${getMarkerId(markerEnd, $flowId)})` : undefined;
|
||||
$: isSelectable = selectable || ($elementsSelectable && typeof selectable === 'undefined');
|
||||
@@ -113,6 +114,7 @@
|
||||
{data}
|
||||
{style}
|
||||
{interactionWidth}
|
||||
type={edgeType}
|
||||
sourceHandleId={sourceHandle}
|
||||
targetHandleId={targetHandle}
|
||||
markerStart={markerStartUrl}
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
<svelte:options immutable />
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
createEventDispatcher,
|
||||
setContext,
|
||||
SvelteComponent,
|
||||
type ComponentType,
|
||||
onDestroy
|
||||
} from 'svelte';
|
||||
import { createEventDispatcher, setContext, onDestroy } from 'svelte';
|
||||
import { get, writable } from 'svelte/store';
|
||||
import cc from 'classcat';
|
||||
import { errorMessages, Position, type NodeProps } from '@xyflow/system';
|
||||
import { errorMessages, Position } from '@xyflow/system';
|
||||
|
||||
import drag from '$lib/actions/drag';
|
||||
import { useStore } from '$lib/store';
|
||||
@@ -71,8 +65,7 @@
|
||||
console.warn('003', errorMessages['error003'](type!));
|
||||
}
|
||||
|
||||
const nodeComponent: ComponentType<SvelteComponent<NodeProps>> =
|
||||
$nodeTypes[nodeType] || DefaultNode;
|
||||
const nodeComponent = $nodeTypes[nodeType] || DefaultNode;
|
||||
const dispatch = createEventDispatcher<{
|
||||
nodeclick: { node: Node; event: MouseEvent | TouchEvent };
|
||||
nodecontextmenu: { node: Node; event: MouseEvent | TouchEvent };
|
||||
@@ -211,11 +204,11 @@
|
||||
{selected}
|
||||
{sourcePosition}
|
||||
{targetPosition}
|
||||
{type}
|
||||
{zIndex}
|
||||
{dragging}
|
||||
{dragHandle}
|
||||
isConnectable={connectable}
|
||||
type={nodeType}
|
||||
isConnectable={$connectableStore}
|
||||
positionAbsoluteX={positionX}
|
||||
positionAbsoluteY={positionY}
|
||||
{width}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
export let id: $$Props['id'] = '';
|
||||
export let source: $$Props['source'] = '';
|
||||
export let target: $$Props['target'] = '';
|
||||
export let type: $$Props['type'] = 'default';
|
||||
export let animated: $$Props['animated'] = undefined;
|
||||
export let selected: $$Props['selected'] = undefined;
|
||||
export let label: $$Props['label'] = undefined;
|
||||
@@ -43,6 +44,7 @@
|
||||
id;
|
||||
source;
|
||||
target;
|
||||
type;
|
||||
animated;
|
||||
selected;
|
||||
data;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
export let id: $$Props['id'] = '';
|
||||
export let source: $$Props['source'] = '';
|
||||
export let target: $$Props['target'] = '';
|
||||
export let type: $$Props['type'] = 'smoothstep';
|
||||
|
||||
export let animated: $$Props['animated'] = undefined;
|
||||
export let selected: $$Props['selected'] = undefined;
|
||||
@@ -43,6 +44,7 @@
|
||||
id;
|
||||
source;
|
||||
target;
|
||||
type;
|
||||
animated;
|
||||
selected;
|
||||
data;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
export let id: $$Props['id'] = '';
|
||||
export let source: $$Props['source'] = '';
|
||||
export let target: $$Props['target'] = '';
|
||||
export let type: $$Props['type'] = 'step';
|
||||
|
||||
export let animated: $$Props['animated'] = undefined;
|
||||
export let selected: $$Props['selected'] = undefined;
|
||||
@@ -44,6 +45,7 @@
|
||||
id;
|
||||
source;
|
||||
target;
|
||||
type;
|
||||
animated;
|
||||
selected;
|
||||
data;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
export let id: $$Props['id'] = '';
|
||||
export let source: $$Props['source'] = '';
|
||||
export let target: $$Props['target'] = '';
|
||||
export let type: $$Props['type'] = 'straight';
|
||||
|
||||
export let animated: $$Props['animated'] = undefined;
|
||||
export let selected: $$Props['selected'] = undefined;
|
||||
@@ -41,6 +42,7 @@
|
||||
id;
|
||||
source;
|
||||
target;
|
||||
type;
|
||||
animated;
|
||||
selected;
|
||||
data;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { Position, type NodeProps } from '@xyflow/system';
|
||||
import { Position } from '@xyflow/system';
|
||||
|
||||
import { Handle } from '$lib/components/Handle';
|
||||
import type { NodeProps } from '$lib/types';
|
||||
|
||||
interface $$Props extends NodeProps<{ label: string }> {}
|
||||
interface $$Props extends NodeProps {}
|
||||
|
||||
export let data: $$Props['data'] = { label: 'Node' };
|
||||
export let targetPosition: $$Props['targetPosition'] = Position.Top;
|
||||
@@ -15,12 +16,12 @@
|
||||
export let height: $$Props['height'] = undefined;
|
||||
export let selected: $$Props['selected'] = undefined;
|
||||
export let type: $$Props['type'] = undefined;
|
||||
export let zIndex: $$Props['zIndex'] = undefined;
|
||||
export let dragging: $$Props['dragging'] = false;
|
||||
export let dragHandle: $$Props['dragHandle'] = undefined;
|
||||
export let positionAbsoluteX: $$Props['positionAbsoluteX'] = 0;
|
||||
export let positionAbsoluteY: $$Props['positionAbsoluteY'] = 0;
|
||||
export let isConnectable: $$Props['isConnectable'] = undefined;
|
||||
export let isConnectable: $$Props['isConnectable'];
|
||||
export let zIndex: $$Props['zIndex'];
|
||||
|
||||
// @todo: there must be a better way to do this
|
||||
id;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import type { NodeProps } from '@xyflow/system';
|
||||
import type { NodeProps } from '$lib/types';
|
||||
|
||||
interface $$Props extends NodeProps<{}> {}
|
||||
interface $$Props extends NodeProps {}
|
||||
|
||||
// unused props - we need to list them here in order to prevent warnings
|
||||
export let id: $$Props['id'] = '';
|
||||
@@ -12,12 +12,12 @@
|
||||
export let sourcePosition: $$Props['sourcePosition'] = undefined;
|
||||
export let targetPosition: $$Props['targetPosition'] = undefined;
|
||||
export let type: $$Props['type'] = undefined;
|
||||
export let zIndex: $$Props['zIndex'] = undefined;
|
||||
export let dragging: $$Props['dragging'] = false;
|
||||
export let dragHandle: $$Props['dragHandle'] = undefined;
|
||||
export let positionAbsoluteX: $$Props['positionAbsoluteX'] = 0;
|
||||
export let positionAbsoluteY: $$Props['positionAbsoluteY'] = 0;
|
||||
export let isConnectable: $$Props['isConnectable'] = undefined;
|
||||
export let isConnectable: $$Props['isConnectable'];
|
||||
export let zIndex: $$Props['zIndex'];
|
||||
|
||||
// @todo: there must be a better way to do this
|
||||
id;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { Position, type NodeProps } from '@xyflow/system';
|
||||
import { Position } from '@xyflow/system';
|
||||
import type { NodeProps } from '$lib/types';
|
||||
|
||||
import { Handle } from '$lib/components/Handle';
|
||||
|
||||
interface $$Props extends NodeProps<{ label: string }> {}
|
||||
interface $$Props extends NodeProps {}
|
||||
|
||||
export let data: $$Props['data'] = { label: 'Node' };
|
||||
export let sourcePosition: $$Props['sourcePosition'] = Position.Bottom;
|
||||
@@ -15,12 +16,12 @@
|
||||
export let selected: $$Props['selected'] = undefined;
|
||||
export let targetPosition: $$Props['targetPosition'] = undefined;
|
||||
export let type: $$Props['type'] = undefined;
|
||||
export let zIndex: $$Props['zIndex'] = undefined;
|
||||
export let dragging: $$Props['dragging'] = false;
|
||||
export let dragHandle: $$Props['dragHandle'] = undefined;
|
||||
export let positionAbsoluteX: $$Props['positionAbsoluteX'] = 0;
|
||||
export let positionAbsoluteY: $$Props['positionAbsoluteY'] = 0;
|
||||
export let isConnectable: $$Props['isConnectable'] = undefined;
|
||||
export let isConnectable: $$Props['isConnectable'];
|
||||
export let zIndex: $$Props['zIndex'];
|
||||
|
||||
// @todo: there must be a better way to do this
|
||||
id;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { Position, type NodeProps } from '@xyflow/system';
|
||||
import { Position } from '@xyflow/system';
|
||||
import type { NodeProps } from '$lib/types';
|
||||
|
||||
import { Handle } from '$lib/components/Handle';
|
||||
|
||||
interface $$Props extends NodeProps<{ label: string }> {}
|
||||
interface $$Props extends NodeProps {}
|
||||
|
||||
export let data: $$Props['data'] = { label: 'Node' };
|
||||
export let targetPosition: $$Props['targetPosition'] = Position.Top;
|
||||
@@ -15,12 +16,12 @@
|
||||
export let selected: $$Props['selected'] = undefined;
|
||||
export let sourcePosition: $$Props['sourcePosition'] = undefined;
|
||||
export let type: $$Props['type'] = undefined;
|
||||
export let zIndex: $$Props['zIndex'] = undefined;
|
||||
export let dragging: $$Props['dragging'] = false;
|
||||
export let dragHandle: $$Props['dragHandle'] = undefined;
|
||||
export let positionAbsoluteX: $$Props['positionAbsoluteX'] = 0;
|
||||
export let positionAbsoluteY: $$Props['positionAbsoluteY'] = 0;
|
||||
export let isConnectable: $$Props['isConnectable'] = undefined;
|
||||
export let isConnectable: $$Props['isConnectable'];
|
||||
export let zIndex: $$Props['zIndex'];
|
||||
|
||||
// @todo: there must be a better way to do this
|
||||
id;
|
||||
|
||||
@@ -1,22 +1,9 @@
|
||||
import { derived, type Readable } from 'svelte/store';
|
||||
import { shallowNodeData } from '@xyflow/system';
|
||||
|
||||
import type { Node } from '$lib/types';
|
||||
import { useStore } from '$lib/store';
|
||||
|
||||
function areNodesDataEqual(a: any[], b: any[]) {
|
||||
if (a.length !== b.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
if (a[i].data !== b[i].data) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for receiving data of one or multiple nodes
|
||||
*
|
||||
@@ -51,7 +38,7 @@ export function useNodesData(nodeIds: any): any {
|
||||
}
|
||||
}
|
||||
|
||||
if (!areNodesDataEqual(nextNodesData, prevNodesData)) {
|
||||
if (!shallowNodeData(nextNodesData, prevNodesData)) {
|
||||
prevNodesData = nextNodesData;
|
||||
set(isArrayOfIds ? nextNodesData : nextNodesData[0] ?? null);
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ export type {
|
||||
DefaultEdgeOptions
|
||||
} from '$lib/types/edges';
|
||||
export type { HandleComponentProps, FitViewOptions } from '$lib/types/general';
|
||||
export type { Node, NodeTypes, DefaultNodeOptions, BuiltInNode } from '$lib/types/nodes';
|
||||
export type { Node, NodeTypes, DefaultNodeOptions, BuiltInNode, NodeProps } from '$lib/types/nodes';
|
||||
export type { SvelteFlowStore } from '$lib/store/types';
|
||||
|
||||
// system types
|
||||
@@ -77,7 +77,6 @@ export {
|
||||
SelectionMode,
|
||||
type SelectionRect,
|
||||
type OnError,
|
||||
type NodeProps,
|
||||
type NodeOrigin,
|
||||
type OnSelectionDrag,
|
||||
Position,
|
||||
|
||||
@@ -49,11 +49,14 @@ export type BuiltInEdge = SmoothStepEdge | BezierEdge | StepEdge;
|
||||
/**
|
||||
* Custom edge component props.
|
||||
*/
|
||||
export type EdgeProps<
|
||||
EdgeData extends Record<string, unknown> = Record<string, unknown>,
|
||||
EdgeType extends string | undefined = string | undefined
|
||||
> = Omit<Edge<EdgeData, EdgeType>, 'sourceHandle' | 'targetHandle' | 'type'> &
|
||||
export type EdgeProps<EdgeType extends Edge = Edge> = Omit<
|
||||
EdgeType,
|
||||
'sourceHandle' | 'targetHandle'
|
||||
> &
|
||||
EdgePosition & {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
data?: any;
|
||||
type: string;
|
||||
markerStart?: string;
|
||||
markerEnd?: string;
|
||||
sourceHandleId?: string | null;
|
||||
@@ -103,7 +106,19 @@ export type StepEdgeProps = EdgeComponentWithPathOptions<StepPathOptions>;
|
||||
*/
|
||||
export type StraightEdgeProps = Omit<EdgeComponentProps, 'sourcePosition' | 'targetPosition'>;
|
||||
|
||||
export type EdgeTypes = Record<string, ComponentType<SvelteComponent<EdgeProps>>>;
|
||||
export type EdgeTypes = Record<
|
||||
string,
|
||||
ComponentType<
|
||||
SvelteComponent<
|
||||
EdgeProps & {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
data?: any;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type: any;
|
||||
}
|
||||
>
|
||||
>
|
||||
>;
|
||||
|
||||
export type DefaultEdgeOptions = DefaultEdgeOptionsBase<Edge>;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ComponentType, SvelteComponent } from 'svelte';
|
||||
import type { NodeBase, NodeProps } from '@xyflow/system';
|
||||
import type { NodeBase, NodeProps as NodePropsBase } from '@xyflow/system';
|
||||
|
||||
/**
|
||||
* The node data structure that gets used for the nodes prop.
|
||||
@@ -7,13 +7,32 @@ import type { NodeBase, NodeProps } from '@xyflow/system';
|
||||
*/
|
||||
export type Node<
|
||||
NodeData extends Record<string, unknown> = Record<string, unknown>,
|
||||
NodeType extends string | undefined = string | undefined
|
||||
NodeType extends string = string
|
||||
> = NodeBase<NodeData, NodeType> & {
|
||||
class?: string;
|
||||
style?: string;
|
||||
};
|
||||
|
||||
export type NodeTypes = Record<string, ComponentType<SvelteComponent<NodeProps>>>;
|
||||
// @todo: currently generics for nodes are not really supported
|
||||
// let's fix `type: any` when we migrate to Svelte 5
|
||||
export type NodeProps<NodeType extends Node = Node> = NodePropsBase<NodeType> & {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type: any;
|
||||
};
|
||||
|
||||
export type NodeTypes = Record<
|
||||
string,
|
||||
ComponentType<
|
||||
SvelteComponent<
|
||||
NodeProps & {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
data: any;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type: any;
|
||||
}
|
||||
>
|
||||
>
|
||||
>;
|
||||
|
||||
export type DefaultNodeOptions = Partial<Omit<Node, 'id'>>;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@xyflow/system",
|
||||
"version": "0.0.18",
|
||||
"version": "0.0.19",
|
||||
"description": "xyflow core system that powers React Flow and Svelte Flow.",
|
||||
"keywords": [
|
||||
"node-based UI",
|
||||
|
||||
@@ -324,6 +324,7 @@ svg.xy-flow__connectionline {
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.xy-flow__minimap {
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Optional } from '../utils/types';
|
||||
*/
|
||||
export type NodeBase<
|
||||
NodeData extends Record<string, unknown> = Record<string, unknown>,
|
||||
NodeType extends string | undefined = string | undefined
|
||||
NodeType extends string = string
|
||||
> = {
|
||||
/** Unique id of a node */
|
||||
id: string;
|
||||
@@ -82,25 +82,19 @@ export type NodeBase<
|
||||
* The node data structure that gets used for the nodes prop.
|
||||
*
|
||||
* @public
|
||||
* @param id - The id of the node.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type NodeProps<NodeData = any> = {
|
||||
id: NodeBase['id'];
|
||||
data: NodeData;
|
||||
dragHandle: NodeBase['dragHandle'];
|
||||
type: NodeBase['type'];
|
||||
selected: NodeBase['selected'];
|
||||
isConnectable: NodeBase['connectable'];
|
||||
zIndex: NodeBase['zIndex'];
|
||||
positionAbsoluteX: number;
|
||||
positionAbsoluteY: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
dragging: NodeBase['dragging'];
|
||||
sourcePosition?: NodeBase['sourcePosition'];
|
||||
targetPosition?: NodeBase['targetPosition'];
|
||||
};
|
||||
export type NodeProps<NodeType extends NodeBase> = Pick<
|
||||
NodeType,
|
||||
'id' | 'data' | 'width' | 'height' | 'sourcePosition' | 'targetPosition' | 'selected' | 'dragHandle'
|
||||
> &
|
||||
Required<Pick<NodeType, 'type' | 'dragging' | 'zIndex'>> & {
|
||||
/** whether a node is connectable or not */
|
||||
isConnectable: boolean;
|
||||
/** position absolute x value */
|
||||
positionAbsoluteX: number;
|
||||
/** position absolute x value */
|
||||
positionAbsoluteY: number;
|
||||
};
|
||||
|
||||
export type NodeHandleBounds = {
|
||||
source: HandleElement[] | null;
|
||||
|
||||
@@ -7,3 +7,4 @@ export * from './marker';
|
||||
export * from './node-toolbar';
|
||||
export * from './store';
|
||||
export * from './types';
|
||||
export * from './shallow-node-data';
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { NodeBase } from '../types';
|
||||
|
||||
type NodeData = Pick<NodeBase, 'id' | 'type' | 'data'>;
|
||||
|
||||
export function shallowNodeData(a: NodeData | NodeData[], b: NodeData | NodeData[]) {
|
||||
const _a = Array.isArray(a) ? a : [a];
|
||||
const _b = Array.isArray(b) ? b : [b];
|
||||
|
||||
if (_a.length !== _b.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < _a.length; i++) {
|
||||
if (_a[i].id !== _b[i].id || _a[i].type !== _b[i].type || !Object.is(_a[i].data, _b[i].data)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
Generated
+876
-326
File diff suppressed because it is too large
Load Diff
@@ -30,11 +30,12 @@ test.describe('Edges', () => {
|
||||
await edge1.click();
|
||||
await expect(edge1).toHaveClass(/selected/);
|
||||
|
||||
// FIXME: I cannot get this to work
|
||||
await page.keyboard.down('MetaLeft');
|
||||
// we are using "s" here because Meta doesn't work for some reason
|
||||
await page.keyboard.down('s');
|
||||
await edge2.click();
|
||||
await expect(edge2).toHaveClass(/selected/);
|
||||
await page.keyboard.up('s');
|
||||
|
||||
await expect(edge2).toHaveClass(/selected/);
|
||||
await expect(edge1).toHaveClass(/selected/);
|
||||
});
|
||||
});
|
||||
@@ -88,8 +89,6 @@ test.describe('Edges', () => {
|
||||
|
||||
await expect(edge).toBeAttached();
|
||||
|
||||
const edgeBox = await edge.boundingBox();
|
||||
|
||||
await edge.click();
|
||||
await expect(edge).toHaveClass(/selected/);
|
||||
|
||||
@@ -154,8 +153,8 @@ test.describe('Edges', () => {
|
||||
|
||||
await expect(edge).toBeAttached();
|
||||
|
||||
await expect(edge).toHaveAttribute('marker-start', 'url(#1__type=arrowclosed)');
|
||||
await expect(edge).toHaveAttribute('marker-end', 'url(#1__type=arrow)');
|
||||
await expect(edge).toHaveAttribute('marker-start', "url('#1__type=arrowclosed')");
|
||||
await expect(edge).toHaveAttribute('marker-end', "url('#1__type=arrow')");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -52,7 +52,7 @@ test.describe('Pane default', () => {
|
||||
});
|
||||
|
||||
test.describe('minZoom & maxZoom', () => {
|
||||
test('minZoom works as intended', async ({ page }) => {
|
||||
test('minZoom', async ({ page }) => {
|
||||
const pane = page.locator(`.${FRAMEWORK}-flow__pane`);
|
||||
const viewport = page.locator(`.${FRAMEWORK}-flow__viewport`);
|
||||
|
||||
@@ -67,7 +67,7 @@ test.describe('Pane default', () => {
|
||||
expect(transformsMinZoom.scale).toBe(0.25);
|
||||
});
|
||||
|
||||
test('maxZoom works as intended', async ({ page }) => {
|
||||
test('maxZoom', async ({ page }) => {
|
||||
const pane = page.locator(`.${FRAMEWORK}-flow__pane`);
|
||||
const viewport = page.locator(`.${FRAMEWORK}-flow__viewport`);
|
||||
|
||||
@@ -84,7 +84,7 @@ test.describe('Pane default', () => {
|
||||
});
|
||||
|
||||
test.describe('autoPan', () => {
|
||||
test('autoPanOnNodeDrag works as intended', async ({ page }) => {
|
||||
test('autoPanOnNodeDrag', async ({ page }) => {
|
||||
const viewport = page.locator(`.${FRAMEWORK}-flow__viewport`);
|
||||
const node = page.locator('[data-id="1"]');
|
||||
|
||||
@@ -96,7 +96,8 @@ test.describe('Pane default', () => {
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(0, 0);
|
||||
await page.waitForTimeout(500);
|
||||
await page.mouse.move(100, 100);
|
||||
await page.mouse.move(1000, 100, { steps: 100 });
|
||||
await page.mouse.up();
|
||||
|
||||
const transformAfter = await getTransform(viewport);
|
||||
|
||||
@@ -104,7 +105,7 @@ test.describe('Pane default', () => {
|
||||
await expect(transformAfter.translateY).toBeGreaterThan(transformBefore.translateY);
|
||||
});
|
||||
|
||||
test('autoPanOnConnect works as intended', async ({ page }) => {
|
||||
test('autoPanOnConnect', async ({ page }) => {
|
||||
const viewport = page.locator(`.${FRAMEWORK}-flow__viewport`);
|
||||
const handle = page.locator(`[data-id="1"] .${FRAMEWORK}-flow__handle`);
|
||||
|
||||
@@ -116,7 +117,7 @@ test.describe('Pane default', () => {
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(0, 0);
|
||||
await page.waitForTimeout(500);
|
||||
await page.mouse.move(100, 100);
|
||||
await page.mouse.move(100, 100, { steps: 100 });
|
||||
|
||||
const transformAfter = await getTransform(viewport);
|
||||
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.40.1",
|
||||
"@playwright/test": "^1.42.1",
|
||||
"@types/node": "^18.7.16"
|
||||
},
|
||||
"dependencies": {
|
||||
"@playwright/experimental-ct-react": "^1.40.1",
|
||||
"@playwright/experimental-ct-react": "^1.42.1",
|
||||
"@types/react": "^18.2.31",
|
||||
"@types/react-dom": "^18.2.14",
|
||||
"react": "^18.2.0",
|
||||
|
||||
Reference in New Issue
Block a user