diff --git a/README.md b/README.md index 05cc94fe..1d6e72c7 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ cytoscape-edgehandles ## Description -This extension creates handles on nodes that can be dragged to create edges between nodes ([demo](https://cytoscape.github.io/cytoscape.js-edgehandles/), [snapping demo](https://cytoscape.github.io/cytoscape.js-edgehandles/demo-snap.html), [compound demo](https://cytoscape.github.io/cytoscape.js-edgehandles/demo-compound.html), [compound snapping demo](https://cytoscape.github.io/cytoscape.js-edgehandles/demo-compound-snap.html)) +This extension creates handles on nodes that can be dragged to create edges between nodes ([demo](https://cytoscape.github.io/cytoscape.js-edgehandles/), [demo (snapping disabled)](https://cytoscape.github.io/cytoscape.js-edgehandles/demo-snap.html), [compound demo](https://cytoscape.github.io/cytoscape.js-edgehandles/demo-compound.html), [compound demo (snapping disabled)](https://cytoscape.github.io/cytoscape.js-edgehandles/demo-compound-snap.html)) ## Dependencies @@ -68,80 +68,21 @@ let cy = cytoscape({ // the default values of each option are outlined below: let defaults = { - preview: true, // whether to show added edges preview before releasing selection - hoverDelay: 150, // time spent hovering over a target node before it is considered selected - handleNodes: 'node', // selector/filter function for whether edges can be made from a given node - snap: false, // when enabled, the edge can be drawn by just moving close to a target node - snapThreshold: 50, // the target node must be less than or equal to this many pixels away from the cursor/finger - snapFrequency: 15, // the number of times per second (Hz) that snap checks done (lower is less expensive) - noEdgeEventsInDraw: false, // set events:no to edges during draws, prevents mouseouts on compounds - disableBrowserGestures: true, // during an edge drawing gesture, disable browser gestures such as two-finger trackpad swipe and pinch-to-zoom - handlePosition: function( node ){ - return 'middle top'; // sets the position of the handle in the format of "X-AXIS Y-AXIS" such as "left top", "middle top" - }, - handleInDrawMode: false, // whether to show the handle in draw mode - edgeType: function( sourceNode, targetNode ){ - // can return 'flat' for flat edges between nodes or 'node' for intermediate node between them - // returning null/undefined means an edge can't be added between the two nodes - return 'flat'; - }, - loopAllowed: function( node ){ - // for the specified node, return whether edges from itself to itself are allowed - return false; - }, - nodeLoopOffset: -50, // offset for edgeType: 'node' loops - nodeParams: function( sourceNode, targetNode ){ - // for edges between the specified source and target - // return element object to be passed to cy.add() for intermediary node - return {}; + canConnect: function( sourceNode, targetNode ){ + // whether an edge can be created between source and target + return !sourceNode.same(targetNode); // e.g. disallow loops }, - edgeParams: function( sourceNode, targetNode, i ){ + edgeParams: function( sourceNode, targetNode ){ // for edges between the specified source and target // return element object to be passed to cy.add() for edge - // NB: i indicates edge index in case of edgeType: 'node' - return {}; - }, - ghostEdgeParams: function(){ - // return element object to be passed to cy.add() for the ghost edge - // (default classes are always added for you) return {}; }, - show: function( sourceNode ){ - // fired when handle is shown - }, - hide: function( sourceNode ){ - // fired when the handle is hidden - }, - start: function( sourceNode ){ - // fired when edgehandles interaction starts (drag on handle) - }, - complete: function( sourceNode, targetNode, addedEles ){ - // fired when edgehandles is done and elements are added - }, - stop: function( sourceNode ){ - // fired when edgehandles interaction is stopped (either complete with added edges or incomplete) - }, - cancel: function( sourceNode, cancelledTargets ){ - // fired when edgehandles are cancelled (incomplete gesture) - }, - hoverover: function( sourceNode, targetNode ){ - // fired when a target is hovered - }, - hoverout: function( sourceNode, targetNode ){ - // fired when a target isn't hovered anymore - }, - previewon: function( sourceNode, targetNode, previewEles ){ - // fired when preview is shown - }, - previewoff: function( sourceNode, targetNode, previewEles ){ - // fired when preview is hidden - }, - drawon: function(){ - // fired when draw mode enabled - }, - drawoff: function(){ - // fired when draw mode disabled - } + hoverDelay: 150, // time spent hovering over a target node before it is considered selected + snap: true, // when enabled, the edge can be drawn by just moving close to a target node (can be confusing on compound graphs) + snapThreshold: 50, // the target node must be less than or equal to this many pixels away from the cursor/finger + snapFrequency: 15, // the number of times per second (Hz) that snap checks done (lower is less expensive) + noEdgeEventsInDraw: true, // set events:no to edges during draws, prevents mouseouts on compounds + disableBrowserGestures: true // during an edge drawing gesture, disable browser gestures such as two-finger trackpad swipe and pinch-to-zoom }; let eh = cy.edgehandles( defaults ); @@ -155,7 +96,6 @@ The object returned by `cy.edgehandles()` has several functions available on it: * `start( sourceNode )` : manually start the gesture (as if the handle were already held) * `stop()` : manually completes or cancels the gesture -* `hide()` : remove the handle node from the graph * `disable()` : disables edgehandles behaviour * `enable()` : enables edgehandles behaviour * `enableDrawMode()` : turn on draw mode (the entire node body acts like the handle) @@ -167,13 +107,12 @@ The object returned by `cy.edgehandles()` has several functions available on it: These classes can be used for styling the graph as it interacts with the extension: -* `eh-handle` : The handle node * `eh-source` : The source node * `eh-target` : A target node -* `eh-preview` : Preview elements (used with `options.preview: true`) +* `eh-preview` : Preview edges (i.e. shown before releasing the mouse button and the edge creation is confirmed) * `eh-hover` : Added to nodes as they are hovered over as targets -* `eh-ghost-node` : The ghost node (target) -* `eh-ghost-edge` : The ghost handle line edge +* `eh-ghost-node` : The ghost node (target), used when the cursor isn't pointed at a target node yet (i.e. in place of a target node) +* `eh-ghost-edge` : The ghost handle line edge, used when the cursor isn't pointed at a target node yet (i.e. the edge is pointing to empty space) * `eh-ghost` : A ghost element * `eh-presumptive-target` : A node that, during an edge drag, may become a target when released * `eh-preview-active` : Applied to the source, target, and ghost edge when the preview is active @@ -183,17 +122,11 @@ These classes can be used for styling the graph as it interacts with the extensi During the course of a user's interaction with the extension, several events are generated and triggered on the core. Each event callback has a number of extra parameters, and certain events have associated positions. -* `ehshow` : when the handle is shown - * `(event, sourceNode)` - * `event.position` : handle position -* `ehhide` : when the handle is hidden - * `(event, sourceNode)` - * `event.position` : handle position * `ehstart` : when the edge creation gesture starts * `(event, sourceNode)` * `event.position` : handle position * `ehcomplete` : when the edge is created - * `(event, sourceNode, targetNode, addedEles)` + * `(event, sourceNode, targetNode, addedEdge)` * `event.position` : cursor/finger position * `ehstop` : when the edge creation gesture is stopped (either successfully completed or cancelled) * `(event, sourceNode)` @@ -208,10 +141,10 @@ During the course of a user's interaction with the extension, several events are * `(event, sourceNode, targetNode)` * `event.position` : cursor/finger position * `ehpreviewon` : when a preview is shown - * `(event, sourceNode, targetNode, previewEles)` + * `(event, sourceNode, targetNode, previewEdge)` * `event.position` : cursor/finger position * `ehpreviewoff` : when the preview is removed - * `(event, sourceNode, targetNode, previewEles)` + * `(event, sourceNode, targetNode, previewEdge)` * `event.position` : cursor/finger position * `ehdrawon` : when draw mode is enabled * `(event)` @@ -221,7 +154,7 @@ During the course of a user's interaction with the extension, several events are Example binding: ```js -cy.on('ehcomplete', (event, sourceNode, targetNode, addedEles) => { +cy.on('ehcomplete', (event, sourceNode, targetNode, addedEdge) => { let { position } = event; // ... diff --git a/demo-compound-snap.html b/demo-compound-snap.html index 847c3c02..dc001081 100644 --- a/demo-compound-snap.html +++ b/demo-compound-snap.html @@ -39,8 +39,9 @@ #buttons { position: absolute; right: 0; - bottom: 0; + top: 0; z-index: 99999; + margin: 1em; } @@ -1289,8 +1290,7 @@ }); var eh = cy.edgehandles({ - noEdgeEventsInDraw: true, - snap: true + snap: false }); document.querySelector('#draw-on').addEventListener('click', function() { @@ -1310,7 +1310,7 @@ -

cytoscape-edgehandles compound snapping demo

+

cytoscape-edgehandles compound (snapping disabled)

diff --git a/demo-compound.html b/demo-compound.html index b5dededa..4c5a2857 100644 --- a/demo-compound.html +++ b/demo-compound.html @@ -39,8 +39,9 @@ #buttons { position: absolute; right: 0; - bottom: 0; + top: 0; z-index: 99999; + margin: 1em; } @@ -1122,7 +1123,6 @@ }); var eh = cy.edgehandles({ - noEdgeEventsInDraw: true }); document.querySelector('#draw-on').addEventListener('click', function() { diff --git a/demo-snap.html b/demo-snap.html index d8286e4d..4c303632 100644 --- a/demo-snap.html +++ b/demo-snap.html @@ -39,8 +39,9 @@ #buttons { position: absolute; right: 0; - bottom: 0; + top: 0; z-index: 99999; + margin: 1em; } @@ -151,7 +152,7 @@ }); var eh = cy.edgehandles({ - snap: true + snap: false }); document.querySelector('#draw-on').addEventListener('click', function() { @@ -171,7 +172,7 @@ -

cytoscape-edgehandles snapping demo

+

cytoscape-edgehandles (snapping disabled)

diff --git a/demo.html b/demo.html index 63d166ae..beb73313 100644 --- a/demo.html +++ b/demo.html @@ -15,6 +15,10 @@ + + + + @@ -163,6 +176,101 @@ eh.start( cy.$('node:selected') ); }); + var popperEnabled = false; + + document.querySelector('#popper').addEventListener('click', function() { + if (popperEnabled) { return; } + + popperEnabled = true; + + // example code for making your own handles -- customise events and presentation where fitting + // var popper; + var popperNode; + var popper; + var popperDiv; + var started = false; + + function start() { + eh.start(popperNode); + } + + function stop() { + eh.stop(); + } + + function setHandleOn(node) { + if (started) { return; } + + removeHandle(); // rm old handle + + popperNode = node; + + popperDiv = document.createElement('div'); + popperDiv.classList.add('popper-handle'); + popperDiv.addEventListener('mousedown', start); + document.body.appendChild(popperDiv); + + popper = node.popper({ + content: popperDiv, + popper: { + placement: 'top', + modifiers: [ + { + name: 'offset', + options: { + offset: [0, -10], + }, + }, + ] + } + }); + } + + function removeHandle() { + if (popper){ + popper.destroy(); + popper = null; + } + + if (popperDiv) { + document.body.removeChild(popperDiv); + popperDiv = null; + } + + popperNode = null; + } + + cy.on('mouseover', 'node', function(e) { + setHandleOn(e.target); + }); + + cy.on('grab', 'node', function(){ + removeHandle(); + }); + + cy.on('tap', function(e){ + if (e.target === cy) { + removeHandle(); + } + }); + + cy.on('zoom pan', function(){ + removeHandle(); + }); + + window.addEventListener('mouseup', function(e){ + stop(); + }); + + cy.on('ehstart', function(){ + started = true; + }); + + cy.on('ehstop', function(){ + started = false; + }); + }); + }); @@ -176,6 +284,7 @@

cytoscape-edgehandles demo

+ diff --git a/package-lock.json b/package-lock.json index 7c6881c7..58d70dbb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2522,7 +2522,7 @@ "kind-of": "^3.0.3", "lazy-cache": "^2.0.1", "os-homedir": "^1.0.1", - "resolve-file": "github:jonschlinkert/resolve-file#261082c95a5f407c43d82797c13bae3527462842" + "resolve-file": "resolve-file@github:jonschlinkert/resolve-file#261082c95a5f407c43d82797c13bae3527462842" }, "dependencies": { "cwd": { @@ -5720,7 +5720,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -5741,12 +5742,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5761,17 +5764,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -5888,7 +5894,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -5900,6 +5907,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5914,6 +5922,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -5921,12 +5930,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -5945,6 +5956,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -6025,7 +6037,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -6037,6 +6050,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -6122,7 +6136,8 @@ "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -6158,6 +6173,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -6177,6 +6193,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -6220,12 +6237,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, diff --git a/src/edgehandles/cy-listeners.js b/src/edgehandles/cy-listeners.js index 19bd3e74..6f1887bc 100644 --- a/src/edgehandles/cy-listeners.js +++ b/src/edgehandles/cy-listeners.js @@ -5,39 +5,14 @@ function addCytoscapeListeners(){ this.addListener( cy, 'drag', () => this.grabbingNode = true ); this.addListener( cy, 'free', () => this.grabbingNode = false ); - // show handle on hover - this.addListener( cy, 'mouseover', 'node', e => { - this.show( e.target ); - } ); - - // hide handle on tap handle - this.addListener( cy, 'tap', 'node', e => { - let node = e.target; - - if( !node.same( this.handleNode ) ){ - this.show( node ); - } - } ); - - // hide handle when source node moved - this.addListener( cy, 'position', 'node', e => { - if( e.target.same( this.sourceNode ) ){ - this.hide(); - } - } ); - // start on tapstart handle // start on tapstart node (draw mode) // toggle on source node this.addListener( cy, 'tapstart', 'node', e => { let node = e.target; - if( node.same( this.handleNode ) ){ - this.start( this.sourceNode ); - } else if( this.drawMode ){ + if( this.drawMode ){ this.start( node ); - } else if( node.same( this.sourceNode ) ){ - this.hide(); } } ); @@ -69,13 +44,6 @@ function addCytoscapeListeners(){ this.stop(); } ); - // hide handle if source node is removed - this.addListener( cy, 'remove', e => { - if( e.target.same( this.sourceNode ) ){ - this.hide(); - } - } ); - return this; } diff --git a/src/edgehandles/defaults.js b/src/edgehandles/defaults.js index 8d3cd646..b836e04f 100644 --- a/src/edgehandles/defaults.js +++ b/src/edgehandles/defaults.js @@ -1,79 +1,20 @@ /* eslint-disable no-unused-vars */ let defaults = { - preview: true, // whether to show added edges preview before releasing selection - hoverDelay: 150, // time spent hovering over a target node before it is considered selected - handleNodes: 'node', // selector/filter function for whether edges can be made from a given node - snap: false, // when enabled, the edge can be drawn by just moving close to a target node (can be confusing on compound graphs) - snapThreshold: 50, // the target node must be less than or equal to this many pixels away from the cursor/finger - snapFrequency: 15, // the number of times per second (Hz) that snap checks done (lower is less expensive) - noEdgeEventsInDraw: false, // set events:no to edges during draws, prevents mouseouts on compounds - disableBrowserGestures: true, // during an edge drawing gesture, disable browser gestures such as two-finger trackpad swipe and pinch-to-zoom - handlePosition: function( node ){ - return 'middle top'; // sets the position of the handle in the format of "X-AXIS Y-AXIS" such as "left top", "middle top" - }, - handleInDrawMode: false, // whether to show the handle in draw mode - edgeType: function( sourceNode, targetNode ){ - // can return 'flat' for flat edges between nodes or 'node' for intermediate node between them - // returning null/undefined means an edge can't be added between the two nodes - return 'flat'; - }, - loopAllowed: function( node ){ - // for the specified node, return whether edges from itself to itself are allowed - return false; - }, - nodeLoopOffset: -50, // offset for edgeType: 'node' loops - nodeParams: function( sourceNode, targetNode ){ - // for edges between the specified source and target - // return element object to be passed to cy.add() for intermediary node - return {}; + canConnect: function( sourceNode, targetNode ){ + // whether an edge can be created between source and target + return !sourceNode.same(targetNode); // e.g. disallow loops }, - edgeParams: function( sourceNode, targetNode, i ){ + edgeParams: function( sourceNode, targetNode ){ // for edges between the specified source and target // return element object to be passed to cy.add() for edge - // NB: i indicates edge index in case of edgeType: 'node' - return {}; - }, - ghostEdgeParams: function(){ - // return element object to be passed to cy.add() for the ghost edge - // (default classes are always added for you) return {}; }, - show: function( sourceNode ){ - // fired when handle is shown - }, - hide: function( sourceNode ){ - // fired when the handle is hidden - }, - start: function( sourceNode ){ - // fired when edgehandles interaction starts (drag on handle) - }, - complete: function( sourceNode, targetNode, addedEles ){ - // fired when edgehandles is done and elements are added - }, - stop: function( sourceNode ){ - // fired when edgehandles interaction is stopped (either complete with added edges or incomplete) - }, - cancel: function( sourceNode, cancelledTargets ){ - // fired when edgehandles are cancelled (incomplete gesture) - }, - hoverover: function( sourceNode, targetNode ){ - // fired when a target is hovered - }, - hoverout: function( sourceNode, targetNode ){ - // fired when a target isn't hovered anymore - }, - previewon: function( sourceNode, targetNode, previewEles ){ - // fired when preview is shown - }, - previewoff: function( sourceNode, targetNode, previewEles ){ - // fired when preview is hidden - }, - drawon: function(){ - // fired when draw mode enabled - }, - drawoff: function(){ - // fired when draw mode disabled - } + hoverDelay: 150, // time spent hovering over a target node before it is considered selected + snap: true, // when enabled, the edge can be drawn by just moving close to a target node (can be confusing on compound graphs) + snapThreshold: 50, // the target node must be less than or equal to this many pixels away from the cursor/finger + snapFrequency: 15, // the number of times per second (Hz) that snap checks done (lower is less expensive) + noEdgeEventsInDraw: true, // set events:no to edges during draws, prevents mouseouts on compounds + disableBrowserGestures: true // during an edge drawing gesture, disable browser gestures such as two-finger trackpad swipe and pinch-to-zoom }; /* eslint-enable */ diff --git a/src/edgehandles/draw-mode.js b/src/edgehandles/draw-mode.js index dec21694..812b3c76 100644 --- a/src/edgehandles/draw-mode.js +++ b/src/edgehandles/draw-mode.js @@ -1,5 +1,5 @@ function toggleDrawMode( bool ){ - let { cy, options } = this; + let { cy } = this; this.drawMode = bool != null ? bool : !this.drawMode; @@ -8,10 +8,6 @@ function toggleDrawMode( bool ){ cy.autoungrabify( true ); - if( !options.handleInDrawMode && this.handleShown() ){ - this.hide(); - } - this.emit('drawon'); } else { cy.autoungrabify( this.prevUngrabifyState ); diff --git a/src/edgehandles/drawing.js b/src/edgehandles/drawing.js index dd38f0bc..ac56b7d7 100644 --- a/src/edgehandles/drawing.js +++ b/src/edgehandles/drawing.js @@ -29,16 +29,13 @@ function makeEdges( preview = false ) { let target = this.targetNode; let classes = preview ? 'eh-preview' : ''; let added = cy.collection(); - let edgeType = options.edgeType( source, target ); + let canConnect = this.canConnect(target); // can't make edges outside of regular gesture lifecycle if( !active ){ return; } - // must have a non-empty edge type - if( !edgeType ){ return; } - - // can't make preview if disabled - if( preview && !options.preview ){ return; } + // must be able to connect + if( !canConnect ){ return; } // detect cancel if( !target || target.size() === 0 ){ @@ -50,7 +47,7 @@ function makeEdges( preview = false ) { } // just remove preview class if we already have the edges - if( !preview && options.preview ) { + if( !preview ) { previewEles.removeClass('eh-preview').style('events', ''); this.emit( 'complete', this.mp(), source, target, previewEles ); @@ -58,80 +55,21 @@ function makeEdges( preview = false ) { return; } - let p1 = source.position(); - let p2 = target.position(); - - let p; - if( source.same( target ) ) { - p = { - x: p1.x + options.nodeLoopOffset, - y: p1.y + options.nodeLoopOffset - }; - } else { - p = { - x: ( p1.x + p2.x ) / 2, - y: ( p1.y + p2.y ) / 2 - }; - } + let source2target = cy.add( + getEleJson( + { + group: 'edges', + data: { + source: source.id(), + target: target.id() + } + }, + this.edgeParams( target ), + classes + ) + ); - if( edgeType === 'node' ){ - let interNode = cy.add( - getEleJson( - { - group: 'nodes', - position: p - }, - options.nodeParams( source, target ), - classes - ) - ); - - let source2inter = cy.add( - getEleJson( - { - group: 'edges', - data: { - source: source.id(), - target: interNode.id() - } - }, - options.edgeParams( source, target, 0 ), - classes - ) - ); - - let inter2target = cy.add( - getEleJson( - { - group: 'edges', - data: { - source: interNode.id(), - target: target.id() - } - }, - options.edgeParams( source, target, 1 ), - classes - ) - ); - - added = added.merge( interNode ).merge( source2inter ).merge( inter2target ); - } else { // flat - let source2target = cy.add( - getEleJson( - { - group: 'edges', - data: { - source: source.id(), - target: target.id() - } - }, - options.edgeParams( source, target, 0 ), - classes - ) - ); - - added = added.merge( source2target ); - } + added = added.merge( source2target ); if( preview ) { this.previewEles = added; @@ -164,72 +102,8 @@ function removePreview() { return this; } -function handleShown(){ - return this.handleNode.nonempty() && this.handleNode.inside(); -} - -function removeHandle(){ - if( this.handleShown() ){ - this.handleNode.remove(); - } - - return this; -} - -function setHandleFor( node ){ - let { options, cy } = this; - - let handlePosition = typeof options.handlePosition === typeof '' ? () => options.handlePosition : options.handlePosition; - - let p = node.position(); - let h = node.outerHeight(); - let w = node.outerWidth(); - - // store how much we should move the handle from origin(p.x, p.y) - let moveX = 0; - let moveY = 0; - - // grab axes - let axes = handlePosition( node ).toLowerCase().split(/\s+/); - let axisX = axes[0]; - let axisY = axes[1]; - - // based on handlePosition move left/right/top/bottom. Middle/middle will just be normal - if( axisX === 'left' ){ - moveX = -(w / 2); - } else if( axisX === 'right' ){ - moveX = w / 2; - } if( axisY === 'top' ){ - moveY = -(h / 2); - } else if( axisY === 'bottom' ){ - moveY = h / 2; - } - - // set handle x and y based on adjusted positions - let hx = this.hx = p.x + moveX; - let hy = this.hy = p.y + moveY; - let pos = { x: hx, y: hy }; - - if( this.handleShown() ){ - this.handleNode.position( pos ); - } else { - cy.batch( () => { - this.handleNode = cy.add({ - classes: 'eh-handle', - position: pos, - grabbable: false, - selectable: false - }); - - this.handleNode.style('z-index', 9007199254740991); - } ); - } - - return this; -} - function updateEdge() { - let { sourceNode, ghostNode, cy, mx, my, options } = this; + let { sourceNode, ghostNode, cy, mx, my } = this; let x = mx; let y = my; let ghostEdge, ghostEles; @@ -258,7 +132,7 @@ function updateEdge() { 'events': 'no' }); - let ghostEdgeParams = options.ghostEdgeParams(); + let ghostEdgeParams = {}; ghostEdge = cy.add( assign({}, ghostEdgeParams, { group: 'edges', @@ -284,6 +158,5 @@ function updateEdge() { module.exports = { makeEdges, makePreview, removePreview, previewShown, - updateEdge, - handleShown, setHandleFor, removeHandle + updateEdge }; diff --git a/src/edgehandles/gesture-lifecycle.js b/src/edgehandles/gesture-lifecycle.js index 587d81f9..2327182e 100644 --- a/src/edgehandles/gesture-lifecycle.js +++ b/src/edgehandles/gesture-lifecycle.js @@ -2,18 +2,16 @@ const memoize = require('lodash.memoize'); const sqrt2 = Math.sqrt(2); function canStartOn( node ){ - const { options, previewEles, ghostEles, handleNode } = this; + const { previewEles, ghostEles } = this; const isPreview = el => previewEles.anySame(el); const isGhost = el => ghostEles.anySame(el); - const userFilter = el => el.filter( options.handleNodes ).length > 0; - const isHandle = el => handleNode.same(el); - const isTemp = el => isPreview(el) || isHandle(el) || isGhost(el); + const isTemp = el => isPreview(el) || isGhost(el); const { enabled, active, grabbingNode } = this; return ( enabled && !active && !grabbingNode - && ( node == null || (!isTemp(node) && userFilter(node)) ) + && node != null && node.nonempty() && !isTemp(node) ); } @@ -25,28 +23,6 @@ function canStartNonDrawModeOn( node ){ return this.canStartOn( node ) && !this.drawMode; } -function show( node ){ - let { options, drawMode } = this; - - if( !this.canStartOn(node) || ( drawMode && !options.handleInDrawMode ) ){ return; } - - this.sourceNode = node; - - this.setHandleFor( node ); - - this.emit( 'show', this.hp(), this.sourceNode ); - - return this; -} - -function hide(){ - this.removeHandle(); - - this.emit( 'hide', this.hp(), this.sourceNode ); - - return this; -} - function start( node ){ if( !this.canStartOn(node) ){ return; } @@ -58,6 +34,16 @@ function start( node ){ this.disableGestures(); this.disableEdgeEvents(); + const getId = n => n.id(); + + this.canConnect = memoize(target => { + return this.options.canConnect(this.sourceNode, target); + }, getId); + + this.edgeParams = memoize(target => { + return this.options.edgeParams(this.sourceNode, target); + }, getId); + this.emit( 'start', this.hp(), node ); } @@ -82,7 +68,7 @@ function snap(){ let tgt = this.targetNode; let threshold = this.options.snapThreshold; let mousePos = this.mp(); - let { handleNode, previewEles, ghostNode } = this; + let { previewEles, ghostNode } = this; let radius = n => sqrt2 * Math.max(n.outerWidth(), n.outerHeight())/2; // worst-case enclosure of bb by circle let sqDist = (x1, y1, x2, y2) => { let dx = x2 - x1; let dy = y2 - y1; return dx*dx + dy*dy; }; @@ -159,7 +145,7 @@ function snap(){ ); }; - let isEhEle = n => n.same(handleNode) || n.same(previewEles) || n.same(ghostNode); + let isEhEle = n => n.same(previewEles) || n.same(ghostNode); let nodesByDist = cy.nodes(n => !isEhEle(n) && isWithinThreshold(n)).sort(cmp); let snapped = false; @@ -189,16 +175,12 @@ function snap(){ function preview( target, allowHoverDelay = true ){ let { options, sourceNode, ghostNode, ghostEles, presumptiveTargets, previewEles, active } = this; let source = sourceNode; - let isLoop = target.same( source ); - let loopAllowed = options.loopAllowed( target ); let isGhost = target.same( ghostNode ); - let noEdge = !options.edgeType( source, target ); - let isHandle = target.same( this.handleNode ); + let noEdge = !this.canConnect( target ); let isExistingTgt = target.same( this.targetNode ); if( - !active || isHandle || isGhost || noEdge || isExistingTgt - || (isLoop && !loopAllowed) + !active || isGhost || noEdge || isExistingTgt // || (target.isParent()) ){ return false; @@ -220,17 +202,15 @@ function preview( target, allowHoverDelay = true ){ this.emit( 'hoverover', this.mp(), source, target ); - if( options.preview ){ - target.addClass('eh-preview'); + target.addClass('eh-preview'); - ghostEles.addClass('eh-preview-active'); - sourceNode.addClass('eh-preview-active'); - target.addClass('eh-preview-active'); + ghostEles.addClass('eh-preview-active'); + sourceNode.addClass('eh-preview-active'); + target.addClass('eh-preview-active'); - this.makePreview(); + this.makePreview(); - this.emit( 'previewon', this.mp(), source, target, previewEles ); - } + this.emit( 'previewon', this.mp(), source, target, previewEles ); }; if( allowHoverDelay && options.hoverDelay > 0 ){ @@ -243,7 +223,7 @@ function preview( target, allowHoverDelay = true ){ } function unpreview( target ) { - if( !this.active || target.same( this.handleNode ) ){ return; } + if( !this.active ){ return; } let { previewTimeout, sourceNode, previewEles, ghostEles, cy } = this; clearTimeout( previewTimeout ); @@ -278,8 +258,6 @@ function stop(){ this.makeEdges(); - this.removeHandle(); - ghostEles.remove(); this.clearCollections(); @@ -295,6 +273,6 @@ function stop(){ } module.exports = { - show, hide, start, update, preview, unpreview, stop, snap, + start, update, preview, unpreview, stop, snap, canStartOn, canStartDrawModeOn, canStartNonDrawModeOn }; diff --git a/src/edgehandles/index.js b/src/edgehandles/index.js index dde88ca4..5b8ee421 100644 --- a/src/edgehandles/index.js +++ b/src/edgehandles/index.js @@ -24,14 +24,8 @@ function Edgehandles( options ){ this.grabbingNode = false; // edgehandles elements - this.handleNode = cy.collection(); this.clearCollections(); - // handle - this.hx = 0; - this.hy = 0; - this.hr = 0; - // mouse position this.mx = 0; this.my = 0; @@ -54,7 +48,9 @@ function Edgehandles( options ){ } ); window.addEventListener( 'test', null, opts ); - } catch( err ){} + } catch( err ){ + // swallow + } if( supportsPassive ){ this.windowListenerOptions = { capture: true, passive: false }; diff --git a/src/edgehandles/listeners.js b/src/edgehandles/listeners.js index 32d8cfd4..d0b004cd 100644 --- a/src/edgehandles/listeners.js +++ b/src/edgehandles/listeners.js @@ -84,16 +84,10 @@ function removeListener( target, event, selector, callback, options ){ } function emit( type, position, ...args ){ - let { options, cy } = this; + let { cy } = this; cy.emit( { type: `eh${type}`, position }, args ); - let handler = options[ type ]; - - if( handler != null ){ - handler( ...args ); - } - return this; }