dispatch(setNodesSelection({ isActive: false }))}\n ref={zoomPane}\n />\n
\n );\n});\n\nGraphView.displayName = 'GraphView';\n\nexport default GraphView;\n","import { useEffect, useContext, memo } from 'react';\n\nimport useKeyPress from '../hooks/useKeyPress';\nimport { setNodesSelection } from '../state/actions';\nimport { GraphContext } from '../GraphContext';\nimport { isEdge, getConnectedEdges } from '../graph-utils';\n\nexport default memo((props) => {\n const { state, dispatch } = useContext(GraphContext);\n const removePressed = useKeyPress('Backspace');\n\n useEffect(() => {\n if (removePressed && state.selectedElements.length) {\n let elementsToRemove = state.selectedElements;\n\n // we also want to remove the edges if only one node is selected\n if (state.selectedElements.length === 1 && !isEdge(state.selectedElements[0])) {\n const connectedEdges = getConnectedEdges(state.selectedElements, state.edges);\n elementsToRemove = [...state.selectedElements, ...connectedEdges];\n }\n\n props.onElementsRemove(elementsToRemove);\n dispatch(setNodesSelection({ isActive: false }));\n }\n }, [removePressed])\n\n return null;\n});\n","import React, { memo } from 'react';\nimport cx from 'classnames';\n\nfunction onMouseDown(evt, { nodeId, setSourceId, setPosition, onConnect, isTarget }) {\n const containerBounds = document.querySelector('.react-graph').getBoundingClientRect();\n\n setPosition({\n x: evt.clientX - containerBounds.x,\n y: evt.clientY - containerBounds.y,\n });\n setSourceId(nodeId);\n\n function onMouseMove(evt) {\n setPosition({\n x: evt.clientX - containerBounds.x,\n y: evt.clientY - containerBounds.y,\n });\n }\n\n function onMouseUp(evt) {\n const elementBelow = document.elementFromPoint(evt.clientX, evt.clientY);\n\n if (elementBelow && (elementBelow.classList.contains('target') || elementBelow.classList.contains('source'))) {\n if (isTarget) {\n const sourceId = elementBelow.getAttribute('data-nodeid');\n onConnect({ source: sourceId, target: nodeId });\n } else {\n const targetId = elementBelow.getAttribute('data-nodeid');\n onConnect({ source: nodeId, target: targetId });\n }\n }\n\n setSourceId(null);\n document.removeEventListener('mousemove', onMouseMove);\n document.removeEventListener('mouseup', onMouseUp);\n }\n\n document.addEventListener('mousemove', onMouseMove);\n document.addEventListener('mouseup', onMouseUp)\n}\n\nconst BaseHandle = memo(({\n source, target, nodeId, onConnect,\n setSourceId, setPosition, className, ...rest\n}) => {\n const handleClasses = cx(\n 'react-graph__handle',\n className,\n { source, target }\n );\n\n return (\n
onMouseDown(evt, { nodeId, setSourceId, setPosition, onConnect, isTarget: target })}\n {...rest}\n />\n );\n});\n\nBaseHandle.displayName = 'BaseHandle';\nBaseHandle.whyDidYouRender = false;\n\nexport default BaseHandle;\n","import { createContext } from 'react';\n\nexport const NodeIdContext = createContext(null);\nexport const Provider = NodeIdContext.Provider;\nexport const Consumer = NodeIdContext.Consumer;\n\nexport default NodeIdContext;\n","import React, { memo, useContext } from 'react';\n\nimport BaseHandle from './BaseHandle';\nimport { ConnectionContext } from '../../ConnectionContext';\nimport NodeIdContext from '../NodeIdContext'\n\nconst TargetHandle = memo((props) => {\n const nodeId = useContext(NodeIdContext);\n const { setPosition, setSourceId, onConnect } = useContext(ConnectionContext);\n\n return (\n
\n );\n});\n\nTargetHandle.displayName = 'TargetHandle';\nTargetHandle.whyDidYouRender = false;\n\nexport default TargetHandle;\n","import React, { memo, useContext } from 'react';\n\nimport BaseHandle from './BaseHandle';\nimport NodeIdContext from '../NodeIdContext'\nimport { ConnectionContext } from '../../ConnectionContext';\n\nexport default memo((props) => {\n const nodeId = useContext(NodeIdContext);\n const { setPosition, setSourceId, onConnect } = useContext(ConnectionContext);\n\n return (\n
\n );\n});\n","import React from 'react';\n\nimport TargetHandle from '../HandleTypes/TargetHandle';\nimport SourceHandle from '../HandleTypes/SourceHandle';\n\nconst nodeStyles = {\n background: '#ff6060',\n padding: 10,\n borderRadius: 5,\n width: 150\n};\n\nexport default ({ data, style }) => (\n
\n \n {data.label}\n \n
\n);\n","import React from 'react';\n\nimport SourceHandle from '../HandleTypes/SourceHandle';\n\nconst nodeStyles = {\n background: '#9999ff',\n padding: 10,\n borderRadius: 5,\n width: 150\n};\n\nexport default ({ data, style }) => (\n
\n {data.label}\n \n
\n);\n","import React from 'react';\n\nimport TargetHandle from '../HandleTypes/TargetHandle';\n\nconst nodeStyles = {\n background: '#55dd99',\n padding: 10,\n borderRadius: 5,\n width: 150\n};\n\nexport default ({ data, style }) => (\n
\n \n {data.label}\n
\n);\n","import React, { useEffect, useRef, useState, memo } from 'react';\nimport ReactDraggable from 'react-draggable';\nimport cx from 'classnames';\n\nimport { updateNodeData, updateNodePos, setSelectedElements } from '../../state/actions';\nimport { Provider } from '../NodeIdContext';\n\nconst isInput = e => ['INPUT', 'SELECT', 'TEXTAREA'].includes(e.target.nodeName);\nconst isHandle = e => (\n e.target.className &&\n e.target.className.includes &&\n (e.target.className.includes('source') || e.target.className.includes('target'))\n);\n\nconst getHandleBounds = (sel, nodeElement, parentBounds, k) => {\n const handle = nodeElement.querySelector(sel);\n\n if (!handle) {\n return null;\n }\n\n const bounds = handle.getBoundingClientRect();\n const unscaledWith = Math.round(bounds.width * (1 / k));\n const unscaledHeight = Math.round(bounds.height * (1 / k));\n\n return {\n x: (bounds.x - parentBounds.x) * (1 / k),\n y: (bounds.y - parentBounds.y) * (1 / k),\n width: unscaledWith,\n height: unscaledHeight\n };\n};\n\nconst onStart = (evt, { dispatch, setOffset, onClick, id, type, data, position, transform }) => {\n if (isInput(evt) || isHandle(evt)) {\n return false;\n }\n\n const scaledClient = {\n x: evt.clientX * (1 / [transform[2]]),\n y: evt.clientY * (1 / [transform[2]])\n }\n const offsetX = scaledClient.x - position.x - [transform[0]];\n const offsetY = scaledClient.y - position.y - [transform[1]];\n const node = { id, type, position, data }\n\n dispatch(setSelectedElements({ id, type }));\n setOffset({ x: offsetX, y: offsetY });\n onClick(node);\n};\n\nconst onDrag = (evt, { dispatch, setDragging, id, offset, transform }) => {\n const scaledClient = {\n x: evt.clientX * (1 / [transform[2]]),\n y: evt.clientY * (1 / [transform[2]])\n };\n\n setDragging(true);\n dispatch(updateNodePos(id, {\n x: scaledClient.x - [transform[0]] - offset.x,\n y: scaledClient.y - [transform[1]] - offset.y\n }));\n};\n\nconst onStop = ({ onNodeDragStop, setDragging, isDragging, id, type, position, data }) => {\n if (!isDragging) {\n return false;\n }\n\n setDragging(false);\n onNodeDragStop({\n id, type, position, data\n });\n};\n\nexport default NodeComponent => {\n const WrappedComp = memo((props) => {\n const nodeElement = useRef(null);\n const [offset, setOffset] = useState({ x: 0, y: 0 });\n const [isDragging, setDragging] = useState(false);\n const {\n id, type, data, transform, xPos, yPos, selected,\n dispatch, onClick, onNodeDragStop, style\n } = props;\n\n const position = { x: xPos, y: yPos };\n const nodeClasses = cx('react-graph__node', { selected });\n const nodeStyle = { zIndex: selected ? 10 : 3, transform: `translate(${xPos}px,${yPos}px)` };\n\n useEffect(() => {\n const bounds = nodeElement.current.getBoundingClientRect();\n const unscaledWith = Math.round(bounds.width * (1 / transform[2]));\n const unscaledHeight = Math.round(bounds.height * (1 / transform[2]));\n const handleBounds = {\n source: getHandleBounds('.source', nodeElement.current, bounds, transform[2]),\n target: getHandleBounds('.target', nodeElement.current, bounds, transform[2])\n };\n\n dispatch(updateNodeData(id, { width: unscaledWith, height: unscaledHeight, handleBounds }));\n }, []);\n\n return (\n
onStart(evt, { dispatch, onClick, id, type, data, setOffset, transform, position })}\n onDrag={evt => onDrag(evt, { dispatch, setDragging, id, offset, transform })}\n onStop={() => onStop({ onNodeDragStop, isDragging, setDragging, id, type, position, data })}\n scale={transform[2]}\n >\n \n \n );\n });\n\n WrappedComp.displayName = 'Wrapped Node';\n WrappedComp.whyDidYouRender = false;\n\n return WrappedComp;\n};\n","import DefaultNode from './NodeTypes/DefaultNode';\nimport InputNode from './NodeTypes/InputNode';\nimport OutputNode from './NodeTypes/OutputNode';\nimport wrapNode from './NodeTypes/wrapNode';\n\nexport function createNodeTypes(nodeTypes) {\n const standardTypes = {\n input: wrapNode(nodeTypes.input || InputNode),\n default: wrapNode(nodeTypes.default || DefaultNode),\n output: wrapNode(nodeTypes.output || OutputNode)\n };\n\n const specialTypes = Object\n .keys(nodeTypes)\n .filter(k => !['input', 'default', 'output'].includes(k))\n .reduce((res, key) => {\n res[key] = wrapNode(nodeTypes[key] || DefaultNode);\n\n return res;\n }, {});\n\n return {\n ...standardTypes,\n ...specialTypes\n };\n}\n","import React, { memo } from 'react';\n\nexport default memo((props) => {\n const {\n sourceX, sourceY, targetX, targetY, style = {}\n } = props;\n\n const yOffset = Math.abs(targetY - sourceY) / 2;\n const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset;\n const dAttr = `M${sourceX},${sourceY} C${sourceX},${centerY} ${targetX},${centerY} ${targetX},${targetY}`;\n\n return (\n
\n );\n});\n","import React, { memo } from 'react';\n\nexport default memo((props) => {\n const {\n sourceX, sourceY, targetX, targetY, style = {}\n } = props;\n\n return (\n
\n );\n});\n","import React, { memo } from 'react';\n\nexport default memo((props) => {\n const {\n sourceX, sourceY, targetX, targetY, style = {}\n } = props;\n\n const yOffset = Math.abs(targetY - sourceY) / 2;\n const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset;\n\n return (\n
\n );\n});\n","import React, { memo } from 'react';\nimport cx from 'classnames';\n\nimport { setSelectedElements } from '../../state/actions';\n\nconst isInput = e => ['INPUT', 'SELECT', 'TEXTAREA'].includes(e.target.nodeName);\n\nexport default EdgeComponent => {\n const WrappedEdge = memo((props) => {\n const {\n source, target, animated, type,\n dispatch, selected, onClick\n } = props;\n const edgeClasses = cx('react-graph__edge', { selected, animated: animated });\n\n return (\n
{\n if (isInput(e)) {\n return false;\n }\n\n dispatch(setSelectedElements({ source, target }));\n onClick({ source, target, type });\n }}\n >\n \n \n );\n });\n\n WrappedEdge.displayName = 'Wrapped Edge';\n WrappedEdge.whyDidYouRender = false;\n\n return WrappedEdge;\n};\n","import StraightEdge from './EdgeTypes/StraightEdge';\nimport BezierEdge from './EdgeTypes/BezierEdge';\nimport wrapEdge from './EdgeTypes/wrapEdge';\n\nexport function createEdgeTypes(edgeTypes) {\n const standardTypes = {\n default: wrapEdge(edgeTypes.default || BezierEdge),\n straight: wrapEdge(edgeTypes.bezier || StraightEdge)\n };\n\n const specialTypes = Object\n .keys(edgeTypes)\n .filter(k => !['default', 'bezier'].includes(k))\n .reduce((res, key) => {\n res[key] = wrapEdge(edgeTypes[key] ||BezierEdge);\n\n return res;\n }, {});\n\n return {\n ...standardTypes,\n ...specialTypes\n };\n}\n","import React, { useMemo } from 'react';\n\nif (process.env.NODE_ENV !== 'production') {\n const whyDidYouRender = require('@welldone-software/why-did-you-render');\n whyDidYouRender(React);\n}\n\nimport GraphView from '../GraphView';\nimport GlobalKeyHandler from '../GlobalKeyHandler';\nimport { Provider } from '../GraphContext';\n\nimport DefaultNode from '../NodeRenderer/NodeTypes/DefaultNode';\nimport InputNode from '../NodeRenderer/NodeTypes/InputNode';\nimport OutputNode from '../NodeRenderer/NodeTypes/OutputNode';\nimport { createNodeTypes } from '../NodeRenderer/utils';\n\nimport BezierEdge from '../EdgeRenderer/EdgeTypes/BezierEdge';\nimport StraightEdge from '../EdgeRenderer/EdgeTypes/StraightEdge';\nimport StepEdge from '../EdgeRenderer/EdgeTypes/StepEdge';\nimport { createEdgeTypes } from '../EdgeRenderer/utils';\n\nimport '../style.css';\n\nconst ReactGraph = ({\n style, onElementClick, elements, children,\n onLoad, onMove, onElementsRemove, onConnect,\n onNodeDragStop, connectionLineType, connectionLineStyle\n}) => {\n const nodeTypes = useMemo(() => createNodeTypes(props.nodeTypes), []);\n const edgeTypes = useMemo(() => createEdgeTypes(props.edgeTypes), []);\n\n return (\n
\n
\n \n \n {children}\n \n
\n );\n};\n\nReactGraph.displayName = 'ReactGraph';\n\nReactGraph.defaultProps = {\n onElementClick: () => {},\n onElementsRemove: () => {},\n onNodeDragStop: () => {},\n onConnect: () => {},\n\tonLoad: () => {},\n onMove: () => {},\n nodeTypes: {\n input: InputNode,\n default: DefaultNode,\n output: OutputNode\n },\n edgeTypes: {\n default: BezierEdge,\n straight: StraightEdge,\n step: StepEdge\n },\n connectionLineType: 'bezier',\n connectionLineStyle: {}\n};\n\nexport default ReactGraph;\n","import React, { useRef, useContext, useEffect } from 'react';\nimport classnames from 'classnames';\n\nimport { isFunction } from '../../utils'\nimport { getNodesInside } from '../../graph-utils';\nimport { GraphContext } from '../../GraphContext';\n\nconst baseStyle = {\n position: 'absolute',\n zIndex: 5,\n bottom: 10,\n right: 10,\n width: 200,\n};\n\nexport default ({ style = {}, className, bgColor = '#f8f8f8', nodeColor = '#ddd' }) => {\n const canvasNode = useRef(null);\n const { state } = useContext(GraphContext);\n const mapClasses = classnames('react-graph__minimap', className);\n const nodePositions = state.nodes.map(n => n.__rg.position);\n const width = style.width || baseStyle.width;\n const height = (state.height / (state.width || 1)) * width;\n const bbox = { x: 0, y: 0, width: state.width, height: state.height };\n const scaleFactor = width / state.width;\n const nodeColorFunc = isFunction(nodeColor) ? nodeColor : () => nodeColor;\n\n useEffect(() => {\n if (canvasNode) {\n const ctx = canvasNode.current.getContext('2d');\n const nodesInside = getNodesInside(state.nodes, bbox, state.transform, true);\n\n ctx.fillStyle = bgColor;\n ctx.fillRect(0, 0, width, height);\n\n nodesInside.forEach((n) => {\n const pos = n.__rg.position;\n const transformX = state.transform[0];\n const transformY = state.transform[1];\n const x = (pos.x * state.transform[2]) + transformX;\n const y = (pos.y * state.transform[2]) + transformY;\n\n ctx.fillStyle = nodeColorFunc(n);\n\n ctx.fillRect(\n (x * scaleFactor),\n (y * scaleFactor),\n n.__rg.width * scaleFactor * state.transform[2],\n n.__rg.height * scaleFactor * state.transform[2]\n );\n });\n }\n }, [nodePositions, state.transform, height])\n\n return (\n
\n );\n}\n","import ReactGraph from './ReactGraph';\n\nexport default ReactGraph;\n\nexport { default as SourceHandle } from './NodeRenderer/HandleTypes/SourceHandle';\nexport { default as TargetHandle } from './NodeRenderer/HandleTypes/TargetHandle';\nexport { default as MiniMap } from './Plugins/MiniMap';\n\nexport {\n isNode,\n isEdge,\n removeElements,\n getOutgoers\n} from './graph-utils';\n","import React, { PureComponent } from 'react';\n\nimport Graph, { isEdge, removeElements, getOutgoers, SourceHandle, TargetHandle, MiniMap } from '../src';\n// import Graph, { isEdge, removeElements, getOutgoers, SourceHandle, TargetHandle } from '../dist/ReactGraph';\n\nconst SpecialNode = ({ data, styles }) => (\n
\n
\n
I am special!
{data.label}
\n
\n
\n
\n);\n\nconst InputNode = ({ data, styles }) => (\n
\n
\n
{data.input}
\n
data.onChange(e.target.value, data)} />\n
\n
\n);\n\nconst onNodeDragStop = node => console.log('drag stop', node);\n\nclass App extends PureComponent {\n constructor() {\n super();\n\n const onChange = (option, d) => {\n this.setState(prevState => (\n {elements: prevState.elements.map(e => {\n if (isEdge(e) || e.id !== '6') {\n return e;\n }\n\n return { ...e, data: { ...e.data, label: `Option ${option} selected.` } };\n })}\n ));\n }\n\n const onChangeInput = (input, d) => {\n this.setState(prevState => (\n {elements: prevState.elements.map(e => {\n if (isEdge(e) || e.id !== '8') {\n return e;\n }\n\n if (e.id === '8') {\n return { ...e, data: { ...e.data, input: input || 'write something' } };\n }\n })}\n ));\n }\n\n this.state = {\n graphLoaded: false,\n elements: [\n { id: '1', type: 'input', data: { label: '1 Tests' }, position: { x: 250, y: 5 } },\n { id: '2', data: { label: '2 This is a node This is a node This is a node This is a node' }, position: { x: 100, y: 100 } },\n { id: '3', data: { label: '3 I bring my own style' }, position: { x: 100, y: 200 }, style: { background: '#eee', color: '#222', border: '1px solid #bbb' } },\n { id: '4', type: 'output', data: { label: '4 nody nodes' }, position: { x: 50, y: 300 } },\n { id: '5', type: 'default', data: { label: '5 Another node'}, position: { x: 400, y: 300 } },\n { id: '6', type: 'special', data: { onChange, label: '6 no option selected' }, position: { x: 425, y: 375 } },\n { id: '7', type: 'output', data: { label: '7 output' }, position: { x: 250, y: 500 } },\n { id: '8', type: 'text', data: { onChange: onChangeInput, input: 'write something' }, position: { x: 300, y: 100 } },\n { source: '1', target: '2', animated: true },\n { source: '1', target: '8', animated: true },\n { source: '2', target: '3' },\n { source: '3', target: '4', type: 'step' },\n { source: '3', target: '5' },\n { source: '5', target: '6', type: 'step', animated: true, style: { stroke: '#FFCC00' } },\n { source: '6', target: '7', style: { stroke: '#FFCC00' }},\n ]\n };\n\n this.onElementClick = this.onElementClick.bind(this);\n this.onConnect = this.onConnect.bind(this);\n }\n\n onLoad(graphInstance) {\n console.log('graph loaded:', graphInstance);\n window.rg = graphInstance;\n\n this.graphInstance = graphInstance;\n this.graphInstance.fitView({ padding: 0.1 });\n this.setState({\n graphLoaded: true\n });\n }\n\n onFitView() {\n this.graphInstance.fitView();\n }\n\n onAdd() {\n this.setState(prevState => ({\n ...prevState,\n elements: prevState.elements.concat({\n id: (prevState.elements.length + 1).toString(),\n data: { label: 'Added node' },\n position: { x: Math.random() * window.innerWidth, y: Math.random() * window.innerHeight }\n })\n }));\n }\n\n onElementClick(element) {\n console.log('click', element);\n console.log('outgoers', getOutgoers(element, this.state.elements));\n }\n\n onZoomIn() {\n this.graphInstance.zoomIn();\n }\n\n onZoomOut() {\n this.graphInstance.zoomOut();\n }\n\n onElementsRemove(elementsToRemove) {\n this.setState(prevState => ({\n elements: removeElements(prevState.elements, elementsToRemove)\n }));\n }\n\n onConnect(params) {\n console.log('connect', params);\n this.setState(prevState => ({\n elements: prevState.elements.concat(params)\n }));\n }\n\n render() {\n return (\n
this.onElementsRemove(elements)}\n onConnect={this.onConnect}\n onNodeDragStop={onNodeDragStop}\n style={{ width: '100%', height: '100%' }}\n onLoad={graphInstance => this.onLoad(graphInstance)}\n nodeTypes={{\n special: SpecialNode,\n text: InputNode\n }}\n connectionLineStyle={{ stroke: '#ddd', strokeWidth: 2 }}\n connectionLineType=\"bezier\"\n >\n {\n if (n.type === 'input') return 'blue';\n if (n.type === 'output') return 'green';\n if (n.type === 'default') return 'red';\n\n return '#FFCC00';\n }}\n />\n \n {this.state.graphLoaded && (\n \n \n \n \n
\n )}\n \n );\n }\n}\n\nexport default App;\n","import React from 'react';\nimport { render } from 'react-dom';\nimport { unstable_trace as trace } from \"scheduler/tracing\";\n\nimport SimpleGraph from './SimpleGraph';\n\ntrace('initial render', performance.now(), () =>\n render(
, document.getElementById('root'))\n);\n"]}
\ No newline at end of file
diff --git a/examples/build/example.e31bb0bc.css b/examples/build/example.e31bb0bc.css
new file mode 100644
index 00000000..62ef51a3
--- /dev/null
+++ b/examples/build/example.e31bb0bc.css
@@ -0,0 +1,163 @@
+.react-graph {
+ width: 100%;
+ height: 100%;
+ position: relative;
+ overflow: hidden;
+}
+
+.react-graph__renderer {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+}
+
+.react-graph__zoompane {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 1;
+}
+
+.react-graph__selectionpane {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 2;
+}
+
+.react-graph__selection {
+ position: absolute;
+ top: 0;
+ left: 0;
+ background: rgba(0, 89, 220, 0.08);
+ border: 1px dotted rgba(0, 89, 220, 0.8);
+}
+
+.react-graph__edges {
+ position: absolute;
+ top: 0;
+ left: 0;
+ pointer-events: none;
+ z-index: 2;
+}
+
+.react-graph__edge {
+ fill: none;
+ stroke: #bbb;
+ stroke-width: 2;
+ pointer-events: all;
+}
+
+.react-graph__edge.selected {
+ stroke: #555;
+ }
+
+.react-graph__edge.animated {
+ stroke-dasharray: 5;
+ -webkit-animation: dashdraw 0.5s linear infinite;
+ animation: dashdraw 0.5s linear infinite;
+ }
+
+.react-graph__edge.connection {
+ stroke: '#ddd';
+ pointer-events: none;
+ }
+
+@-webkit-keyframes dashdraw {
+ from {stroke-dashoffset: 10}
+}
+
+@keyframes dashdraw {
+ from {stroke-dashoffset: 10}
+}
+
+.react-graph__nodes {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ z-index: 3;
+ pointer-events: none;
+ transform-origin: 0 0;
+}
+
+.react-graph__node {
+ position: absolute;
+ color: #222;
+ font-family: sans-serif;
+ font-size: 12px;
+ text-align: center;
+ cursor: -webkit-grab;
+ cursor: grab;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ pointer-events: all;
+ transform-origin: 0 0;
+}
+
+.react-graph__node:hover > * {
+ box-shadow: 0 1px 5px 2px rgba(0, 0, 0, 0.08);
+ }
+
+.react-graph__node.selected > * {
+ box-shadow: 0 0 0 2px #555;
+ }
+
+.react-graph__handle {
+ position: absolute;
+ width: 10px;
+ height: 8px;
+ background: rgba(255, 255, 255, 0.4);
+ cursor: crosshair;
+}
+
+.react-graph__handle.bottom {
+ top: auto;
+ left: 50%;
+ bottom: 0;
+ transform: translate(-50%, 0);
+ }
+
+.react-graph__handle.top {
+ left: 50%;
+ top: 0;
+ transform: translate(-50%, 0);
+ }
+
+.react-graph__handle.left {
+ top: 50%;
+ left: 0;
+ transform: translate(0, -50%);
+
+ }
+
+.react-graph__handle.right {
+ right: 0;
+ top: 50%;
+ transform: translate(0, -50%);
+ }
+
+.react-graph__nodesselection {
+ z-index: 3;
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ transform-origin: left top;
+ pointer-events: none;
+}
+
+.react-graph__nodesselection-rect {
+ position: absolute;
+ background: rgba(0, 89, 220, 0.08);
+ border: 1px dotted rgba(0, 89, 220, 0.8);
+ pointer-events: all;
+ }
+
+/*# sourceMappingURL=/example.e31bb0bc.css.map */
\ No newline at end of file
diff --git a/examples/build/example.e31bb0bc.css.map b/examples/build/example.e31bb0bc.css.map
new file mode 100644
index 00000000..56b3d503
--- /dev/null
+++ b/examples/build/example.e31bb0bc.css.map
@@ -0,0 +1 @@
+{"version":3,"sources":["style.css"],"names":[],"mappings":"AAAA;EACE,WAAW;EACX,YAAY;EACZ,kBAAkB;EAClB,gBAAgB;AAClB;;AAEA;EACE,WAAW;EACX,YAAY;EACZ,kBAAkB;AACpB;;AAEA;EACE,WAAW;EACX,YAAY;EACZ,kBAAkB;EAClB,MAAM;EACN,OAAO;EACP,UAAU;AACZ;;AAEA;EACE,WAAW;EACX,YAAY;EACZ,kBAAkB;EAClB,MAAM;EACN,OAAO;EACP,UAAU;AACZ;;AAEA;EACE,kBAAkB;EAClB,MAAM;EACN,OAAO;EACP,kCAAkC;EAClC,wCAAwC;AAC1C;;AAEA;EACE,kBAAkB;EAClB,MAAM;EACN,OAAO;EACP,oBAAoB;EACpB,UAAU;AACZ;;AAEA;EACE,UAAU;EACV,YAAY;EACZ,eAAe;EACf,mBAAmB;AAerB;;AAbE;IACE,YAAY;EACd;;AAEA;IACE,mBAAmB;IACnB,gDAAwC;YAAxC,wCAAwC;EAC1C;;AAEA;IACE,cAAc;IACd,oBAAoB;EACtB;;AAGF;EACE,MAAM,qBAAqB;AAC7B;;AAFA;EACE,MAAM,qBAAqB;AAC7B;;AAEA;EACE,WAAW;EACX,YAAY;EACZ,kBAAkB;EAClB,UAAU;EACV,oBAAoB;EACpB,qBAAqB;AACvB;;AAEA;EACE,kBAAkB;EAClB,WAAW;EACX,uBAAuB;EACvB,eAAe;EACf,kBAAkB;EAClB,oBAAY;EAAZ,YAAY;EACZ,yBAAiB;KAAjB,sBAAiB;MAAjB,qBAAiB;UAAjB,iBAAiB;EACjB,mBAAmB;EACnB,qBAAqB;AASvB;;AAPE;IACE,6CAA6C;EAC/C;;AAEA;IACE,0BAA0B;EAC5B;;AAGF;EACE,kBAAkB;EAClB,WAAW;EACX,WAAW;EACX,oCAAoC;EACpC,iBAAiB;AA2BnB;;AAzBE;IACE,SAAS;IACT,SAAS;IACT,SAAS;IACT,6BAA6B;EAC/B;;AAEA;IACE,SAAS;IACT,MAAM;IACN,6BAA6B;EAC/B;;AAEA;IACE,QAAQ;IACR,OAAO;IACP,6BAA6B;;EAE/B;;AAEA;IACE,QAAQ;IACR,QAAQ;IACR,6BAA6B;EAC/B;;AAGF;EACE,UAAU;EACV,kBAAkB;EAClB,WAAW;EACX,YAAY;EACZ,MAAM;EACN,OAAO;EACP,0BAA0B;EAC1B,oBAAoB;AAQtB;;AANE;IACE,kBAAkB;IAClB,kCAAkC;IAClC,wCAAwC;IACxC,mBAAmB;EACrB","file":"example.e31bb0bc.css","sourceRoot":"..","sourcesContent":[".react-graph {\n width: 100%;\n height: 100%;\n position: relative;\n overflow: hidden;\n}\n\n.react-graph__renderer {\n width: 100%;\n height: 100%;\n position: absolute;\n}\n\n.react-graph__zoompane {\n width: 100%;\n height: 100%;\n position: absolute;\n top: 0;\n left: 0;\n z-index: 1;\n}\n\n.react-graph__selectionpane {\n width: 100%;\n height: 100%;\n position: absolute;\n top: 0;\n left: 0;\n z-index: 2;\n}\n\n.react-graph__selection {\n position: absolute;\n top: 0;\n left: 0;\n background: rgba(0, 89, 220, 0.08);\n border: 1px dotted rgba(0, 89, 220, 0.8);\n}\n\n.react-graph__edges {\n position: absolute;\n top: 0;\n left: 0;\n pointer-events: none;\n z-index: 2;\n}\n\n.react-graph__edge {\n fill: none;\n stroke: #bbb;\n stroke-width: 2;\n pointer-events: all;\n\n &.selected {\n stroke: #555;\n }\n\n &.animated {\n stroke-dasharray: 5;\n animation: dashdraw 0.5s linear infinite;\n }\n\n &.connection {\n stroke: '#ddd';\n pointer-events: none;\n }\n}\n\n@keyframes dashdraw {\n from {stroke-dashoffset: 10}\n}\n\n.react-graph__nodes {\n width: 100%;\n height: 100%;\n position: absolute;\n z-index: 3;\n pointer-events: none;\n transform-origin: 0 0;\n}\n\n.react-graph__node {\n position: absolute;\n color: #222;\n font-family: sans-serif;\n font-size: 12px;\n text-align: center;\n cursor: grab;\n user-select: none;\n pointer-events: all;\n transform-origin: 0 0;\n\n &:hover > * {\n box-shadow: 0 1px 5px 2px rgba(0, 0, 0, 0.08);\n }\n\n &.selected > * {\n box-shadow: 0 0 0 2px #555;\n }\n}\n\n.react-graph__handle {\n position: absolute;\n width: 10px;\n height: 8px;\n background: rgba(255, 255, 255, 0.4);\n cursor: crosshair;\n\n &.bottom {\n top: auto;\n left: 50%;\n bottom: 0;\n transform: translate(-50%, 0);\n }\n\n &.top {\n left: 50%;\n top: 0;\n transform: translate(-50%, 0);\n }\n\n &.left {\n top: 50%;\n left: 0;\n transform: translate(0, -50%);\n\n }\n\n &.right {\n right: 0;\n top: 50%;\n transform: translate(0, -50%);\n }\n}\n\n.react-graph__nodesselection {\n z-index: 3;\n position: absolute;\n width: 100%;\n height: 100%;\n top: 0;\n left: 0;\n transform-origin: left top;\n pointer-events: none;\n\n &-rect {\n position: absolute;\n background: rgba(0, 89, 220, 0.08);\n border: 1px dotted rgba(0, 89, 220, 0.8);\n pointer-events: all;\n }\n}"]}
\ No newline at end of file
diff --git a/examples/build/example.e31bb0bc.js b/examples/build/example.e31bb0bc.js
new file mode 100644
index 00000000..95da066c
--- /dev/null
+++ b/examples/build/example.e31bb0bc.js
@@ -0,0 +1,46850 @@
+// modules are defined as an array
+// [ module function, map of requires ]
+//
+// map of requires is short require name -> numeric require
+//
+// anything defined in a previous bundle is accessed via the
+// orig method which is the require for previous bundles
+parcelRequire = (function (modules, cache, entry, globalName) {
+ // Save the require from previous bundle to this closure if any
+ var previousRequire = typeof parcelRequire === 'function' && parcelRequire;
+ var nodeRequire = typeof require === 'function' && require;
+
+ function newRequire(name, jumped) {
+ if (!cache[name]) {
+ if (!modules[name]) {
+ // if we cannot find the module within our internal map or
+ // cache jump to the current global require ie. the last bundle
+ // that was added to the page.
+ var currentRequire = typeof parcelRequire === 'function' && parcelRequire;
+ if (!jumped && currentRequire) {
+ return currentRequire(name, true);
+ }
+
+ // If there are other bundles on this page the require from the
+ // previous one is saved to 'previousRequire'. Repeat this as
+ // many times as there are bundles until the module is found or
+ // we exhaust the require chain.
+ if (previousRequire) {
+ return previousRequire(name, true);
+ }
+
+ // Try the node require function if it exists.
+ if (nodeRequire && typeof name === 'string') {
+ return nodeRequire(name);
+ }
+
+ var err = new Error('Cannot find module \'' + name + '\'');
+ err.code = 'MODULE_NOT_FOUND';
+ throw err;
+ }
+
+ localRequire.resolve = resolve;
+ localRequire.cache = {};
+
+ var module = cache[name] = new newRequire.Module(name);
+
+ modules[name][0].call(module.exports, localRequire, module, module.exports, this);
+ }
+
+ return cache[name].exports;
+
+ function localRequire(x){
+ return newRequire(localRequire.resolve(x));
+ }
+
+ function resolve(x){
+ return modules[name][1][x] || x;
+ }
+ }
+
+ function Module(moduleName) {
+ this.id = moduleName;
+ this.bundle = newRequire;
+ this.exports = {};
+ }
+
+ newRequire.isParcelRequire = true;
+ newRequire.Module = Module;
+ newRequire.modules = modules;
+ newRequire.cache = cache;
+ newRequire.parent = previousRequire;
+ newRequire.register = function (id, exports) {
+ modules[id] = [function (require, module) {
+ module.exports = exports;
+ }, {}];
+ };
+
+ var error;
+ for (var i = 0; i < entry.length; i++) {
+ try {
+ newRequire(entry[i]);
+ } catch (e) {
+ // Save first error but execute all entries
+ if (!error) {
+ error = e;
+ }
+ }
+ }
+
+ if (entry.length) {
+ // Expose entry point to Node, AMD or browser globals
+ // Based on https://github.com/ForbesLindesay/umd/blob/master/template.js
+ var mainExports = newRequire(entry[entry.length - 1]);
+
+ // CommonJS
+ if (typeof exports === "object" && typeof module !== "undefined") {
+ module.exports = mainExports;
+
+ // RequireJS
+ } else if (typeof define === "function" && define.amd) {
+ define(function () {
+ return mainExports;
+ });
+
+ //
+