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