Merge branch 'next' into feat/multiple-on-selection-change
This commit is contained in:
@@ -2,7 +2,7 @@ import { defineConfig } from 'cypress';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
baseUrl: 'http://localhost:3000',
|
||||
baseUrl: 'http://localhost:3000/examples',
|
||||
viewportWidth: 1280,
|
||||
viewportHeight: 720,
|
||||
video: false,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>React Flow Examples</title>
|
||||
</head>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@xyflow/react-examples",
|
||||
"name": "react-examples",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
@@ -15,25 +15,25 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@xyflow/react": "workspace:*",
|
||||
"classcat": "^5.0.3",
|
||||
"classcat": "^5.0.4",
|
||||
"dagre": "^0.8.5",
|
||||
"localforage": "^1.10.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"zustand": "^4.4.1"
|
||||
"react-router-dom": "^6.18.0",
|
||||
"zustand": "^4.4.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cypress/skip-test": "^2.6.1",
|
||||
"@types/dagre": "^0.7.48",
|
||||
"@types/react": "^18.0.17",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@vitejs/plugin-react": "4.0.4",
|
||||
"@vitejs/plugin-react-swc": "^3.3.2",
|
||||
"@types/dagre": "^0.7.52",
|
||||
"@types/react": "^18.2.36",
|
||||
"@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.10.0",
|
||||
"start-server-and-test": "^1.14.0",
|
||||
"typescript": "5.1.3",
|
||||
"vite": "4.4.9"
|
||||
"cypress-real-events": "1.11.0",
|
||||
"start-server-and-test": "^2.0.2",
|
||||
"typescript": "5.2.2",
|
||||
"vite": "4.5.0"
|
||||
}
|
||||
}
|
||||
|
||||
BIN
examples/react/public/favicon.ico
Normal file
BIN
examples/react/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 111 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
34
examples/react/src/App/header.tsx
Normal file
34
examples/react/src/App/header.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Outlet, useLocation, useNavigate } from 'react-router-dom';
|
||||
|
||||
import routes from './routes';
|
||||
|
||||
export default function Header() {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const [currentPath, setCurrentPath] = useState(location.pathname);
|
||||
|
||||
useEffect(() => {
|
||||
const name = routes.find((route) => route.path === currentPath)?.name;
|
||||
document.title = `React Flow Examples${name ? ' - ' + name : ''}`;
|
||||
navigate(currentPath);
|
||||
}, [currentPath]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<header>
|
||||
<a className="logo" href="https://github.com/xyflow/xyflow">
|
||||
React Flow Dev
|
||||
</a>
|
||||
<select value={currentPath} onChange={(event) => setCurrentPath(event.target.value)}>
|
||||
{routes.map((route) => (
|
||||
<option value={route.path} key={route.path}>
|
||||
{route.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</header>
|
||||
<Outlet />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,326 +1,21 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { BrowserRouter, Route, Routes, useLocation, useNavigate } from 'react-router-dom';
|
||||
import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
|
||||
|
||||
import routes from './routes';
|
||||
import GenericTestRoutes from '../generic-tests';
|
||||
import Basic from '../examples/Basic';
|
||||
import Backgrounds from '../examples/Backgrounds';
|
||||
import ControlledUncontrolled from '../examples/ControlledUncontrolled';
|
||||
import ControlledViewport from '../examples/ControlledViewport';
|
||||
import CustomConnectionLine from '../examples/CustomConnectionLine';
|
||||
import CustomMiniMapNode from '../examples/CustomMiniMapNode';
|
||||
import CustomNode from '../examples/CustomNode';
|
||||
import DefaultNodes from '../examples/DefaultNodes';
|
||||
import DragHandle from '../examples/DragHandle';
|
||||
import DragNDrop from '../examples/DragNDrop';
|
||||
import EasyConnect from '../examples/EasyConnect';
|
||||
import Edges from '../examples/Edges';
|
||||
import EdgeRenderer from '../examples/EdgeRenderer';
|
||||
import EdgeTypes from '../examples/EdgeTypes';
|
||||
import Empty from '../examples/Empty';
|
||||
import Figma from '../examples/Figma';
|
||||
import FloatingEdges from '../examples/FloatingEdges';
|
||||
import Hidden from '../examples/Hidden';
|
||||
import Interaction from '../examples/Interaction';
|
||||
import Intersection from '../examples/Intersection';
|
||||
import Layouting from '../examples/Layouting';
|
||||
import MultiFlows from '../examples/MultiFlows';
|
||||
import NestedNodes from '../examples/NestedNodes';
|
||||
import NodeResizer from '../examples/NodeResizer';
|
||||
import NodeTypeChange from '../examples/NodeTypeChange';
|
||||
import NodeTypesObjectChange from '../examples/NodeTypesObjectChange';
|
||||
import Overview from '../examples/Overview';
|
||||
import Provider from '../examples/Provider';
|
||||
import SaveRestore from '../examples/SaveRestore';
|
||||
import Stress from '../examples/Stress';
|
||||
import Subflow from '../examples/Subflow';
|
||||
import SwitchFlow from '../examples/Switch';
|
||||
import TouchDevice from '../examples/TouchDevice';
|
||||
import Undirectional from '../examples/Undirectional';
|
||||
import UpdatableEdge from '../examples/UpdatableEdge';
|
||||
import UpdateNode from '../examples/UpdateNode';
|
||||
import UseUpdateNodeInternals from '../examples/UseUpdateNodeInternals';
|
||||
import UseReactFlow from '../examples/UseReactFlow';
|
||||
import Validation from '../examples/Validation';
|
||||
import UseKeyPress from '../examples/UseKeyPress';
|
||||
import EdgeRouting from '../examples/EdgeRouting';
|
||||
import CancelConnection from '../examples/CancelConnection';
|
||||
import InteractiveMinimap from '../examples/InteractiveMinimap';
|
||||
import UseOnSelectionChange from '../examples/UseOnSelectionChange';
|
||||
import NodeToolbar from '../examples/NodeToolbar';
|
||||
import useNodesInitialized from '../examples/UseNodesInit';
|
||||
|
||||
interface IRoute {
|
||||
name: string;
|
||||
path: string;
|
||||
component: React.ComponentType;
|
||||
}
|
||||
|
||||
const routes: IRoute[] = [
|
||||
{
|
||||
name: 'Basic',
|
||||
path: '/',
|
||||
component: Basic,
|
||||
},
|
||||
{
|
||||
name: 'Backgrounds',
|
||||
path: '/backgrounds',
|
||||
component: Backgrounds,
|
||||
},
|
||||
{
|
||||
name: 'Cancel Connection',
|
||||
path: '/cancel-connection',
|
||||
component: CancelConnection,
|
||||
},
|
||||
{
|
||||
name: 'Controlled/Uncontrolled',
|
||||
path: '/controlled-uncontrolled',
|
||||
component: ControlledUncontrolled,
|
||||
},
|
||||
{
|
||||
name: 'Controlled Viewport',
|
||||
path: '/controlled-viewport',
|
||||
component: ControlledViewport,
|
||||
},
|
||||
{
|
||||
name: 'Custom Connection Line',
|
||||
path: '/custom-connectionline',
|
||||
component: CustomConnectionLine,
|
||||
},
|
||||
{
|
||||
name: 'Custom Minimap Node',
|
||||
path: '/custom-minimap-node',
|
||||
component: CustomMiniMapNode,
|
||||
},
|
||||
{
|
||||
name: 'Custom Node',
|
||||
path: '/custom-node',
|
||||
component: CustomNode,
|
||||
},
|
||||
{
|
||||
name: 'Default Nodes',
|
||||
path: '/default-nodes',
|
||||
component: DefaultNodes,
|
||||
},
|
||||
{
|
||||
name: 'Drag Handle',
|
||||
path: '/draghandle',
|
||||
component: DragHandle,
|
||||
},
|
||||
{
|
||||
name: 'Drag and Drop',
|
||||
path: '/dragndrop',
|
||||
component: DragNDrop,
|
||||
},
|
||||
{
|
||||
name: 'EasyConnect',
|
||||
path: '/easy-connect',
|
||||
component: EasyConnect,
|
||||
},
|
||||
{
|
||||
name: 'Edges',
|
||||
path: '/edges',
|
||||
component: Edges,
|
||||
},
|
||||
{
|
||||
name: 'Edge Renderer',
|
||||
path: '/edge-renderer',
|
||||
component: EdgeRenderer,
|
||||
},
|
||||
{
|
||||
name: 'Edge Types',
|
||||
path: '/edge-types',
|
||||
component: EdgeTypes,
|
||||
},
|
||||
{
|
||||
name: 'Edge Routing',
|
||||
path: '/edge-routing',
|
||||
component: EdgeRouting,
|
||||
},
|
||||
{
|
||||
name: 'Empty',
|
||||
path: '/empty',
|
||||
component: Empty,
|
||||
},
|
||||
{
|
||||
name: 'Figma',
|
||||
path: '/figma',
|
||||
component: Figma,
|
||||
},
|
||||
{
|
||||
name: 'Floating Edges',
|
||||
path: '/floating-edges',
|
||||
component: FloatingEdges,
|
||||
},
|
||||
{
|
||||
name: 'Hidden',
|
||||
path: '/hidden',
|
||||
component: Hidden,
|
||||
},
|
||||
{
|
||||
name: 'Interaction',
|
||||
path: '/interaction',
|
||||
component: Interaction,
|
||||
},
|
||||
{
|
||||
name: 'Intersection',
|
||||
path: '/intersection',
|
||||
component: Intersection,
|
||||
},
|
||||
{
|
||||
name: 'Interactive Minimap',
|
||||
path: '/interactive-minimap',
|
||||
component: InteractiveMinimap,
|
||||
},
|
||||
{
|
||||
name: 'Layouting',
|
||||
path: '/layouting',
|
||||
component: Layouting,
|
||||
},
|
||||
{
|
||||
name: 'Multi Flows',
|
||||
path: '/multiflows',
|
||||
component: MultiFlows,
|
||||
},
|
||||
{
|
||||
name: 'Nested Nodes',
|
||||
path: '/nested-nodes',
|
||||
component: NestedNodes,
|
||||
},
|
||||
{
|
||||
name: 'Node Type Change',
|
||||
path: '/nodetype-change',
|
||||
component: NodeTypeChange,
|
||||
},
|
||||
{
|
||||
name: 'nodeTypes Object Change',
|
||||
path: '/nodetypesobject-change',
|
||||
component: NodeTypesObjectChange,
|
||||
},
|
||||
{
|
||||
name: 'NodeToolbar',
|
||||
path: '/node-toolbar',
|
||||
component: NodeToolbar,
|
||||
},
|
||||
{
|
||||
name: 'NodeResizer',
|
||||
path: '/node-resizer',
|
||||
component: NodeResizer,
|
||||
},
|
||||
{
|
||||
name: 'Overview',
|
||||
path: '/overview',
|
||||
component: Overview,
|
||||
},
|
||||
{
|
||||
name: 'Provider',
|
||||
path: '/provider',
|
||||
component: Provider,
|
||||
},
|
||||
{
|
||||
name: 'Save/Restore',
|
||||
path: '/save-restore',
|
||||
component: SaveRestore,
|
||||
},
|
||||
{
|
||||
name: 'Stress',
|
||||
path: '/stress',
|
||||
component: Stress,
|
||||
},
|
||||
{
|
||||
name: 'Subflow',
|
||||
path: '/subflow',
|
||||
component: Subflow,
|
||||
},
|
||||
{
|
||||
name: 'Switch Flow',
|
||||
path: '/switch',
|
||||
component: SwitchFlow,
|
||||
},
|
||||
{
|
||||
name: 'Touch Device',
|
||||
path: '/touch-device',
|
||||
component: TouchDevice,
|
||||
},
|
||||
{
|
||||
name: 'Undirectional',
|
||||
path: '/undirectional',
|
||||
component: Undirectional,
|
||||
},
|
||||
{
|
||||
name: 'Updatable Edge',
|
||||
path: '/updatable-edge',
|
||||
component: UpdatableEdge,
|
||||
},
|
||||
{
|
||||
name: 'Update Node',
|
||||
path: '/update-node',
|
||||
component: UpdateNode,
|
||||
},
|
||||
{
|
||||
name: 'useNodesInitialized',
|
||||
path: '/use-nodes-initialized',
|
||||
component: useNodesInitialized,
|
||||
},
|
||||
{
|
||||
name: 'useOnSelectionChange',
|
||||
path: '/use-on-selection-change',
|
||||
component: UseOnSelectionChange,
|
||||
},
|
||||
{
|
||||
name: 'useReactFlow',
|
||||
path: '/usereactflow',
|
||||
component: UseReactFlow,
|
||||
},
|
||||
{
|
||||
name: 'useUpdateNodeInternals',
|
||||
path: '/useupdatenodeinternals',
|
||||
component: UseUpdateNodeInternals,
|
||||
},
|
||||
{
|
||||
name: 'Validation',
|
||||
path: '/validation',
|
||||
component: Validation,
|
||||
},
|
||||
{
|
||||
name: 'useKeyPress',
|
||||
path: '/use-key-press',
|
||||
component: UseKeyPress,
|
||||
},
|
||||
];
|
||||
|
||||
const Header = () => {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const [currentPath, setCurrentPath] = useState(location.pathname);
|
||||
|
||||
useEffect(() => {
|
||||
const name = routes.find((route) => route.path === currentPath)?.name;
|
||||
document.title = `React Flow Examples${name ? ' - ' + name : ''}`;
|
||||
navigate(currentPath);
|
||||
}, [currentPath]);
|
||||
|
||||
return (
|
||||
<header>
|
||||
<a className="logo" href="https://github.com/wbkd/react-flow">
|
||||
React Flow Dev
|
||||
</a>
|
||||
<select value={currentPath} onChange={(event) => setCurrentPath(event.target.value)}>
|
||||
{routes.map((route) => (
|
||||
<option value={route.path} key={route.path}>
|
||||
{route.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
import Header from './header';
|
||||
|
||||
export default () => (
|
||||
<BrowserRouter>
|
||||
<Header />
|
||||
<Routes>
|
||||
{routes.map((route) => (
|
||||
<Route path={route.path} key={route.path} element={<route.component />} />
|
||||
))}
|
||||
<Route path="/" element={<Navigate to="/examples" />} />
|
||||
<Route path="examples" element={<Header />}>
|
||||
<Route index element={<Basic />} />
|
||||
{routes.map((route) => (
|
||||
<Route path={route.path} key={route.path} element={<route.component />} />
|
||||
))}
|
||||
</Route>
|
||||
<Route path="tests/generic/*" element={<GenericTestRoutes />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
);
|
||||
|
||||
287
examples/react/src/App/routes.ts
Normal file
287
examples/react/src/App/routes.ts
Normal file
@@ -0,0 +1,287 @@
|
||||
import Basic from '../examples/Basic';
|
||||
import Backgrounds from '../examples/Backgrounds';
|
||||
import ControlledUncontrolled from '../examples/ControlledUncontrolled';
|
||||
import ControlledViewport from '../examples/ControlledViewport';
|
||||
import CustomConnectionLine from '../examples/CustomConnectionLine';
|
||||
import CustomMiniMapNode from '../examples/CustomMiniMapNode';
|
||||
import CustomNode from '../examples/CustomNode';
|
||||
import DefaultNodes from '../examples/DefaultNodes';
|
||||
import DragHandle from '../examples/DragHandle';
|
||||
import DragNDrop from '../examples/DragNDrop';
|
||||
import EasyConnect from '../examples/EasyConnect';
|
||||
import Edges from '../examples/Edges';
|
||||
import EdgeRenderer from '../examples/EdgeRenderer';
|
||||
import EdgeTypes from '../examples/EdgeTypes';
|
||||
import Empty from '../examples/Empty';
|
||||
import Figma from '../examples/Figma';
|
||||
import FloatingEdges from '../examples/FloatingEdges';
|
||||
import Hidden from '../examples/Hidden';
|
||||
import Interaction from '../examples/Interaction';
|
||||
import Intersection from '../examples/Intersection';
|
||||
import Layouting from '../examples/Layouting';
|
||||
import MultiFlows from '../examples/MultiFlows';
|
||||
import NestedNodes from '../examples/NestedNodes';
|
||||
import NodeResizer from '../examples/NodeResizer';
|
||||
import NodeTypeChange from '../examples/NodeTypeChange';
|
||||
import NodeTypesObjectChange from '../examples/NodeTypesObjectChange';
|
||||
import Overview from '../examples/Overview';
|
||||
import Provider from '../examples/Provider';
|
||||
import SaveRestore from '../examples/SaveRestore';
|
||||
import Stress from '../examples/Stress';
|
||||
import Subflow from '../examples/Subflow';
|
||||
import SwitchFlow from '../examples/Switch';
|
||||
import TouchDevice from '../examples/TouchDevice';
|
||||
import Undirectional from '../examples/Undirectional';
|
||||
import UpdatableEdge from '../examples/UpdatableEdge';
|
||||
import UpdateNode from '../examples/UpdateNode';
|
||||
import UseUpdateNodeInternals from '../examples/UseUpdateNodeInternals';
|
||||
import UseReactFlow from '../examples/UseReactFlow';
|
||||
import Validation from '../examples/Validation';
|
||||
import UseKeyPress from '../examples/UseKeyPress';
|
||||
import EdgeRouting from '../examples/EdgeRouting';
|
||||
import CancelConnection from '../examples/CancelConnection';
|
||||
import InteractiveMinimap from '../examples/InteractiveMinimap';
|
||||
import UseOnSelectionChange from '../examples/UseOnSelectionChange';
|
||||
import NodeToolbar from '../examples/NodeToolbar';
|
||||
import useNodesInitialized from '../examples/UseNodesInit';
|
||||
|
||||
export interface IRoute {
|
||||
name: string;
|
||||
path: string;
|
||||
component: React.ComponentType;
|
||||
}
|
||||
|
||||
const routes: IRoute[] = [
|
||||
{
|
||||
name: 'Basic',
|
||||
path: 'basic',
|
||||
component: Basic,
|
||||
},
|
||||
{
|
||||
name: 'Backgrounds',
|
||||
path: 'backgrounds',
|
||||
component: Backgrounds,
|
||||
},
|
||||
{
|
||||
name: 'Cancel Connection',
|
||||
path: 'cancel-connection',
|
||||
component: CancelConnection,
|
||||
},
|
||||
{
|
||||
name: 'Controlled/Uncontrolled',
|
||||
path: 'controlled-uncontrolled',
|
||||
component: ControlledUncontrolled,
|
||||
},
|
||||
{
|
||||
name: 'Controlled Viewport',
|
||||
path: 'controlled-viewport',
|
||||
component: ControlledViewport,
|
||||
},
|
||||
{
|
||||
name: 'Custom Connection Line',
|
||||
path: 'custom-connectionline',
|
||||
component: CustomConnectionLine,
|
||||
},
|
||||
{
|
||||
name: 'Custom Minimap Node',
|
||||
path: 'custom-minimap-node',
|
||||
component: CustomMiniMapNode,
|
||||
},
|
||||
{
|
||||
name: 'Custom Node',
|
||||
path: 'custom-node',
|
||||
component: CustomNode,
|
||||
},
|
||||
{
|
||||
name: 'Default Nodes',
|
||||
path: 'default-nodes',
|
||||
component: DefaultNodes,
|
||||
},
|
||||
{
|
||||
name: 'Drag Handle',
|
||||
path: 'draghandle',
|
||||
component: DragHandle,
|
||||
},
|
||||
{
|
||||
name: 'Drag and Drop',
|
||||
path: 'dragndrop',
|
||||
component: DragNDrop,
|
||||
},
|
||||
{
|
||||
name: 'EasyConnect',
|
||||
path: 'easy-connect',
|
||||
component: EasyConnect,
|
||||
},
|
||||
{
|
||||
name: 'Edges',
|
||||
path: 'edges',
|
||||
component: Edges,
|
||||
},
|
||||
{
|
||||
name: 'Edge Renderer',
|
||||
path: 'edge-renderer',
|
||||
component: EdgeRenderer,
|
||||
},
|
||||
{
|
||||
name: 'Edge Types',
|
||||
path: 'edge-types',
|
||||
component: EdgeTypes,
|
||||
},
|
||||
{
|
||||
name: 'Edge Routing',
|
||||
path: 'edge-routing',
|
||||
component: EdgeRouting,
|
||||
},
|
||||
{
|
||||
name: 'Empty',
|
||||
path: 'empty',
|
||||
component: Empty,
|
||||
},
|
||||
{
|
||||
name: 'Figma',
|
||||
path: 'figma',
|
||||
component: Figma,
|
||||
},
|
||||
{
|
||||
name: 'Floating Edges',
|
||||
path: 'floating-edges',
|
||||
component: FloatingEdges,
|
||||
},
|
||||
{
|
||||
name: 'Hidden',
|
||||
path: 'hidden',
|
||||
component: Hidden,
|
||||
},
|
||||
{
|
||||
name: 'Interaction',
|
||||
path: 'interaction',
|
||||
component: Interaction,
|
||||
},
|
||||
{
|
||||
name: 'Intersection',
|
||||
path: 'intersection',
|
||||
component: Intersection,
|
||||
},
|
||||
{
|
||||
name: 'Interactive Minimap',
|
||||
path: 'interactive-minimap',
|
||||
component: InteractiveMinimap,
|
||||
},
|
||||
{
|
||||
name: 'Layouting',
|
||||
path: 'layouting',
|
||||
component: Layouting,
|
||||
},
|
||||
{
|
||||
name: 'Multi Flows',
|
||||
path: 'multiflows',
|
||||
component: MultiFlows,
|
||||
},
|
||||
{
|
||||
name: 'Nested Nodes',
|
||||
path: 'nested-nodes',
|
||||
component: NestedNodes,
|
||||
},
|
||||
{
|
||||
name: 'Node Type Change',
|
||||
path: 'nodetype-change',
|
||||
component: NodeTypeChange,
|
||||
},
|
||||
{
|
||||
name: 'nodeTypes Object Change',
|
||||
path: 'nodetypesobject-change',
|
||||
component: NodeTypesObjectChange,
|
||||
},
|
||||
{
|
||||
name: 'NodeToolbar',
|
||||
path: 'node-toolbar',
|
||||
component: NodeToolbar,
|
||||
},
|
||||
{
|
||||
name: 'NodeResizer',
|
||||
path: 'node-resizer',
|
||||
component: NodeResizer,
|
||||
},
|
||||
{
|
||||
name: 'Overview',
|
||||
path: 'overview',
|
||||
component: Overview,
|
||||
},
|
||||
{
|
||||
name: 'Provider',
|
||||
path: 'provider',
|
||||
component: Provider,
|
||||
},
|
||||
{
|
||||
name: 'Save/Restore',
|
||||
path: 'save-restore',
|
||||
component: SaveRestore,
|
||||
},
|
||||
{
|
||||
name: 'Stress',
|
||||
path: 'stress',
|
||||
component: Stress,
|
||||
},
|
||||
{
|
||||
name: 'Subflow',
|
||||
path: 'subflow',
|
||||
component: Subflow,
|
||||
},
|
||||
{
|
||||
name: 'Switch Flow',
|
||||
path: 'switch',
|
||||
component: SwitchFlow,
|
||||
},
|
||||
{
|
||||
name: 'Touch Device',
|
||||
path: 'touch-device',
|
||||
component: TouchDevice,
|
||||
},
|
||||
{
|
||||
name: 'Undirectional',
|
||||
path: 'undirectional',
|
||||
component: Undirectional,
|
||||
},
|
||||
{
|
||||
name: 'Updatable Edge',
|
||||
path: 'updatable-edge',
|
||||
component: UpdatableEdge,
|
||||
},
|
||||
{
|
||||
name: 'Update Node',
|
||||
path: 'update-node',
|
||||
component: UpdateNode,
|
||||
},
|
||||
{
|
||||
name: 'useNodesInitialized',
|
||||
path: 'use-nodes-initialized',
|
||||
component: useNodesInitialized,
|
||||
},
|
||||
{
|
||||
name: 'useOnSelectionChange',
|
||||
path: 'use-on-selection-change',
|
||||
component: UseOnSelectionChange,
|
||||
},
|
||||
{
|
||||
name: 'useReactFlow',
|
||||
path: 'usereactflow',
|
||||
component: UseReactFlow,
|
||||
},
|
||||
{
|
||||
name: 'useUpdateNodeInternals',
|
||||
path: 'useupdatenodeinternals',
|
||||
component: UseUpdateNodeInternals,
|
||||
},
|
||||
{
|
||||
name: 'Validation',
|
||||
path: 'validation',
|
||||
component: Validation,
|
||||
},
|
||||
{
|
||||
name: 'useKeyPress',
|
||||
path: 'use-key-press',
|
||||
component: UseKeyPress,
|
||||
},
|
||||
];
|
||||
|
||||
export default routes;
|
||||
7
examples/react/src/app.d.ts
vendored
Normal file
7
examples/react/src/app.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
import { Edge, Node, ReactFlowProps } from '@xyflow/react';
|
||||
|
||||
declare global {
|
||||
interface FlowConfig {
|
||||
flowProps: Omit<ReactFlowProps, 'nodes' | 'edges'> & { nodes: Node[]; edges: Edge[] };
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,7 @@ const initialNodes: Node[] = [
|
||||
data: { label: 'Node 4' },
|
||||
position: { x: 400, y: 200 },
|
||||
className: 'light',
|
||||
connectable: false,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import {
|
||||
import {
|
||||
ReactFlow,
|
||||
addEdge,
|
||||
ReactFlowProvider,
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
useEdgesState,
|
||||
useOnSelectionChange,
|
||||
OnSelectionChangeParams,
|
||||
OnSelectionChangeFunc,
|
||||
} from '@xyflow/react';
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
@@ -64,7 +65,7 @@ const Flow = () => {
|
||||
};
|
||||
|
||||
const WrappedFlow = () => {
|
||||
const [secondLoggerActive, setSecondLoggerActive] = useState<boolean>(false);
|
||||
const [secondLoggerActive, setSecondLoggerActive] = useState<boolean>(true);
|
||||
|
||||
const toggleSecondLogger = () => {
|
||||
setSecondLoggerActive(!secondLoggerActive);
|
||||
|
||||
30
examples/react/src/generic-tests/Flow.tsx
Normal file
30
examples/react/src/generic-tests/Flow.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import {
|
||||
ReactFlow,
|
||||
applyNodeChanges,
|
||||
applyEdgeChanges,
|
||||
addEdge,
|
||||
OnNodesChange,
|
||||
OnEdgesChange,
|
||||
OnConnect,
|
||||
} from '@xyflow/react';
|
||||
|
||||
type FlowProps = {
|
||||
flowConfig: FlowConfig;
|
||||
};
|
||||
|
||||
export default ({ flowConfig }: FlowProps) => {
|
||||
const [nodes, setNodes] = useState(flowConfig.flowProps.nodes);
|
||||
const [edges, setEdges] = useState(flowConfig.flowProps.edges);
|
||||
const props = { ...flowConfig.flowProps, nodes, edges };
|
||||
|
||||
const onNodesChange: OnNodesChange = useCallback((changes) => setNodes((nds) => applyNodeChanges(changes, nds)), []);
|
||||
const onEdgesChange: OnEdgesChange = useCallback((changes) => setEdges((eds) => applyEdgeChanges(changes, eds)), []);
|
||||
const onConnect: OnConnect = useCallback((params) => setEdges((eds) => addEdge(params, eds)), []);
|
||||
|
||||
return (
|
||||
<div style={{ height: '100%' }}>
|
||||
<ReactFlow {...props} onNodesChange={onNodesChange} onEdgesChange={onEdgesChange} onConnect={onConnect} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
163
examples/react/src/generic-tests/edges/general.ts
Normal file
163
examples/react/src/generic-tests/edges/general.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
import { MarkerType } from '@xyflow/react';
|
||||
|
||||
export default {
|
||||
flowProps: {
|
||||
fitView: true,
|
||||
nodes: [
|
||||
{
|
||||
id: '1',
|
||||
data: { label: '1' },
|
||||
position: { x: 0, y: 0 },
|
||||
type: 'input',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
data: { label: '2' },
|
||||
position: { x: -100, y: 100 },
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
data: { label: '3' },
|
||||
position: { x: 100, y: 100 },
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
data: { label: '4' },
|
||||
position: { x: -100, y: 200 },
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
data: { label: '5' },
|
||||
position: { x: 100, y: 200 },
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
data: { label: '6' },
|
||||
position: { x: -100, y: 300 },
|
||||
},
|
||||
{
|
||||
id: '7',
|
||||
data: { label: '7' },
|
||||
position: { x: 100, y: 300 },
|
||||
},
|
||||
{
|
||||
id: '8',
|
||||
data: { label: '8' },
|
||||
position: { x: -100, y: 400 },
|
||||
},
|
||||
{
|
||||
id: '9',
|
||||
data: { label: '9' },
|
||||
position: { x: 100, y: 400 },
|
||||
},
|
||||
{
|
||||
id: '10',
|
||||
data: { label: '10' },
|
||||
position: { x: -100, y: 500 },
|
||||
},
|
||||
{
|
||||
id: '11',
|
||||
data: { label: '11' },
|
||||
position: { x: 100, y: 500 },
|
||||
},
|
||||
// {
|
||||
// id: '12',
|
||||
// data: { label: '12' },
|
||||
// position: { x: -100, y: 600 }
|
||||
// },
|
||||
// {
|
||||
// id: '13',
|
||||
// data: { label: '13' },
|
||||
// position: { x: 100, y: 600 }
|
||||
// }
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
id: 'edge-with-class',
|
||||
source: '1',
|
||||
target: '2',
|
||||
className: 'edge-class-test',
|
||||
},
|
||||
{
|
||||
id: 'edge-with-style',
|
||||
source: '1',
|
||||
target: '3',
|
||||
style: {
|
||||
stroke: 'red',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'hidden-edge',
|
||||
source: '2',
|
||||
target: '4',
|
||||
label: 'hidden',
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
id: 'animated-edge',
|
||||
source: '3',
|
||||
target: '5',
|
||||
label: 'animated',
|
||||
animated: true,
|
||||
},
|
||||
{
|
||||
id: 'not-selectable-edge',
|
||||
source: '4',
|
||||
target: '6',
|
||||
label: 'not-selectable',
|
||||
selectable: false,
|
||||
},
|
||||
{
|
||||
id: 'not-deletable',
|
||||
source: '5',
|
||||
target: '7',
|
||||
label: 'not-deletable',
|
||||
deletable: false,
|
||||
},
|
||||
{
|
||||
id: 'z-index',
|
||||
source: '6',
|
||||
target: '8',
|
||||
label: 'z-index',
|
||||
zIndex: 3141592,
|
||||
},
|
||||
{
|
||||
id: 'aria-label',
|
||||
source: '7',
|
||||
target: '9',
|
||||
label: 'aria-label',
|
||||
ariaLabel: 'aria-label-test',
|
||||
},
|
||||
{
|
||||
id: 'interaction-width',
|
||||
source: '8',
|
||||
target: '10',
|
||||
label: 'interaction-width',
|
||||
interactionWidth: 42,
|
||||
},
|
||||
{
|
||||
id: 'markers',
|
||||
source: '9',
|
||||
target: '11',
|
||||
label: 'markers',
|
||||
markerEnd: { type: MarkerType.Arrow },
|
||||
markerStart: { type: MarkerType.ArrowClosed },
|
||||
},
|
||||
// {
|
||||
// id: 'updatable',
|
||||
// source: '9',
|
||||
// target: '11',
|
||||
// label: 'focusable',
|
||||
// updatable: true
|
||||
// },
|
||||
// {
|
||||
// id: 'not-focusable',
|
||||
//
|
||||
// source: '5',
|
||||
// target: '7',
|
||||
// label: 'not-focusable',
|
||||
// focusable: false
|
||||
// },
|
||||
],
|
||||
},
|
||||
} satisfies FlowConfig;
|
||||
17
examples/react/src/generic-tests/index.tsx
Normal file
17
examples/react/src/generic-tests/index.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import Flow from './Flow';
|
||||
|
||||
const flowConfigs = import.meta.glob<FlowConfig>('./**/*.ts', { eager: true, import: 'default' });
|
||||
|
||||
export default () => {
|
||||
const location = useLocation();
|
||||
const path = `.${location.pathname.replace('/tests/generic', '')}.ts`;
|
||||
const flowConfig = flowConfigs[path];
|
||||
|
||||
if (!flowConfig) {
|
||||
return `404: This route doesn't exists.`;
|
||||
}
|
||||
|
||||
return <Flow flowConfig={flowConfig} />;
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
export default () => {
|
||||
return (
|
||||
<div
|
||||
className="container"
|
||||
style={{
|
||||
width: '100px',
|
||||
height: '50px',
|
||||
background: 'red',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="drag-handle custom-drag-handle"
|
||||
style={{
|
||||
display: 'inline-block',
|
||||
width: '25px',
|
||||
height: '25px',
|
||||
backgroundColor: 'green',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
91
examples/react/src/generic-tests/nodes/general.ts
Normal file
91
examples/react/src/generic-tests/nodes/general.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import DragHandleNode from './components/DragHandleNode';
|
||||
|
||||
export default {
|
||||
flowProps: {
|
||||
fitView: true,
|
||||
nodeTypes: {
|
||||
DragHandleNode,
|
||||
},
|
||||
nodes: [
|
||||
{
|
||||
id: 'Node-1',
|
||||
data: { label: 'Node-1' },
|
||||
position: { x: 0, y: 0 },
|
||||
type: 'input',
|
||||
className: 'playwright-test-class-123',
|
||||
style: { backgroundColor: 'red' },
|
||||
},
|
||||
{
|
||||
id: 'Node-2',
|
||||
type: 'output',
|
||||
data: { label: 'Node-2' },
|
||||
position: { x: -100, y: 100 },
|
||||
},
|
||||
{
|
||||
id: 'Node-3',
|
||||
data: { label: 'Node-3' },
|
||||
position: { x: 100, y: 100 },
|
||||
},
|
||||
{
|
||||
id: 'Node-4',
|
||||
data: { label: 'Node-4' },
|
||||
position: { x: 0, y: 200 },
|
||||
type: 'output',
|
||||
},
|
||||
{
|
||||
id: 'drag-handle',
|
||||
data: { label: 'Drag Handle' },
|
||||
position: { x: 200, y: 0 },
|
||||
type: 'DragHandleNode',
|
||||
dragHandle: '.custom-drag-handle',
|
||||
},
|
||||
{
|
||||
id: 'notConnectable',
|
||||
type: 'output',
|
||||
data: { label: 'notConnectable' },
|
||||
position: { x: 0, y: 300 },
|
||||
connectable: false,
|
||||
},
|
||||
{
|
||||
id: 'notDraggable',
|
||||
data: { label: 'notDraggable' },
|
||||
position: { x: 0, y: 400 },
|
||||
draggable: false,
|
||||
},
|
||||
{
|
||||
id: 'notSelectable',
|
||||
data: { label: 'notSelectable' },
|
||||
position: { x: 0, y: 500 },
|
||||
selectable: false,
|
||||
},
|
||||
{
|
||||
id: 'notDeletable',
|
||||
data: { label: 'notDeletable' },
|
||||
position: { x: 0, y: 600 },
|
||||
deletable: false,
|
||||
},
|
||||
{
|
||||
id: 'hidden',
|
||||
data: { label: 'hidden' },
|
||||
position: { x: 0, y: 700 },
|
||||
hidden: true,
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
id: '1-2',
|
||||
type: 'default',
|
||||
source: 'Node-1',
|
||||
target: 'Node-2',
|
||||
label: 'edge',
|
||||
},
|
||||
{
|
||||
id: '1-3',
|
||||
type: 'default',
|
||||
source: 'Node-1',
|
||||
target: 'Node-3',
|
||||
label: 'edge',
|
||||
},
|
||||
],
|
||||
},
|
||||
} satisfies FlowConfig;
|
||||
37
examples/react/src/generic-tests/pane/general.ts
Normal file
37
examples/react/src/generic-tests/pane/general.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
export default {
|
||||
flowProps: {
|
||||
minZoom: 0.25,
|
||||
maxZoom: 4,
|
||||
fitView: true,
|
||||
nodes: [
|
||||
{
|
||||
id: '1',
|
||||
data: { label: '1' },
|
||||
position: { x: 0, y: 0 },
|
||||
type: 'input',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
data: { label: '2' },
|
||||
position: { x: -100, y: 100 },
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
data: { label: '3' },
|
||||
position: { x: 100, y: 100 },
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
id: 'first-edge',
|
||||
source: '1',
|
||||
target: '2',
|
||||
},
|
||||
{
|
||||
id: 'second-edge',
|
||||
source: '1',
|
||||
target: '3',
|
||||
},
|
||||
],
|
||||
},
|
||||
} satisfies FlowConfig;
|
||||
38
examples/react/src/generic-tests/pane/non-defaults.ts
Normal file
38
examples/react/src/generic-tests/pane/non-defaults.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
export default {
|
||||
flowProps: {
|
||||
panOnScroll: true,
|
||||
defaultViewport: { x: 1.23, y: 9.87, zoom: 1.234 },
|
||||
autoPanOnConnect: false,
|
||||
autoPanOnNodeDrag: false,
|
||||
nodes: [
|
||||
{
|
||||
id: '1',
|
||||
data: { label: '1' },
|
||||
position: { x: 0, y: 0 },
|
||||
type: 'input',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
data: { label: '2' },
|
||||
position: { x: -100, y: 100 },
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
data: { label: '3' },
|
||||
position: { x: 100, y: 100 },
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
id: 'first-edge',
|
||||
source: '1',
|
||||
target: '2',
|
||||
},
|
||||
{
|
||||
id: 'second-edge',
|
||||
source: '1',
|
||||
target: '3',
|
||||
},
|
||||
],
|
||||
},
|
||||
} satisfies FlowConfig;
|
||||
@@ -1,9 +1,10 @@
|
||||
import { StrictMode } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
import App from './App';
|
||||
|
||||
import './index.css';
|
||||
import '@xyflow/react/dist/style.css';
|
||||
import './index.css';
|
||||
|
||||
createRoot(document.getElementById('root') as HTMLElement).render(
|
||||
<StrictMode>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "svelte",
|
||||
"name": "svelte-examples",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -12,20 +12,20 @@
|
||||
"format": "prettier --plugin-search-dir . --write ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^2.0.0",
|
||||
"@sveltejs/kit": "^1.20.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.45.0",
|
||||
"@typescript-eslint/parser": "^5.45.0",
|
||||
"eslint": "^8.28.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-svelte": "^2.30.0",
|
||||
"prettier": "^2.8.0",
|
||||
"prettier-plugin-svelte": "^2.10.1",
|
||||
"svelte": "^4.0.5",
|
||||
"svelte-check": "^3.4.3",
|
||||
"tslib": "^2.4.1",
|
||||
"typescript": "^5.0.0",
|
||||
"vite": "^4.4.2"
|
||||
"@sveltejs/adapter-auto": "^2.1.1",
|
||||
"@sveltejs/kit": "^1.27.3",
|
||||
"@typescript-eslint/eslint-plugin": "^6.10.0",
|
||||
"@typescript-eslint/parser": "^6.10.0",
|
||||
"eslint": "^8.53.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"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",
|
||||
"tslib": "^2.6.2",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^4.5.0"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
7
examples/svelte/src/app.d.ts
vendored
7
examples/svelte/src/app.d.ts
vendored
@@ -1,4 +1,7 @@
|
||||
// See https://kit.svelte.dev/docs/types#app
|
||||
|
||||
import type { Edge, Node, SvelteFlowProps } from '@xyflow/svelte';
|
||||
|
||||
// for information about these interfaces
|
||||
declare global {
|
||||
namespace App {
|
||||
@@ -7,6 +10,10 @@ declare global {
|
||||
// interface PageData {}
|
||||
// interface Platform {}
|
||||
}
|
||||
|
||||
interface FlowConfig {
|
||||
flowProps: Omit<SvelteFlowProps, 'nodes' | 'edges'> & { nodes: Node[]; edges: Edge[] };
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<style>
|
||||
html,
|
||||
body,
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<div class="logo">Svelte Flow</div>
|
||||
<select on:change={onChange} value={$page.route.id}>
|
||||
{#each routes as route}
|
||||
<option value={`/${route}`}>{route}</option>
|
||||
<option value={`/examples/${route}`}>{route}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</header>
|
||||
|
||||
161
examples/svelte/src/generic-tests/edges/general.ts
Normal file
161
examples/svelte/src/generic-tests/edges/general.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
import { MarkerType } from '@xyflow/svelte';
|
||||
|
||||
export default {
|
||||
flowProps: {
|
||||
fitView: true,
|
||||
nodes: [
|
||||
{
|
||||
id: '1',
|
||||
data: { label: '1' },
|
||||
position: { x: 0, y: 0 },
|
||||
type: 'input'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
data: { label: '2' },
|
||||
position: { x: -100, y: 100 }
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
data: { label: '3' },
|
||||
position: { x: 100, y: 100 }
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
data: { label: '4' },
|
||||
position: { x: -100, y: 200 }
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
data: { label: '5' },
|
||||
position: { x: 100, y: 200 }
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
data: { label: '6' },
|
||||
position: { x: -100, y: 300 }
|
||||
},
|
||||
{
|
||||
id: '7',
|
||||
data: { label: '7' },
|
||||
position: { x: 100, y: 300 }
|
||||
},
|
||||
{
|
||||
id: '8',
|
||||
data: { label: '8' },
|
||||
position: { x: -100, y: 400 }
|
||||
},
|
||||
{
|
||||
id: '9',
|
||||
data: { label: '9' },
|
||||
position: { x: 100, y: 400 }
|
||||
},
|
||||
{
|
||||
id: '10',
|
||||
data: { label: '10' },
|
||||
position: { x: -100, y: 500 }
|
||||
},
|
||||
{
|
||||
id: '11',
|
||||
data: { label: '11' },
|
||||
position: { x: 100, y: 500 }
|
||||
}
|
||||
// {
|
||||
// id: '12',
|
||||
// data: { label: '12' },
|
||||
// position: { x: -100, y: 600 }
|
||||
// },
|
||||
// {
|
||||
// id: '13',
|
||||
// data: { label: '13' },
|
||||
// position: { x: 100, y: 600 }
|
||||
// }
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
id: 'edge-with-class',
|
||||
source: '1',
|
||||
target: '2',
|
||||
class: 'edge-class-test'
|
||||
},
|
||||
{
|
||||
id: 'edge-with-style',
|
||||
source: '1',
|
||||
target: '3',
|
||||
style: 'stroke: red;'
|
||||
},
|
||||
{
|
||||
id: 'hidden-edge',
|
||||
source: '2',
|
||||
target: '4',
|
||||
label: 'hidden',
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
id: 'animated-edge',
|
||||
source: '3',
|
||||
target: '5',
|
||||
label: 'animated',
|
||||
animated: true
|
||||
},
|
||||
{
|
||||
id: 'not-selectable-edge',
|
||||
source: '4',
|
||||
target: '6',
|
||||
label: 'not-selectable',
|
||||
selectable: false
|
||||
},
|
||||
{
|
||||
id: 'not-deletable',
|
||||
source: '5',
|
||||
target: '7',
|
||||
label: 'not-deletable',
|
||||
deletable: false
|
||||
},
|
||||
{
|
||||
id: 'z-index',
|
||||
source: '6',
|
||||
target: '8',
|
||||
label: 'z-index',
|
||||
zIndex: 3141592
|
||||
},
|
||||
{
|
||||
id: 'aria-label',
|
||||
source: '7',
|
||||
target: '9',
|
||||
label: 'aria-label',
|
||||
ariaLabel: 'aria-label-test'
|
||||
},
|
||||
{
|
||||
id: 'interaction-width',
|
||||
source: '8',
|
||||
target: '10',
|
||||
label: 'interaction-width',
|
||||
interactionWidth: 42
|
||||
},
|
||||
{
|
||||
id: 'markers',
|
||||
source: '9',
|
||||
target: '11',
|
||||
label: 'markers',
|
||||
markerEnd: { type: MarkerType.Arrow },
|
||||
markerStart: { type: MarkerType.ArrowClosed }
|
||||
}
|
||||
// {
|
||||
// id: 'updatable',
|
||||
// source: '9',
|
||||
// target: '11',
|
||||
// label: 'focusable',
|
||||
// updatable: true
|
||||
// },
|
||||
// {
|
||||
// id: 'not-focusable',
|
||||
//
|
||||
// source: '5',
|
||||
// target: '7',
|
||||
// label: 'not-focusable',
|
||||
// focusable: false
|
||||
// },
|
||||
]
|
||||
}
|
||||
} satisfies FlowConfig;
|
||||
@@ -0,0 +1,26 @@
|
||||
<script lang="ts">
|
||||
import type { NodeProps } from '@xyflow/svelte';
|
||||
|
||||
type $$Props = NodeProps;
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
<div class="drag-handle custom-drag-handle" />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
width: 100px;
|
||||
height: 50px;
|
||||
background: red;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.drag-handle {
|
||||
display: inline-block;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
background-color: green;
|
||||
}
|
||||
</style>
|
||||
91
examples/svelte/src/generic-tests/nodes/general.ts
Normal file
91
examples/svelte/src/generic-tests/nodes/general.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import DragHandleNode from './components/DragHandleNode.svelte';
|
||||
|
||||
export default {
|
||||
flowProps: {
|
||||
fitView: true,
|
||||
nodeTypes: {
|
||||
DragHandleNode
|
||||
},
|
||||
nodes: [
|
||||
{
|
||||
id: 'Node-1',
|
||||
data: { label: 'Node-1' },
|
||||
position: { x: 0, y: 0 },
|
||||
type: 'input',
|
||||
class: 'playwright-test-class-123',
|
||||
style: 'background-color: red;'
|
||||
},
|
||||
{
|
||||
id: 'Node-2',
|
||||
type: 'output',
|
||||
data: { label: 'Node-2' },
|
||||
position: { x: -100, y: 100 }
|
||||
},
|
||||
{
|
||||
id: 'Node-3',
|
||||
data: { label: 'Node-3' },
|
||||
position: { x: 100, y: 100 }
|
||||
},
|
||||
{
|
||||
id: 'Node-4',
|
||||
data: { label: 'Node-4' },
|
||||
position: { x: 0, y: 200 },
|
||||
type: 'output'
|
||||
},
|
||||
{
|
||||
id: 'drag-handle',
|
||||
data: { label: 'Drag Handle' },
|
||||
position: { x: 200, y: 0 },
|
||||
type: 'DragHandleNode',
|
||||
dragHandle: '.custom-drag-handle'
|
||||
},
|
||||
{
|
||||
id: 'notConnectable',
|
||||
type: 'output',
|
||||
data: { label: 'notConnectable' },
|
||||
position: { x: 0, y: 300 },
|
||||
connectable: false
|
||||
},
|
||||
{
|
||||
id: 'notDraggable',
|
||||
data: { label: 'notDraggable' },
|
||||
position: { x: 0, y: 400 },
|
||||
draggable: false
|
||||
},
|
||||
{
|
||||
id: 'notSelectable',
|
||||
data: { label: 'notSelectable' },
|
||||
position: { x: 0, y: 500 },
|
||||
selectable: false
|
||||
},
|
||||
{
|
||||
id: 'notDeletable',
|
||||
data: { label: 'notDeletable' },
|
||||
position: { x: 0, y: 600 },
|
||||
deletable: false
|
||||
},
|
||||
{
|
||||
id: 'hidden',
|
||||
data: { label: 'hidden' },
|
||||
position: { x: 0, y: 700 },
|
||||
hidden: true
|
||||
}
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
id: '1-2',
|
||||
type: 'default',
|
||||
source: 'Node-1',
|
||||
target: 'Node-2',
|
||||
label: 'edge'
|
||||
},
|
||||
{
|
||||
id: '1-3',
|
||||
type: 'default',
|
||||
source: 'Node-1',
|
||||
target: 'Node-3',
|
||||
label: 'edge'
|
||||
}
|
||||
]
|
||||
}
|
||||
} satisfies FlowConfig;
|
||||
40
examples/svelte/src/generic-tests/pane/activation-keys.ts
Normal file
40
examples/svelte/src/generic-tests/pane/activation-keys.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
export default {
|
||||
flowProps: {
|
||||
zoomOnScroll: false,
|
||||
// zoomActivationKey: 'Space',
|
||||
// panOnDrag: false,
|
||||
// panOnScroll: false,
|
||||
panActivationKey: 'Space',
|
||||
fitView: true,
|
||||
nodes: [
|
||||
{
|
||||
id: '1',
|
||||
data: { label: '1' },
|
||||
position: { x: 0, y: 0 },
|
||||
type: 'input'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
data: { label: '2' },
|
||||
position: { x: -100, y: 100 }
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
data: { label: '3' },
|
||||
position: { x: 100, y: 100 }
|
||||
}
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
id: 'first-edge',
|
||||
source: '1',
|
||||
target: '2'
|
||||
},
|
||||
{
|
||||
id: 'second-edge',
|
||||
source: '1',
|
||||
target: '3'
|
||||
}
|
||||
]
|
||||
}
|
||||
} satisfies FlowConfig;
|
||||
37
examples/svelte/src/generic-tests/pane/general.ts
Normal file
37
examples/svelte/src/generic-tests/pane/general.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
export default {
|
||||
flowProps: {
|
||||
minZoom: 0.25,
|
||||
maxZoom: 4,
|
||||
fitView: true,
|
||||
nodes: [
|
||||
{
|
||||
id: '1',
|
||||
data: { label: '1' },
|
||||
position: { x: 0, y: 0 },
|
||||
type: 'input'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
data: { label: '2' },
|
||||
position: { x: -100, y: 100 }
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
data: { label: '3' },
|
||||
position: { x: 100, y: 100 }
|
||||
}
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
id: 'first-edge',
|
||||
source: '1',
|
||||
target: '2'
|
||||
},
|
||||
{
|
||||
id: 'second-edge',
|
||||
source: '1',
|
||||
target: '3'
|
||||
}
|
||||
]
|
||||
}
|
||||
} satisfies FlowConfig;
|
||||
38
examples/svelte/src/generic-tests/pane/non-defaults.ts
Normal file
38
examples/svelte/src/generic-tests/pane/non-defaults.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
export default {
|
||||
flowProps: {
|
||||
panOnScroll: true,
|
||||
initialViewport: { x: 1.23, y: 9.87, zoom: 1.234 },
|
||||
autoPanOnConnect: false,
|
||||
autoPanOnNodeDrag: false,
|
||||
nodes: [
|
||||
{
|
||||
id: '1',
|
||||
data: { label: '1' },
|
||||
position: { x: 0, y: 0 },
|
||||
type: 'input'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
data: { label: '2' },
|
||||
position: { x: -100, y: 100 }
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
data: { label: '3' },
|
||||
position: { x: 100, y: 100 }
|
||||
}
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
id: 'first-edge',
|
||||
source: '1',
|
||||
target: '2'
|
||||
},
|
||||
{
|
||||
id: 'second-edge',
|
||||
source: '1',
|
||||
target: '3'
|
||||
}
|
||||
]
|
||||
}
|
||||
} satisfies FlowConfig;
|
||||
@@ -2,7 +2,7 @@ import { redirect } from '@sveltejs/kit';
|
||||
|
||||
/** @type {import('./$types').LayoutServerLoad} */
|
||||
export function load({ route }) {
|
||||
if (route.id === '/') {
|
||||
throw redirect(307, '/overview');
|
||||
}
|
||||
if (route.id === '/') {
|
||||
throw redirect(307, '/examples/overview');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { Header } from '../components/Header';
|
||||
import { Header } from '$components/Header';
|
||||
</script>
|
||||
|
||||
<div class="app">
|
||||
1
examples/svelte/src/routes/examples/+page.svelte
Normal file
1
examples/svelte/src/routes/examples/+page.svelte
Normal file
@@ -0,0 +1 @@
|
||||
<div>this redirects to /overview</div>
|
||||
@@ -83,6 +83,12 @@
|
||||
type: 'custom',
|
||||
data: { label: 'Custom Node' },
|
||||
position: { x: 150, y: 300 }
|
||||
},
|
||||
{
|
||||
id: 'hideunhide',
|
||||
data: { label: 'HIDE ME' },
|
||||
position: { x: 300, y: 75 },
|
||||
hidden: true
|
||||
}
|
||||
]);
|
||||
|
||||
@@ -179,6 +185,13 @@
|
||||
<Panel position="top-right">
|
||||
<button on:click={updateNode}>update node pos</button>
|
||||
<button on:click={updateEdge}>update edge type</button>
|
||||
<button
|
||||
on:click={() => {
|
||||
console.log($nodes, $nodes.length);
|
||||
$nodes[$nodes.length - 1].hidden = !$nodes[$nodes.length - 1].hidden;
|
||||
$nodes = $nodes;
|
||||
}}>hide/unhide</button
|
||||
>
|
||||
</Panel>
|
||||
</SvelteFlow>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { SvelteFlowProvider } from '@xyflow/svelte';
|
||||
|
||||
import Flow from './Flow.svelte';
|
||||
|
||||
export let data: { flowConfig: FlowConfig };
|
||||
</script>
|
||||
|
||||
<SvelteFlowProvider>
|
||||
<Flow flowConfig={data.flowConfig} />
|
||||
</SvelteFlowProvider>
|
||||
@@ -0,0 +1,19 @@
|
||||
import { error } from '@sveltejs/kit';
|
||||
|
||||
const flowConfigs = import.meta.glob<FlowConfig>('/src/generic-tests/**/*.ts', {
|
||||
eager: true,
|
||||
import: 'default'
|
||||
});
|
||||
|
||||
/** @type {import('./$types').PageLoad} */
|
||||
export function load({ params }) {
|
||||
const flowConfig = flowConfigs[`/src/generic-tests/${params.topic}/${params.example}.ts`];
|
||||
|
||||
if (!flowConfig) {
|
||||
throw error(404, 'Not found');
|
||||
}
|
||||
|
||||
return {
|
||||
flowConfig
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { writable } from 'svelte/store';
|
||||
import { SvelteFlow } from '@xyflow/svelte';
|
||||
|
||||
import '@xyflow/svelte/dist/style.css';
|
||||
|
||||
export let flowConfig: FlowConfig;
|
||||
|
||||
// Create writables here so it is easier to create test cases
|
||||
const nodes = writable(flowConfig.flowProps.nodes);
|
||||
const edges = writable(flowConfig.flowProps.edges);
|
||||
|
||||
const props = { ...flowConfig.flowProps, nodes, edges };
|
||||
</script>
|
||||
|
||||
<SvelteFlow {...props} />
|
||||
BIN
examples/svelte/static/favicon.ico
Normal file
BIN
examples/svelte/static/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 111 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -11,7 +11,10 @@ const config = {
|
||||
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
|
||||
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
|
||||
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
|
||||
adapter: adapter()
|
||||
adapter: adapter(),
|
||||
alias: {
|
||||
$components: './src/components'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
"dev": "turbo run dev --parallel --concurrency 12",
|
||||
"dev:svelte": "turbo run dev --filter=svelte --filter=system",
|
||||
"dev:react": "turbo run dev --filter=react",
|
||||
"test:svelte": "pnpm --filter=playwright run test:svelte",
|
||||
"test:svelte:ui": "pnpm --filter=playwright run test:svelte:ui",
|
||||
"test:react": "pnpm --filter=playwright run test:react",
|
||||
"test:react:ui": "pnpm --filter=playwright run test:react:ui",
|
||||
"build": "turbo run build",
|
||||
"test": "turbo run test",
|
||||
"lint": "turbo run lint",
|
||||
|
||||
@@ -213,6 +213,7 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
|
||||
onKeyDown={isFocusable ? onKeyDown : undefined}
|
||||
tabIndex={isFocusable ? 0 : undefined}
|
||||
role={isFocusable ? 'button' : 'img'}
|
||||
data-id={id}
|
||||
data-testid={`rf__edge-${id}`}
|
||||
aria-label={ariaLabel === null ? undefined : ariaLabel ? ariaLabel : `Edge from ${source} to ${target}`}
|
||||
aria-describedby={isFocusable ? `${ARIA_EDGE_DESC_KEY}-${rfId}` : undefined}
|
||||
|
||||
@@ -32,8 +32,9 @@ const SelectionListener = memo(({ onSelectionChange }: SelectionListenerProps) =
|
||||
|
||||
useEffect(() => {
|
||||
const params = { nodes: selectedNodes, edges: selectedEdges };
|
||||
|
||||
onSelectionChange?.(params);
|
||||
store.getState().onSelectionChange.forEach((fn) => fn(params));
|
||||
store.getState().onSelectionChangeHandlers.forEach((fn) => fn(params));
|
||||
}, [selectedNodes, selectedEdges, onSelectionChange]);
|
||||
|
||||
return null;
|
||||
@@ -41,12 +42,12 @@ const SelectionListener = memo(({ onSelectionChange }: SelectionListenerProps) =
|
||||
|
||||
SelectionListener.displayName = 'SelectionListener';
|
||||
|
||||
const changeSelector = (s: ReactFlowState) => !!s.onSelectionChange;
|
||||
const changeSelector = (s: ReactFlowState) => !!s.onSelectionChangeHandlers;
|
||||
|
||||
function Wrapper({ onSelectionChange }: SelectionListenerProps) {
|
||||
const storeHasSelectionChange = useStore(changeSelector);
|
||||
const storeHasSelectionChangeHandlers = useStore(changeSelector);
|
||||
|
||||
if (onSelectionChange || storeHasSelectionChange) {
|
||||
if (onSelectionChange || storeHasSelectionChangeHandlers) {
|
||||
return <SelectionListener onSelectionChange={onSelectionChange} />;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,11 +11,12 @@ function useOnSelectionChange({ onChange }: UseOnSelectionChangeOptions) {
|
||||
const store = useStoreApi();
|
||||
|
||||
useEffect(() => {
|
||||
const addedOnSelectionChange = [...store.getState().onSelectionChange, onChange];
|
||||
store.setState({ onSelectionChange: addedOnSelectionChange });
|
||||
const nextOnSelectionChangeHandlers = [...store.getState().onSelectionChangeHandlers, onChange];
|
||||
store.setState({ onSelectionChangeHandlers: nextOnSelectionChangeHandlers });
|
||||
|
||||
return () => {
|
||||
const removedOnSelectionChange = store.getState().onSelectionChange.filter((fn) => fn !== onChange);
|
||||
store.setState({ onSelectionChange: removedOnSelectionChange });
|
||||
const nextHandlers = store.getState().onSelectionChangeHandlers.filter((fn) => fn !== onChange);
|
||||
store.setState({ onSelectionChangeHandlers: nextHandlers });
|
||||
};
|
||||
}, [onChange]);
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ const getInitialState = ({
|
||||
connectionRadius: 20,
|
||||
onError: () => null,
|
||||
isValidConnection: undefined,
|
||||
onSelectionChange: [],
|
||||
onSelectionChangeHandlers: [],
|
||||
|
||||
lib: 'react',
|
||||
};
|
||||
|
||||
@@ -123,8 +123,8 @@ export type ReactFlowStore = {
|
||||
onViewportChangeStart?: OnViewportChange;
|
||||
onViewportChange?: OnViewportChange;
|
||||
onViewportChangeEnd?: OnViewportChange;
|
||||
|
||||
onSelectionChange: OnSelectionChangeFunc[];
|
||||
|
||||
onSelectionChangeHandlers: OnSelectionChangeFunc[];
|
||||
|
||||
ariaLiveMessage: string;
|
||||
autoPanOnConnect: boolean;
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
|
||||
<path
|
||||
d={path}
|
||||
{id}
|
||||
class={cc(['svelte-flow__edge-path', className])}
|
||||
marker-start={markerStart}
|
||||
marker-end={markerEnd}
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
<svelte:options immutable />
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
createEventDispatcher,
|
||||
onMount,
|
||||
setContext,
|
||||
SvelteComponent,
|
||||
type ComponentType
|
||||
} from 'svelte';
|
||||
import { createEventDispatcher, setContext, SvelteComponent, type ComponentType } from 'svelte';
|
||||
import { get, writable } from 'svelte/store';
|
||||
import cc from 'classcat';
|
||||
import { errorMessages, Position, type NodeProps } from '@xyflow/system';
|
||||
@@ -54,6 +48,8 @@
|
||||
const nodeType = type || 'default';
|
||||
|
||||
let nodeRef: HTMLDivElement;
|
||||
let prevNodeRef: HTMLDivElement;
|
||||
|
||||
const nodeTypeValid = !!$nodeTypes[nodeType];
|
||||
|
||||
if (!nodeTypeValid) {
|
||||
@@ -109,13 +105,15 @@
|
||||
setContext('svelteflow__node_id', id);
|
||||
setContext('svelteflow__node_connectable', connectableStore);
|
||||
|
||||
onMount(() => {
|
||||
resizeObserver?.observe(nodeRef);
|
||||
|
||||
return () => {
|
||||
resizeObserver?.unobserve(nodeRef);
|
||||
};
|
||||
});
|
||||
$: {
|
||||
// hiding the noder removes html element is removed from the dom
|
||||
if (nodeRef) {
|
||||
resizeObserver?.observe(nodeRef);
|
||||
prevNodeRef = nodeRef;
|
||||
} else if (prevNodeRef) {
|
||||
resizeObserver?.unobserve(prevNodeRef);
|
||||
}
|
||||
}
|
||||
|
||||
function onSelectNodeHandler(event: MouseEvent | TouchEvent) {
|
||||
if (selectable && (!get(selectNodesOnDrag) || !draggable || get(nodeDragThreshold) > 0)) {
|
||||
|
||||
1296
pnpm-lock.yaml
generated
1296
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
||||
packages:
|
||||
- "packages/*"
|
||||
- "examples/*"
|
||||
- "tooling/*"
|
||||
- 'packages/*'
|
||||
- 'examples/*'
|
||||
- 'tooling/*'
|
||||
- 'tests/*'
|
||||
|
||||
7
tests/playwright/.gitignore
vendored
Normal file
7
tests/playwright/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
node_modules/
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/playwright/.cache/
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/playwright/.cache/
|
||||
1
tests/playwright/e2e/constants.ts
Normal file
1
tests/playwright/e2e/constants.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const FRAMEWORK = process.env.FRAMEWORK;
|
||||
161
tests/playwright/e2e/edges.spec.ts
Normal file
161
tests/playwright/e2e/edges.spec.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
import { FRAMEWORK } from './constants';
|
||||
|
||||
test.describe('EDGES', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Go to the starting url before each test.
|
||||
await page.goto('/tests/generic/edges/general');
|
||||
|
||||
// Timeout get's ignored and tests timeout after 200ms ???
|
||||
// page.waitForSelector('[data-id="edge-with-class"]', { timeout: 5000 });
|
||||
});
|
||||
|
||||
test.describe('selection', () => {
|
||||
test('selecting an edge by click', async ({ page }) => {
|
||||
const edge = page.locator(`[data-id="edge-with-class"]`);
|
||||
|
||||
await expect(edge).toBeAttached();
|
||||
await edge.click();
|
||||
await expect(edge).toHaveClass(/selected/);
|
||||
});
|
||||
|
||||
test('selecting multiple edges by meta-click', async ({ page }) => {
|
||||
const edge1 = page.locator('[data-id="edge-with-class"]');
|
||||
const edge2 = page.locator('[data-id="edge-with-style"]');
|
||||
|
||||
await expect(edge1).toBeAttached();
|
||||
await expect(edge2).toBeAttached();
|
||||
|
||||
await edge1.click();
|
||||
await expect(edge1).toHaveClass(/selected/);
|
||||
|
||||
// FIXME: I cannot get this to work
|
||||
await page.keyboard.down('MetaLeft');
|
||||
await edge2.click();
|
||||
await expect(edge2).toHaveClass(/selected/);
|
||||
|
||||
await expect(edge1).toHaveClass(/selected/);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('properties', () => {
|
||||
test('classes get applied', async ({ page }) => {
|
||||
const edge = page.locator('[data-id="edge-with-class"]');
|
||||
|
||||
await expect(edge).toHaveClass(/edge-class-test/);
|
||||
});
|
||||
|
||||
test('styles get applied', async ({ page }) => {
|
||||
const edge = page.locator('[data-id="edge-with-style"]').locator(`.${FRAMEWORK}-flow__edge-path`);
|
||||
|
||||
await expect(edge).toHaveCSS('stroke', 'rgb(255, 0, 0)');
|
||||
});
|
||||
|
||||
test('hidden=true hides edge', async ({ page }) => {
|
||||
const edge = page.locator('#hidden-edge');
|
||||
|
||||
await expect(edge).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('animated=true add "animated" class', async ({ page }) => {
|
||||
const edge = page.locator('[data-id="animated-edge"]');
|
||||
|
||||
await expect(edge).toHaveClass(/animated/);
|
||||
});
|
||||
|
||||
test('selectable=false prevents selecting of edges', async ({ page }) => {
|
||||
const edge = page.locator('[data-id="not-selectable-edge"]');
|
||||
|
||||
await expect(edge).toBeAttached();
|
||||
await expect(edge).not.toHaveClass(/selected/);
|
||||
|
||||
// For some reason these commands do not work
|
||||
// await expect(edge).toBeInViewport();
|
||||
// await edge.click();
|
||||
|
||||
const edgeBox = await edge.boundingBox();
|
||||
|
||||
await page.mouse.move(edgeBox!.x + edgeBox!.width * 0.5, edgeBox!.y + edgeBox!.height * 0.5);
|
||||
await page.mouse.down();
|
||||
await page.mouse.up();
|
||||
|
||||
await expect(edge).not.toHaveClass(/selected/);
|
||||
});
|
||||
|
||||
test('deleting edges is possible', async ({ page }) => {
|
||||
const edge = page.locator('[data-id="edge-with-class"]');
|
||||
|
||||
await expect(edge).toBeAttached();
|
||||
|
||||
const edgeBox = await edge.boundingBox();
|
||||
|
||||
await edge.click();
|
||||
await expect(edge).toHaveClass(/selected/);
|
||||
|
||||
await page.keyboard.press('Backspace');
|
||||
|
||||
await expect(edge).not.toBeAttached();
|
||||
});
|
||||
|
||||
test('deletable=false prevents deleting of edges', async ({ page }) => {
|
||||
const edge = page.locator('[data-id="not-deletable"]');
|
||||
|
||||
await expect(edge).toBeAttached();
|
||||
|
||||
// For some reason these commands do not work
|
||||
// await expect(edge).toBeInViewport();
|
||||
// await edge.click();
|
||||
|
||||
const edgeBox = await edge.boundingBox();
|
||||
|
||||
await page.mouse.move(edgeBox!.x + edgeBox!.width * 0.5, edgeBox!.y + edgeBox!.height * 0.5);
|
||||
await page.mouse.down();
|
||||
await page.mouse.up();
|
||||
|
||||
// TODO: times out on webkit
|
||||
await expect(edge).toHaveClass(/selected/);
|
||||
|
||||
await page.keyboard.press('Backspace');
|
||||
await expect(edge).toBeAttached();
|
||||
});
|
||||
|
||||
test('zIndex sets z-index of edge svgs', async ({ page }) => {
|
||||
const svg = page.locator('svg', { has: page.locator('[data-id="z-index"]') });
|
||||
|
||||
await expect(svg).toBeAttached();
|
||||
await expect(svg).toHaveCSS('z-index', '3141592');
|
||||
});
|
||||
|
||||
test('aria-lable is working', async ({ page }) => {
|
||||
const edge = page.locator('[data-id="aria-label"]');
|
||||
|
||||
await expect(edge).toHaveAttribute('aria-label', 'aria-label-test');
|
||||
});
|
||||
|
||||
test('interactionWidth is working', async ({ page }) => {
|
||||
const edge = page.locator('[data-id="interaction-width"]');
|
||||
|
||||
await expect(edge).toBeAttached();
|
||||
|
||||
const edgeBox = await edge.boundingBox();
|
||||
|
||||
//FIXME: Negative values are not working. Investigate further
|
||||
await page.mouse.move(edgeBox!.x + edgeBox!.width * 0.5 + 21, edgeBox!.y + edgeBox!.height * 0.5);
|
||||
await page.mouse.down();
|
||||
await page.mouse.up();
|
||||
|
||||
// TODO: times out on webkit
|
||||
await expect(edge).toHaveClass(/selected/);
|
||||
});
|
||||
|
||||
test('marker-start, marker-end set markers', async ({ page }) => {
|
||||
const edge = page.locator('[data-id="markers"]').locator(`.${FRAMEWORK}-flow__edge-path`);
|
||||
|
||||
await expect(edge).toBeAttached();
|
||||
|
||||
await expect(edge).toHaveAttribute('marker-start', 'url(#1__type=arrowclosed)');
|
||||
await expect(edge).toHaveAttribute('marker-end', 'url(#1__type=arrow)');
|
||||
});
|
||||
});
|
||||
});
|
||||
257
tests/playwright/e2e/nodes.spec.ts
Normal file
257
tests/playwright/e2e/nodes.spec.ts
Normal file
@@ -0,0 +1,257 @@
|
||||
import { test, expect, Locator } from '@playwright/test';
|
||||
|
||||
import { FRAMEWORK } from './constants';
|
||||
|
||||
test.describe('NODES', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Go to the starting url before each test.
|
||||
await page.goto('/tests/generic/nodes/general');
|
||||
});
|
||||
|
||||
test.describe('selection', () => {
|
||||
test('selecting a node by click', async ({ page }) => {
|
||||
const locator = page.locator(`.${FRAMEWORK}-flow__node`).first();
|
||||
await locator.click();
|
||||
|
||||
await expect(locator).toHaveClass(/selected/);
|
||||
});
|
||||
|
||||
test('selecting multiple nodes with shift drag', async ({ page }) => {
|
||||
const nodes = page.locator(`.${FRAMEWORK}-flow__node`);
|
||||
const firstNode = nodes.first();
|
||||
const secondNode = nodes.nth(1);
|
||||
const thirdNode = nodes.nth(2);
|
||||
|
||||
await expect(firstNode).toBeInViewport();
|
||||
await expect(secondNode).toBeInViewport();
|
||||
await expect(thirdNode).toBeInViewport();
|
||||
|
||||
const box = await firstNode.boundingBox();
|
||||
|
||||
await page.mouse.move(box!.x - 150, box!.y - 25);
|
||||
await page.keyboard.down('Shift');
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(box!.x + 275, box!.y + 200);
|
||||
await page.mouse.up();
|
||||
await page.keyboard.up('Shift');
|
||||
|
||||
await expect(firstNode).toHaveClass(/selected/);
|
||||
await expect(secondNode).toHaveClass(/selected/);
|
||||
await expect(thirdNode).toHaveClass(/selected/);
|
||||
|
||||
let selection: Locator | undefined;
|
||||
if (FRAMEWORK === 'react') {
|
||||
selection = page.locator('.react-flow__nodesselection');
|
||||
} else if (FRAMEWORK === 'svelte') {
|
||||
selection = page.locator('.svelte-flow__selection');
|
||||
}
|
||||
|
||||
if (selection) await expect(selection).toBeInViewport();
|
||||
});
|
||||
|
||||
test('selectable=false prevents selection', async ({ page }) => {
|
||||
const locator = page.locator(`.${FRAMEWORK}-flow__node`).and(page.locator('[data-id="notSelectable"]'));
|
||||
await locator.click();
|
||||
|
||||
await expect(locator).not.toHaveClass(/selected/);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('dragging', () => {
|
||||
test('dragging a node', async ({ page }) => {
|
||||
const node = page.locator(`.${FRAMEWORK}-flow__node`).first();
|
||||
|
||||
const transformBeforeMove = await node.evaluate((element) => {
|
||||
return element.style.transform;
|
||||
});
|
||||
|
||||
await node.hover();
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(500, 500);
|
||||
await page.mouse.up();
|
||||
|
||||
const transformAfterMove = await node.evaluate((element) => {
|
||||
return element.style.transform;
|
||||
});
|
||||
|
||||
expect(transformBeforeMove).not.toMatch(transformAfterMove);
|
||||
});
|
||||
|
||||
test('draggable=false prevents dragging', async ({ page }) => {
|
||||
const node = page.locator(`.${FRAMEWORK}-flow__node`).and(page.locator('[data-id="notDraggable"]'));
|
||||
|
||||
const transformBeforeMove = await node.evaluate((element) => {
|
||||
return element.style.transform;
|
||||
});
|
||||
|
||||
await node.hover();
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(500, 500);
|
||||
await page.mouse.up();
|
||||
|
||||
const transformAfterMove = await node.evaluate((element) => {
|
||||
return element.style.transform;
|
||||
});
|
||||
|
||||
expect(transformBeforeMove).toMatch(transformAfterMove);
|
||||
});
|
||||
|
||||
test('custom drag handle works', async ({ page }) => {
|
||||
const node = page.locator(`.${FRAMEWORK}-flow__node`).and(page.locator('[data-id="drag-handle"]'));
|
||||
const dragHandle = page.locator('.custom-drag-handle');
|
||||
|
||||
const transformBeforeMove = await node.evaluate((element) => {
|
||||
return element.style.transform;
|
||||
});
|
||||
|
||||
const nodeBox = await node.boundingBox();
|
||||
|
||||
await page.mouse.move(nodeBox!.x + 10, nodeBox!.y + 10);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(500, 500);
|
||||
await page.mouse.up();
|
||||
|
||||
const transformAfterMove = await node.evaluate((element) => {
|
||||
return element.style.transform;
|
||||
});
|
||||
|
||||
expect(transformBeforeMove).toMatch(transformAfterMove);
|
||||
|
||||
await dragHandle.hover();
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(500, 500);
|
||||
await page.mouse.up();
|
||||
|
||||
const transformAfterDragHandleMove = await node.evaluate((element) => {
|
||||
return element.style.transform;
|
||||
});
|
||||
|
||||
expect(transformBeforeMove).not.toMatch(transformAfterDragHandleMove);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('deleting', () => {
|
||||
test('deleting a node and its edges', async ({ page }) => {
|
||||
const node = page.locator(`.${FRAMEWORK}-flow__node`).and(page.locator('[data-id="Node-1"]'));
|
||||
|
||||
await node.click();
|
||||
await page.keyboard.press('Backspace');
|
||||
|
||||
await expect(node).not.toBeAttached();
|
||||
|
||||
const edges = await page.locator(`.${FRAMEWORK}-flow__edge`).all();
|
||||
expect(edges).toHaveLength(0);
|
||||
});
|
||||
|
||||
// TODO: pressing backspace creates problems on webkit
|
||||
test('deletable=false prevents deletion', async ({ page }) => {
|
||||
const node = page.locator(`.${FRAMEWORK}-flow__node`).and(page.locator('[data-id="notDeletable"]'));
|
||||
|
||||
await expect(node).toBeAttached();
|
||||
|
||||
await node.click();
|
||||
await page.keyboard.press('Backspace');
|
||||
|
||||
await expect(node).toBeAttached();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('connecting', () => {
|
||||
test('connecting two nodes', async ({ page }) => {
|
||||
const outputSourceHandle = page.locator(`.${FRAMEWORK}-flow__handle`).and(page.locator('[data-nodeid="Node-1"]'));
|
||||
const inputSourceHandle = page.locator(`.${FRAMEWORK}-flow__handle`).and(page.locator('[data-nodeid="Node-4"]'));
|
||||
|
||||
await expect(outputSourceHandle).toBeInViewport();
|
||||
await expect(inputSourceHandle).toBeInViewport();
|
||||
|
||||
const edgesBefore = await page.locator(`.${FRAMEWORK}-flow__edge`).all();
|
||||
|
||||
await outputSourceHandle.hover();
|
||||
await page.mouse.down();
|
||||
await inputSourceHandle.hover();
|
||||
await page.mouse.up();
|
||||
|
||||
const edgesAfter = await page.locator(`.${FRAMEWORK}-flow__edge`).all();
|
||||
expect(edgesAfter).toHaveLength(edgesBefore.length + 1);
|
||||
});
|
||||
|
||||
test('connecting two output handles does not work', async ({ page }) => {
|
||||
const firstOutputHandle = page.locator(`.${FRAMEWORK}-flow__handle`).and(page.locator('[data-nodeid="Node-2"]'));
|
||||
const secondOutputHandle = page.locator(`.${FRAMEWORK}-flow__handle`).and(page.locator('[data-nodeid="Node-4"]'));
|
||||
|
||||
await expect(firstOutputHandle).toBeInViewport();
|
||||
await expect(secondOutputHandle).toBeInViewport();
|
||||
|
||||
const edgesBefore = await page.locator(`.${FRAMEWORK}-flow__edge`).all();
|
||||
|
||||
await firstOutputHandle.hover();
|
||||
await page.mouse.down();
|
||||
await secondOutputHandle.hover();
|
||||
await page.mouse.up();
|
||||
|
||||
const edgesAfter = await page.locator(`.${FRAMEWORK}-flow__edge`).all();
|
||||
expect(edgesAfter).toHaveLength(edgesBefore.length);
|
||||
});
|
||||
|
||||
test('connecting two input handles does not work', async ({ page }) => {
|
||||
const firstInputHandle = page.locator(`.${FRAMEWORK}-flow__handle`).and(page.locator('[data-nodeid="Node-1"]'));
|
||||
const secondInputHandle = page
|
||||
.locator(`.${FRAMEWORK}-flow__handle`)
|
||||
.and(page.locator('[data-nodeid="Node-3"]'))
|
||||
.and(page.locator('.source'));
|
||||
|
||||
await expect(firstInputHandle).toBeInViewport();
|
||||
await expect(secondInputHandle).toBeInViewport();
|
||||
|
||||
const edgesBefore = await page.locator(`.${FRAMEWORK}-flow__edge`).all();
|
||||
|
||||
await firstInputHandle.hover();
|
||||
await page.mouse.down();
|
||||
await secondInputHandle.hover();
|
||||
await page.mouse.up();
|
||||
|
||||
const edgesAfter = await page.locator(`.${FRAMEWORK}-flow__edge`).all();
|
||||
expect(edgesAfter).toHaveLength(edgesBefore.length);
|
||||
});
|
||||
|
||||
test('connectable=false prevents connections', async ({ page }) => {
|
||||
const outputHandle = page.locator(`.${FRAMEWORK}-flow__handle`).and(page.locator('[data-nodeid="Node-1"]'));
|
||||
const notConnectableHandle = page
|
||||
.locator(`.${FRAMEWORK}-flow__handle`)
|
||||
.and(page.locator('[data-nodeid="notConnectable"]'));
|
||||
|
||||
const notConnectableBox = await notConnectableHandle.boundingBox();
|
||||
|
||||
await expect(outputHandle).toBeInViewport();
|
||||
await expect(notConnectableHandle).toBeInViewport();
|
||||
|
||||
const edgesBefore = await page.locator(`.${FRAMEWORK}-flow__edge`).all();
|
||||
|
||||
await outputHandle.hover();
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(notConnectableBox!.x + 2, notConnectableBox!.y + 2);
|
||||
await page.mouse.up();
|
||||
|
||||
const edgesAfter = await page.locator(`.${FRAMEWORK}-flow__edge`).all();
|
||||
expect(edgesAfter).toHaveLength(edgesBefore.length);
|
||||
});
|
||||
});
|
||||
|
||||
test('hidden=true hides the node', async ({ page }) => {
|
||||
const node = page.locator(`.${FRAMEWORK}-flow__node`).and(page.locator('[data-id="hidden"]'));
|
||||
|
||||
await expect(node).not.toBeInViewport();
|
||||
});
|
||||
|
||||
test('classes get applied', async ({ page }) => {
|
||||
const node = page.locator(`.${FRAMEWORK}-flow__node`).and(page.locator('[data-id="Node-1"]'));
|
||||
|
||||
await expect(node).toHaveClass(/playwright-test-class-123/);
|
||||
});
|
||||
|
||||
test('styles get applied', async ({ page }) => {
|
||||
const node = page.locator(`.${FRAMEWORK}-flow__node`).and(page.locator('[data-id="Node-1"]'));
|
||||
|
||||
await expect(node).toHaveCSS('background-color', 'rgb(255, 0, 0)');
|
||||
});
|
||||
});
|
||||
196
tests/playwright/e2e/pane.spec.ts
Normal file
196
tests/playwright/e2e/pane.spec.ts
Normal file
@@ -0,0 +1,196 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
import { FRAMEWORK } from './constants';
|
||||
|
||||
const MATCH_ALL_NUMBERS = /[\d\.]+/g;
|
||||
|
||||
// Type "Locator" not exported...
|
||||
async function getTransform(element) {
|
||||
const transformString = await element.evaluate((el) => {
|
||||
return el.style.transform;
|
||||
});
|
||||
|
||||
// Parses all numbers in f.ex "translate(590px, 324px) scale(2)""
|
||||
const transforms = transformString.match(MATCH_ALL_NUMBERS);
|
||||
return {
|
||||
translateX: parseFloat(transforms![0]),
|
||||
translateY: parseFloat(transforms![1]),
|
||||
scale: parseFloat(transforms![2]),
|
||||
};
|
||||
}
|
||||
|
||||
test.describe('PANE DEFAULT', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Go to the starting url before each test.
|
||||
await page.goto('/tests/generic/pane/general');
|
||||
|
||||
// Wait till the edges are rendered
|
||||
await page.waitForSelector('[data-id="first-edge"]', { timeout: 5000 });
|
||||
});
|
||||
|
||||
test.describe('pan & zoom', () => {
|
||||
test('panning the pane moves it', async ({ page }) => {
|
||||
const pane = page.locator(`.${FRAMEWORK}-flow__pane`);
|
||||
const viewport = page.locator(`.${FRAMEWORK}-flow__viewport`);
|
||||
|
||||
await expect(pane).toBeAttached();
|
||||
|
||||
const paneBox = await pane.boundingBox();
|
||||
|
||||
const transformsBefore = await getTransform(viewport);
|
||||
|
||||
await pane.hover();
|
||||
await page.mouse.down();
|
||||
// Move pane by 100, 100
|
||||
await page.mouse.move(paneBox!.x + paneBox!.width * 0.5 + 100, paneBox!.y + paneBox!.height * 0.5 + 100);
|
||||
|
||||
const transformsAfter = await getTransform(viewport);
|
||||
|
||||
expect(transformsAfter.translateX - transformsBefore.translateX).toBe(100);
|
||||
expect(transformsAfter.translateY - transformsBefore.translateY).toBe(100);
|
||||
});
|
||||
|
||||
test('scrolling the default pane zooms it', async ({ page }) => {
|
||||
const pane = page.locator(`.${FRAMEWORK}-flow__pane`);
|
||||
const viewport = page.locator(`.${FRAMEWORK}-flow__viewport`);
|
||||
|
||||
await expect(pane).toBeAttached();
|
||||
|
||||
const transformsBefore = await getTransform(viewport);
|
||||
|
||||
await pane.hover();
|
||||
await page.mouse.wheel(0, 100);
|
||||
|
||||
const transformsAfter = await getTransform(viewport);
|
||||
|
||||
expect(transformsAfter.scale).not.toBe(transformsBefore.scale);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('minZoom & maxZoom', () => {
|
||||
test('minZoom works as intended', async ({ page }) => {
|
||||
const pane = page.locator(`.${FRAMEWORK}-flow__pane`);
|
||||
const viewport = page.locator(`.${FRAMEWORK}-flow__viewport`);
|
||||
|
||||
await expect(pane).toBeAttached();
|
||||
|
||||
await pane.hover();
|
||||
|
||||
// Zoom out
|
||||
await page.mouse.wheel(5000, 5000);
|
||||
|
||||
const transformsMinZoom = await getTransform(viewport);
|
||||
expect(transformsMinZoom.scale).toBe(0.25);
|
||||
});
|
||||
|
||||
test('maxZoom works as intended', async ({ page }) => {
|
||||
const pane = page.locator(`.${FRAMEWORK}-flow__pane`);
|
||||
const viewport = page.locator(`.${FRAMEWORK}-flow__viewport`);
|
||||
|
||||
await expect(pane).toBeAttached();
|
||||
|
||||
await pane.hover();
|
||||
|
||||
// Zoom in
|
||||
await page.mouse.wheel(-5000, -5000);
|
||||
|
||||
const transformsMaxZoom = await getTransform(viewport);
|
||||
expect(transformsMaxZoom.scale).toBe(4);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('autoPan', () => {
|
||||
test('autoPanOnNodeDrag works as intended', async ({ page }) => {
|
||||
const viewport = page.locator(`.${FRAMEWORK}-flow__viewport`);
|
||||
const node = page.locator('[data-id="1"]');
|
||||
|
||||
await expect(node).toBeAttached();
|
||||
|
||||
const transformBefore = await getTransform(viewport);
|
||||
|
||||
await node.hover();
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(0, 0);
|
||||
await page.waitForTimeout(500);
|
||||
await page.mouse.move(100, 100);
|
||||
|
||||
const transformAfter = await getTransform(viewport);
|
||||
|
||||
await expect(transformAfter.translateX).toBeGreaterThan(transformBefore.translateX);
|
||||
await expect(transformAfter.translateY).toBeGreaterThan(transformBefore.translateY);
|
||||
});
|
||||
|
||||
test('autoPanOnConnect works as intended', async ({ page }) => {
|
||||
const viewport = page.locator(`.${FRAMEWORK}-flow__viewport`);
|
||||
const handle = page.locator(`[data-id="1"] .${FRAMEWORK}-flow__handle`);
|
||||
|
||||
await expect(handle).toBeAttached();
|
||||
|
||||
const transformBefore = await getTransform(viewport);
|
||||
|
||||
await handle.hover();
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(0, 0);
|
||||
await page.waitForTimeout(500);
|
||||
await page.mouse.move(100, 100);
|
||||
|
||||
const transformAfter = await getTransform(viewport);
|
||||
|
||||
await expect(transformAfter.translateX).toBeGreaterThan(transformBefore.translateX);
|
||||
await expect(transformAfter.translateY).toBeGreaterThan(transformBefore.translateY);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('PANE NON-DEFAULT', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Go to the starting url before each test.
|
||||
await page.goto('/tests/generic/pane/non-defaults');
|
||||
|
||||
// Wait till the edges are rendered
|
||||
await page.waitForSelector('[data-id="first-edge"]', { timeout: 5000 });
|
||||
});
|
||||
|
||||
test.describe('pan & zoom', () => {
|
||||
test('panOnScroll pans the pane on scrolling', async ({ page }) => {
|
||||
const pane = page.locator(`.${FRAMEWORK}-flow__pane`);
|
||||
const viewport = page.locator(`.${FRAMEWORK}-flow__viewport`);
|
||||
|
||||
await expect(pane).toBeAttached();
|
||||
|
||||
const transformsBefore = await getTransform(viewport);
|
||||
|
||||
await pane.hover();
|
||||
await page.mouse.wheel(100, 100);
|
||||
|
||||
const transformsAfter = await getTransform(viewport);
|
||||
|
||||
expect(transformsAfter.translateX).not.toBe(transformsBefore.translateX);
|
||||
expect(transformsAfter.translateY).not.toBe(transformsBefore.translateY);
|
||||
});
|
||||
|
||||
test('intialViewport', async ({ page }) => {
|
||||
const pane = page.locator(`.${FRAMEWORK}-flow__pane`);
|
||||
const viewport = page.locator(`.${FRAMEWORK}-flow__viewport`);
|
||||
|
||||
await expect(pane).toBeAttached();
|
||||
|
||||
const viewportTransform = await getTransform(viewport);
|
||||
|
||||
expect(viewportTransform.translateX).toBe(1.23);
|
||||
expect(viewportTransform.translateY).toBe(9.87);
|
||||
expect(viewportTransform.scale).toBe(1.234);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('PANE ACTIVATION KEYS', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Go to the starting url before each test.
|
||||
await page.goto('/tests/generic/pane/activation-keys');
|
||||
|
||||
// Wait till the edges are rendered
|
||||
await page.waitForSelector('[data-id="first-edge"]', { timeout: 5000 });
|
||||
});
|
||||
// TODO
|
||||
});
|
||||
1377
tests/playwright/package-lock.json
generated
Normal file
1377
tests/playwright/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
27
tests/playwright/package.json
Normal file
27
tests/playwright/package.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "playwright",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test:svelte": "FRAMEWORK=svelte npx playwright test -c playwright.svelte.config.ts",
|
||||
"test:react": "FRAMEWORK=react npx playwright test -c playwright.react.config.ts",
|
||||
"test:react:ui": "pnpm run test:react --ui",
|
||||
"test:svelte:ui": "pnpm run test:svelte --ui"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.39.0",
|
||||
"@types/node": "^18.7.16"
|
||||
},
|
||||
"dependencies": {
|
||||
"@playwright/experimental-ct-react": "^1.39.0",
|
||||
"@types/react": "^18.2.31",
|
||||
"@types/react-dom": "^18.2.14",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"typescript": "^5.2.2"
|
||||
}
|
||||
}
|
||||
4
tests/playwright/playwright.react.config.ts
Normal file
4
tests/playwright/playwright.react.config.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { defineConfig } from '@playwright/test';
|
||||
import { sharedConfigWithPort } from './playwright.shared.config';
|
||||
|
||||
export default defineConfig(sharedConfigWithPort({ port: 3000, framework: 'react' }));
|
||||
73
tests/playwright/playwright.shared.config.ts
Normal file
73
tests/playwright/playwright.shared.config.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { PlaywrightTestConfig, devices } from '@playwright/test';
|
||||
|
||||
type ConfigParams = {
|
||||
port: number;
|
||||
framework: string;
|
||||
};
|
||||
|
||||
export function sharedConfigWithPort({ port, framework }: ConfigParams): PlaywrightTestConfig {
|
||||
return {
|
||||
testDir: './e2e',
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Retry on CI only */
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: 'html',
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
baseURL: `http://localhost:${port}/`,
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: 'on-first-retry',
|
||||
},
|
||||
webServer: {
|
||||
command: `pnpm --filter=${framework}-examples run dev --port ${port}`,
|
||||
port,
|
||||
reuseExistingServer: !process.env.CI,
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
},
|
||||
|
||||
{
|
||||
name: 'firefox',
|
||||
use: { ...devices['Desktop Firefox'] },
|
||||
},
|
||||
|
||||
{
|
||||
name: 'webkit',
|
||||
use: { ...devices['Desktop Safari'] },
|
||||
},
|
||||
|
||||
/* Test against mobile viewports. */
|
||||
// {
|
||||
// name: 'Mobile Chrome',
|
||||
// use: { ...devices['Pixel 5'] },
|
||||
// },
|
||||
// {
|
||||
// name: 'Mobile Safari',
|
||||
// use: { ...devices['iPhone 12'] },
|
||||
// },
|
||||
|
||||
/* Test against branded browsers. */
|
||||
// {
|
||||
// name: 'Microsoft Edge',
|
||||
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
||||
// },
|
||||
// {
|
||||
// name: 'Google Chrome',
|
||||
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
||||
// },
|
||||
],
|
||||
};
|
||||
}
|
||||
4
tests/playwright/playwright.svelte.config.ts
Normal file
4
tests/playwright/playwright.svelte.config.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { defineConfig } from '@playwright/test';
|
||||
import { sharedConfigWithPort } from './playwright.shared.config';
|
||||
|
||||
export default defineConfig(sharedConfigWithPort({ port: 5173, framework: 'svelte' }));
|
||||
Reference in New Issue
Block a user