├── .babelrc ├── .eslintignore ├── .eslintrc ├── .github └── stale.yml ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── bower.json ├── cytoscape-edgehandles.js ├── demo-compound-snap.html ├── demo-compound.html ├── demo-snap.html ├── demo.html ├── img └── preview.png ├── package-lock.json ├── package.json ├── pages ├── cytoscape-edgehandles.js ├── demo-compound-snap.html ├── demo-compound.html ├── demo-snap.html ├── demo.html └── index.html ├── src ├── assign.js ├── core.js ├── edgehandles │ ├── cy-gestures-toggle.js │ ├── cy-listeners.js │ ├── defaults.js │ ├── draw-mode.js │ ├── drawing.js │ ├── edge-events-toggle.js │ ├── enabling.js │ ├── gesture-lifecycle.js │ ├── index.js │ └── listeners.js └── index.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"] 3 | } 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true, 5 | "node": true, 6 | "amd": true, 7 | "es6": true 8 | }, 9 | "extends": "eslint:recommended", 10 | "rules": { 11 | "semi": "error" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 14 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | # Label to use when marking an issue as stale 9 | staleLabel: stale 10 | # Comment to post when marking an issue as stale. Set to `false` to disable 11 | markComment: > 12 | This issue has been automatically marked as stale, because it has not had 13 | activity within the past 30 days. It will be closed if no further activity 14 | occurs within the next 30 days. If a feature request is important to you, 15 | please consider making a pull request. Thank you for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | package-lock.json 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | 3 | Copyright (c) 2016-2019, 2021, The Cytoscape Consortium. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the “Software”), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | cytoscape-edgehandles 2 | ================================================================================ 3 | 4 | [](https://zenodo.org/badge/latestdoi/16078488) 5 | 6 |  7 | 8 | ## Description 9 | 10 | 11 | This extension allows for drawing 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)) 12 | 13 | You can use [Popper](https://github.com/cytoscape/cytoscape.js-popper) to create your own handles, as shown in the [demo](https://cytoscape.github.io/cytoscape.js-edgehandles/). 14 | 15 | 16 | ## Dependencies 17 | 18 | * Cytoscape.js 3.x, >= 3.2.0 19 | * Lodash 4.x, >= 4.1 20 | * memoize 21 | * throttle 22 | 23 | 24 | ## Usage instructions 25 | 26 | Download the library: 27 | * via npm: `npm install cytoscape-edgehandles`, 28 | * via bower: `bower install cytoscape-edgehandles`, or 29 | * via direct download in the repository (probably from a tag). 30 | 31 | Import the library as appropriate for your project: 32 | 33 | ES import: 34 | 35 | ```js 36 | import cytoscape from 'cytoscape'; 37 | import edgehandles from 'cytoscape-edgehandles'; 38 | 39 | cytoscape.use( edgehandles ); 40 | ``` 41 | 42 | CommonJS require: 43 | 44 | ```js 45 | let cytoscape = require('cytoscape'); 46 | let edgehandles = require('cytoscape-edgehandles'); 47 | 48 | cytoscape.use( edgehandles ); // register extension 49 | ``` 50 | 51 | AMD: 52 | 53 | ```js 54 | require(['cytoscape', 'cytoscape-edgehandles'], function( cytoscape, edgehandles ){ 55 | edgehandles( cytoscape ); // register extension 56 | }); 57 | ``` 58 | 59 | Plain HTML/JS has the extension registered for you automatically, because no `require()` is needed. 60 | 61 | 62 | 63 | ## Initialisation 64 | 65 | You initialise the extension on the Cytoscape instance: 66 | 67 | ```js 68 | 69 | let cy = cytoscape({ 70 | container: document.getElementById('#cy'), 71 | /* ... */ 72 | }); 73 | 74 | // the default values of each option are outlined below: 75 | let defaults = { 76 | canConnect: function( sourceNode, targetNode ){ 77 | // whether an edge can be created between source and target 78 | return !sourceNode.same(targetNode); // e.g. disallow loops 79 | }, 80 | edgeParams: function( sourceNode, targetNode ){ 81 | // for edges between the specified source and target 82 | // return element object to be passed to cy.add() for edge 83 | return {}; 84 | }, 85 | hoverDelay: 150, // time spent hovering over a target node before it is considered selected 86 | snap: true, // when enabled, the edge can be drawn by just moving close to a target node (can be confusing on compound graphs) 87 | snapThreshold: 50, // the target node must be less than or equal to this many pixels away from the cursor/finger 88 | snapFrequency: 15, // the number of times per second (Hz) that snap checks done (lower is less expensive) 89 | noEdgeEventsInDraw: true, // set events:no to edges during draws, prevents mouseouts on compounds 90 | disableBrowserGestures: true // during an edge drawing gesture, disable browser gestures such as two-finger trackpad swipe and pinch-to-zoom 91 | }; 92 | 93 | let eh = cy.edgehandles( defaults ); 94 | 95 | ``` 96 | 97 | 98 | ## API 99 | 100 | The object returned by `cy.edgehandles()` has several functions available on it: 101 | 102 | * `start( sourceNode )` : manually start the gesture (as if the handle were already held) 103 | * `stop()` : manually completes or cancels the gesture 104 | * `disable()` : disables edgehandles behaviour 105 | * `enable()` : enables edgehandles behaviour 106 | * `enableDrawMode()` : turn on draw mode (the entire node body acts like the handle) 107 | * `disableDrawMode()` : turn off draw mode 108 | * `destroy()` : remove edgehandles behaviour 109 | 110 | 111 | ## Classes 112 | 113 | These classes can be used for styling the graph as it interacts with the extension: 114 | 115 | * `eh-source` : The source node 116 | * `eh-target` : A target node 117 | * `eh-preview` : Preview edges (i.e. shown before releasing the mouse button and the edge creation is confirmed) 118 | * `eh-hover` : Added to nodes as they are hovered over as targets 119 | * `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) 120 | * `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) 121 | * `eh-ghost` : A ghost element 122 | * `eh-presumptive-target` : A node that, during an edge drag, may become a target when released 123 | * `eh-preview-active` : Applied to the source, target, and ghost edge when the preview is active 124 | 125 | 126 | ## Events 127 | 128 | 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. 129 | 130 | * `ehstart` : when the edge creation gesture starts 131 | * `(event, sourceNode)` 132 | * `event.position` : handle position 133 | * `ehcomplete` : when the edge is created 134 | * `(event, sourceNode, targetNode, addedEdge)` 135 | * `event.position` : cursor/finger position 136 | * `ehstop` : when the edge creation gesture is stopped (either successfully completed or cancelled) 137 | * `(event, sourceNode)` 138 | * `event.position` : cursor/finger position 139 | * `ehcancel` : when the edge creation gesture is cancelled 140 | * `(event, sourceNode, cancelledTargets)` 141 | * `event.position` : cursor/finger position 142 | * `ehhoverover` : when hovering over a target 143 | * `(event, sourceNode, targetNode)` 144 | * `event.position` : cursor/finger position 145 | * `ehhoverout` : when leaving a target node 146 | * `(event, sourceNode, targetNode)` 147 | * `event.position` : cursor/finger position 148 | * `ehpreviewon` : when a preview is shown 149 | * `(event, sourceNode, targetNode, previewEdge)` 150 | * `event.position` : cursor/finger position 151 | * `ehpreviewoff` : when the preview is removed 152 | * `(event, sourceNode, targetNode, previewEdge)` 153 | * `event.position` : cursor/finger position 154 | * `ehdrawon` : when draw mode is enabled 155 | * `(event)` 156 | * `ehdrawoff` : when draw mode is disabled 157 | * `(event)` 158 | 159 | Example binding: 160 | 161 | ```js 162 | cy.on('ehcomplete', (event, sourceNode, targetNode, addedEdge) => { 163 | let { position } = event; 164 | 165 | // ... 166 | }); 167 | ``` 168 | 169 | ## Build targets 170 | 171 | * `npm run test` : Run Mocha tests in `./test` 172 | * `npm run build` : Build `./src/**` into `cytoscape-edgehandles.js` 173 | * `npm run watch` : Automatically build on changes with live reloading (N.b. you must already have an HTTP server running) 174 | * `npm run dev` : Automatically build on changes with live reloading with webpack dev server 175 | * `npm run lint` : Run eslint on the source 176 | 177 | N.b. all builds use babel, so modern ES features can be used in the `src`. 178 | 179 | 180 | ## Publishing instructions 181 | 182 | This project is set up to automatically be published to npm and bower. To publish: 183 | 184 | 1. Build the extension : `npm run build:release` 185 | 1. Commit the build : `git commit -am "Build for release"` 186 | 1. Bump the version number and tag: `npm version major|minor|patch` 187 | 1. Push to origin: `git push && git push --tags` 188 | 1. Publish to npm: `npm publish .` 189 | 1. If publishing to bower for the first time, you'll need to run `bower register cytoscape-edgehandles https://github.com/cytoscape/edgehandles.git`a 190 | 1. [Make a new release](https://github.com/cytoscape/cytoscape.js-edgehandles/releases/new) for Zenodo. 191 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cytoscape-edgehandles", 3 | "description": "Edge creation UI extension for Cytoscape", 4 | "main": "cytoscape-edgehandles.js", 5 | "dependencies": { 6 | "cytoscape": "^3.2.0" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/cytoscape/cytoscape.js-edgehandles.git" 11 | }, 12 | "ignore": [ 13 | "**/.*", 14 | "node_modules", 15 | "bower_components", 16 | "test", 17 | "tests" 18 | ], 19 | "keywords": [ 20 | "cytoscape", 21 | "cytoscape-extension" 22 | ], 23 | "license": "MIT" 24 | } 25 | -------------------------------------------------------------------------------- /cytoscape-edgehandles.js: -------------------------------------------------------------------------------- 1 | (function webpackUniversalModuleDefinition(root, factory) { 2 | if(typeof exports === 'object' && typeof module === 'object') 3 | module.exports = factory(require("lodash.memoize"), require("lodash.throttle")); 4 | else if(typeof define === 'function' && define.amd) 5 | define(["lodash.memoize", "lodash.throttle"], factory); 6 | else if(typeof exports === 'object') 7 | exports["cytoscapeEdgehandles"] = factory(require("lodash.memoize"), require("lodash.throttle")); 8 | else 9 | root["cytoscapeEdgehandles"] = factory(root["_"]["memoize"], root["_"]["throttle"]); 10 | })(this, function(__WEBPACK_EXTERNAL_MODULE_13__, __WEBPACK_EXTERNAL_MODULE_14__) { 11 | return /******/ (function(modules) { // webpackBootstrap 12 | /******/ // The module cache 13 | /******/ var installedModules = {}; 14 | /******/ 15 | /******/ // The require function 16 | /******/ function __webpack_require__(moduleId) { 17 | /******/ 18 | /******/ // Check if module is in cache 19 | /******/ if(installedModules[moduleId]) { 20 | /******/ return installedModules[moduleId].exports; 21 | /******/ } 22 | /******/ // Create a new module (and put it into the cache) 23 | /******/ var module = installedModules[moduleId] = { 24 | /******/ i: moduleId, 25 | /******/ l: false, 26 | /******/ exports: {} 27 | /******/ }; 28 | /******/ 29 | /******/ // Execute the module function 30 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 31 | /******/ 32 | /******/ // Flag the module as loaded 33 | /******/ module.l = true; 34 | /******/ 35 | /******/ // Return the exports of the module 36 | /******/ return module.exports; 37 | /******/ } 38 | /******/ 39 | /******/ 40 | /******/ // expose the modules object (__webpack_modules__) 41 | /******/ __webpack_require__.m = modules; 42 | /******/ 43 | /******/ // expose the module cache 44 | /******/ __webpack_require__.c = installedModules; 45 | /******/ 46 | /******/ // identity function for calling harmony imports with the correct context 47 | /******/ __webpack_require__.i = function(value) { return value; }; 48 | /******/ 49 | /******/ // define getter function for harmony exports 50 | /******/ __webpack_require__.d = function(exports, name, getter) { 51 | /******/ if(!__webpack_require__.o(exports, name)) { 52 | /******/ Object.defineProperty(exports, name, { 53 | /******/ configurable: false, 54 | /******/ enumerable: true, 55 | /******/ get: getter 56 | /******/ }); 57 | /******/ } 58 | /******/ }; 59 | /******/ 60 | /******/ // getDefaultExport function for compatibility with non-harmony modules 61 | /******/ __webpack_require__.n = function(module) { 62 | /******/ var getter = module && module.__esModule ? 63 | /******/ function getDefault() { return module['default']; } : 64 | /******/ function getModuleExports() { return module; }; 65 | /******/ __webpack_require__.d(getter, 'a', getter); 66 | /******/ return getter; 67 | /******/ }; 68 | /******/ 69 | /******/ // Object.prototype.hasOwnProperty.call 70 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 71 | /******/ 72 | /******/ // __webpack_public_path__ 73 | /******/ __webpack_require__.p = ""; 74 | /******/ 75 | /******/ // Load entry module and return exports 76 | /******/ return __webpack_require__(__webpack_require__.s = 12); 77 | /******/ }) 78 | /************************************************************************/ 79 | /******/ ([ 80 | /* 0 */ 81 | /***/ (function(module, exports, __webpack_require__) { 82 | 83 | "use strict"; 84 | 85 | 86 | // Simple, internal Object.assign() polyfill for options objects etc. 87 | 88 | module.exports = Object.assign != null ? Object.assign.bind(Object) : function (tgt) { 89 | for (var _len = arguments.length, srcs = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 90 | srcs[_key - 1] = arguments[_key]; 91 | } 92 | 93 | srcs.filter(function (src) { 94 | return src != null; 95 | }).forEach(function (src) { 96 | Object.keys(src).forEach(function (k) { 97 | return tgt[k] = src[k]; 98 | }); 99 | }); 100 | 101 | return tgt; 102 | }; 103 | 104 | /***/ }), 105 | /* 1 */ 106 | /***/ (function(module, exports, __webpack_require__) { 107 | 108 | "use strict"; 109 | 110 | 111 | var Edgehandles = __webpack_require__(10); 112 | var assign = __webpack_require__(0); 113 | 114 | module.exports = function (options) { 115 | var cy = this; 116 | 117 | return new Edgehandles(assign({ cy: cy }, options)); 118 | }; 119 | 120 | /***/ }), 121 | /* 2 */ 122 | /***/ (function(module, exports, __webpack_require__) { 123 | 124 | "use strict"; 125 | 126 | 127 | function disableGestures() { 128 | this.saveGestureState(); 129 | 130 | this.cy.zoomingEnabled(false).panningEnabled(false).boxSelectionEnabled(false); 131 | 132 | if (this.options.disableBrowserGestures) { 133 | var wlOpts = this.windowListenerOptions; 134 | 135 | window.addEventListener('touchstart', this.preventDefault, wlOpts); 136 | window.addEventListener('touchmove', this.preventDefault, wlOpts); 137 | window.addEventListener('wheel', this.preventDefault, wlOpts); 138 | } 139 | 140 | return this; 141 | } 142 | 143 | function resetGestures() { 144 | this.cy.zoomingEnabled(this.lastZoomingEnabled).panningEnabled(this.lastPanningEnabled).boxSelectionEnabled(this.lastBoxSelectionEnabled); 145 | 146 | if (this.options.disableBrowserGestures) { 147 | var wlOpts = this.windowListenerOptions; 148 | 149 | window.removeEventListener('touchstart', this.preventDefault, wlOpts); 150 | window.removeEventListener('touchmove', this.preventDefault, wlOpts); 151 | window.removeEventListener('wheel', this.preventDefault, wlOpts); 152 | } 153 | 154 | return this; 155 | } 156 | 157 | function saveGestureState() { 158 | var cy = this.cy; 159 | 160 | 161 | this.lastPanningEnabled = cy.panningEnabled(); 162 | this.lastZoomingEnabled = cy.zoomingEnabled(); 163 | this.lastBoxSelectionEnabled = cy.boxSelectionEnabled(); 164 | 165 | return this; 166 | } 167 | 168 | module.exports = { disableGestures: disableGestures, resetGestures: resetGestures, saveGestureState: saveGestureState }; 169 | 170 | /***/ }), 171 | /* 3 */ 172 | /***/ (function(module, exports, __webpack_require__) { 173 | 174 | "use strict"; 175 | 176 | 177 | function addCytoscapeListeners() { 178 | var _this = this; 179 | 180 | var cy = this.cy, 181 | options = this.options; 182 | 183 | // grabbing nodes 184 | 185 | this.addListener(cy, 'drag', function () { 186 | return _this.grabbingNode = true; 187 | }); 188 | this.addListener(cy, 'free', function () { 189 | return _this.grabbingNode = false; 190 | }); 191 | 192 | // start on tapstart handle 193 | // start on tapstart node (draw mode) 194 | // toggle on source node 195 | this.addListener(cy, 'tapstart', 'node', function (e) { 196 | var node = e.target; 197 | 198 | if (_this.drawMode) { 199 | _this.start(node); 200 | } 201 | }); 202 | 203 | // update line on drag 204 | this.addListener(cy, 'tapdrag', function (e) { 205 | _this.update(e.position); 206 | }); 207 | 208 | // hover over preview 209 | this.addListener(cy, 'tapdragover', 'node', function (e) { 210 | if (options.snap) { 211 | // then ignore events like mouseover 212 | } else { 213 | _this.preview(e.target); 214 | } 215 | }); 216 | 217 | // hover out unpreview 218 | this.addListener(cy, 'tapdragout', 'node', function (e) { 219 | if (options.snap) { 220 | // then keep the preview 221 | } else { 222 | _this.unpreview(e.target); 223 | } 224 | }); 225 | 226 | // stop gesture on tapend 227 | this.addListener(cy, 'tapend', function () { 228 | _this.stop(); 229 | }); 230 | 231 | return this; 232 | } 233 | 234 | module.exports = { addCytoscapeListeners: addCytoscapeListeners }; 235 | 236 | /***/ }), 237 | /* 4 */ 238 | /***/ (function(module, exports, __webpack_require__) { 239 | 240 | "use strict"; 241 | 242 | 243 | /* eslint-disable no-unused-vars */ 244 | var defaults = { 245 | canConnect: function canConnect(sourceNode, targetNode) { 246 | // whether an edge can be created between source and target 247 | return !sourceNode.same(targetNode); // e.g. disallow loops 248 | }, 249 | edgeParams: function edgeParams(sourceNode, targetNode) { 250 | // for edges between the specified source and target 251 | // return element object to be passed to cy.add() for edge 252 | return {}; 253 | }, 254 | hoverDelay: 150, // time spent hovering over a target node before it is considered selected 255 | snap: true, // when enabled, the edge can be drawn by just moving close to a target node (can be confusing on compound graphs) 256 | snapThreshold: 50, // the target node must be less than or equal to this many pixels away from the cursor/finger 257 | snapFrequency: 15, // the number of times per second (Hz) that snap checks done (lower is less expensive) 258 | noEdgeEventsInDraw: true, // set events:no to edges during draws, prevents mouseouts on compounds 259 | disableBrowserGestures: true // during an edge drawing gesture, disable browser gestures such as two-finger trackpad swipe and pinch-to-zoom 260 | }; 261 | /* eslint-enable */ 262 | 263 | module.exports = defaults; 264 | 265 | /***/ }), 266 | /* 5 */ 267 | /***/ (function(module, exports, __webpack_require__) { 268 | 269 | "use strict"; 270 | 271 | 272 | function toggleDrawMode(bool) { 273 | var cy = this.cy; 274 | 275 | 276 | this.drawMode = bool != null ? bool : !this.drawMode; 277 | 278 | if (this.drawMode) { 279 | this.prevUngrabifyState = cy.autoungrabify(); 280 | 281 | cy.autoungrabify(true); 282 | 283 | this.emit('drawon'); 284 | } else { 285 | cy.autoungrabify(this.prevUngrabifyState); 286 | 287 | this.emit('drawoff'); 288 | } 289 | 290 | return this; 291 | } 292 | 293 | function enableDrawMode() { 294 | return this.toggleDrawMode(true); 295 | } 296 | 297 | function disableDrawMode() { 298 | return this.toggleDrawMode(false); 299 | } 300 | 301 | module.exports = { toggleDrawMode: toggleDrawMode, enableDrawMode: enableDrawMode, disableDrawMode: disableDrawMode }; 302 | 303 | /***/ }), 304 | /* 6 */ 305 | /***/ (function(module, exports, __webpack_require__) { 306 | 307 | "use strict"; 308 | 309 | 310 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; 311 | 312 | var assign = __webpack_require__(0); 313 | var isString = function isString(x) { 314 | return (typeof x === 'undefined' ? 'undefined' : _typeof(x)) === _typeof(''); 315 | }; 316 | var isArray = function isArray(x) { 317 | return (typeof x === 'undefined' ? 'undefined' : _typeof(x)) === _typeof([]) && x.length != null; 318 | }; 319 | 320 | function getEleJson(overrides, params, addedClasses) { 321 | var json = {}; 322 | 323 | // basic values 324 | assign(json, params, overrides); 325 | 326 | // make sure params can specify data but that overrides take precedence 327 | assign(json.data, params.data, overrides.data); 328 | 329 | if (isString(params.classes)) { 330 | json.classes = params.classes + ' ' + addedClasses; 331 | } else if (isArray(params.classes)) { 332 | json.classes = params.classes.join(' ') + ' ' + addedClasses; 333 | } else { 334 | json.classes = addedClasses; 335 | } 336 | 337 | return json; 338 | } 339 | 340 | function makeEdges() { 341 | var preview = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; 342 | var cy = this.cy, 343 | options = this.options, 344 | presumptiveTargets = this.presumptiveTargets, 345 | previewEles = this.previewEles, 346 | active = this.active; 347 | 348 | 349 | var source = this.sourceNode; 350 | var target = this.targetNode; 351 | var classes = preview ? 'eh-preview' : ''; 352 | var added = cy.collection(); 353 | var canConnect = this.canConnect(target); 354 | 355 | // can't make edges outside of regular gesture lifecycle 356 | if (!active) { 357 | return; 358 | } 359 | 360 | // must be able to connect 361 | if (!canConnect) { 362 | return; 363 | } 364 | 365 | // detect cancel 366 | if (!target || target.size() === 0) { 367 | previewEles.remove(); 368 | 369 | this.emit('cancel', this.mp(), source, presumptiveTargets); 370 | 371 | return; 372 | } 373 | 374 | // just remove preview class if we already have the edges 375 | if (!preview) { 376 | previewEles.removeClass('eh-preview').style('events', ''); 377 | 378 | this.emit('complete', this.mp(), source, target, previewEles); 379 | 380 | return; 381 | } 382 | 383 | var source2target = cy.add(getEleJson({ 384 | group: 'edges', 385 | data: { 386 | source: source.id(), 387 | target: target.id() 388 | } 389 | }, this.edgeParams(target), classes)); 390 | 391 | added = added.merge(source2target); 392 | 393 | if (preview) { 394 | this.previewEles = added; 395 | 396 | added.style('events', 'no'); 397 | } else { 398 | added.style('events', ''); 399 | 400 | this.emit('complete', this.mp(), source, target, added); 401 | } 402 | 403 | return this; 404 | } 405 | 406 | function makePreview() { 407 | this.makeEdges(true); 408 | 409 | return this; 410 | } 411 | 412 | function previewShown() { 413 | return this.previewEles.nonempty() && this.previewEles.inside(); 414 | } 415 | 416 | function removePreview() { 417 | if (this.previewShown()) { 418 | this.previewEles.remove(); 419 | } 420 | 421 | return this; 422 | } 423 | 424 | function updateEdge() { 425 | var _this = this; 426 | 427 | var sourceNode = this.sourceNode, 428 | ghostNode = this.ghostNode, 429 | cy = this.cy, 430 | mx = this.mx, 431 | my = this.my; 432 | 433 | var x = mx; 434 | var y = my; 435 | var ghostEdge = void 0, 436 | ghostEles = void 0; 437 | 438 | // can't draw a line without having the starting node 439 | if (!sourceNode) { 440 | return; 441 | } 442 | 443 | if (!ghostNode || ghostNode.length === 0 || ghostNode.removed()) { 444 | ghostEles = this.ghostEles = cy.collection(); 445 | 446 | cy.batch(function () { 447 | ghostNode = _this.ghostNode = cy.add({ 448 | group: 'nodes', 449 | classes: 'eh-ghost eh-ghost-node', 450 | position: { 451 | x: 0, 452 | y: 0 453 | } 454 | }); 455 | 456 | ghostNode.style({ 457 | 'background-color': 'blue', 458 | 'width': 0.0001, 459 | 'height': 0.0001, 460 | 'opacity': 0, 461 | 'events': 'no' 462 | }); 463 | 464 | var ghostEdgeParams = {}; 465 | 466 | ghostEdge = cy.add(assign({}, ghostEdgeParams, { 467 | group: 'edges', 468 | data: assign({}, ghostEdgeParams.data, { 469 | source: sourceNode.id(), 470 | target: ghostNode.id() 471 | }), 472 | classes: 'eh-ghost eh-ghost-edge' 473 | })); 474 | 475 | ghostEdge.style({ 476 | 'events': 'no' 477 | }); 478 | }); 479 | 480 | ghostEles.merge(ghostNode).merge(ghostEdge); 481 | } 482 | 483 | ghostNode.position({ x: x, y: y }); 484 | 485 | return this; 486 | } 487 | 488 | module.exports = { 489 | makeEdges: makeEdges, makePreview: makePreview, removePreview: removePreview, previewShown: previewShown, 490 | updateEdge: updateEdge 491 | }; 492 | 493 | /***/ }), 494 | /* 7 */ 495 | /***/ (function(module, exports, __webpack_require__) { 496 | 497 | "use strict"; 498 | 499 | 500 | function disableEdgeEvents() { 501 | if (this.options.noEdgeEventsInDraw) { 502 | this.cy.edges().style('events', 'no'); 503 | } 504 | 505 | return this; 506 | } 507 | 508 | function enableEdgeEvents() { 509 | if (this.options.noEdgeEventsInDraw) { 510 | this.cy.edges().style('events', ''); 511 | } 512 | 513 | return this; 514 | } 515 | 516 | module.exports = { disableEdgeEvents: disableEdgeEvents, enableEdgeEvents: enableEdgeEvents }; 517 | 518 | /***/ }), 519 | /* 8 */ 520 | /***/ (function(module, exports, __webpack_require__) { 521 | 522 | "use strict"; 523 | 524 | 525 | function enable() { 526 | this.enabled = true; 527 | 528 | this.emit('enable'); 529 | 530 | return this; 531 | } 532 | 533 | function disable() { 534 | this.enabled = false; 535 | 536 | this.emit('disable'); 537 | 538 | return this; 539 | } 540 | 541 | module.exports = { enable: enable, disable: disable }; 542 | 543 | /***/ }), 544 | /* 9 */ 545 | /***/ (function(module, exports, __webpack_require__) { 546 | 547 | "use strict"; 548 | 549 | 550 | var memoize = __webpack_require__(13); 551 | var sqrt2 = Math.sqrt(2); 552 | 553 | function canStartOn(node) { 554 | var previewEles = this.previewEles, 555 | ghostEles = this.ghostEles; 556 | 557 | var isPreview = function isPreview(el) { 558 | return previewEles.anySame(el); 559 | }; 560 | var isGhost = function isGhost(el) { 561 | return ghostEles.anySame(el); 562 | }; 563 | var isTemp = function isTemp(el) { 564 | return isPreview(el) || isGhost(el); 565 | }; 566 | 567 | var enabled = this.enabled, 568 | active = this.active, 569 | grabbingNode = this.grabbingNode; 570 | 571 | 572 | return enabled && !active && !grabbingNode && node != null && node.nonempty() && !isTemp(node); 573 | } 574 | 575 | function canStartDrawModeOn(node) { 576 | return this.canStartOn(node) && this.drawMode; 577 | } 578 | 579 | function canStartNonDrawModeOn(node) { 580 | return this.canStartOn(node) && !this.drawMode; 581 | } 582 | 583 | function start(node) { 584 | var _this = this; 585 | 586 | if (!this.canStartOn(node)) { 587 | return; 588 | } 589 | 590 | this.active = true; 591 | 592 | this.sourceNode = node; 593 | this.sourceNode.addClass('eh-source'); 594 | 595 | this.disableGestures(); 596 | this.disableEdgeEvents(); 597 | 598 | var getId = function getId(n) { 599 | return n.id(); 600 | }; 601 | 602 | this.canConnect = memoize(function (target) { 603 | return _this.options.canConnect(_this.sourceNode, target); 604 | }, getId); 605 | 606 | this.edgeParams = memoize(function (target) { 607 | return _this.options.edgeParams(_this.sourceNode, target); 608 | }, getId); 609 | 610 | this.emit('start', this.hp(), node); 611 | } 612 | 613 | function update(pos) { 614 | if (!this.active) { 615 | return; 616 | } 617 | 618 | var p = pos; 619 | 620 | this.mx = p.x; 621 | this.my = p.y; 622 | 623 | this.updateEdge(); 624 | this.throttledSnap(); 625 | 626 | return this; 627 | } 628 | 629 | function snap() { 630 | if (!this.active || !this.options.snap) { 631 | return false; 632 | } 633 | 634 | var cy = this.cy; 635 | var tgt = this.targetNode; 636 | var threshold = this.options.snapThreshold; 637 | var mousePos = this.mp(); 638 | var previewEles = this.previewEles, 639 | ghostNode = this.ghostNode; 640 | 641 | 642 | var radius = function radius(n) { 643 | return sqrt2 * Math.max(n.outerWidth(), n.outerHeight()) / 2; 644 | }; // worst-case enclosure of bb by circle 645 | var sqDist = function sqDist(x1, y1, x2, y2) { 646 | var dx = x2 - x1;var dy = y2 - y1;return dx * dx + dy * dy; 647 | }; 648 | var sqDistByPt = function sqDistByPt(p1, p2) { 649 | return sqDist(p1.x, p1.y, p2.x, p2.y); 650 | }; 651 | var nodeSqDist = function nodeSqDist(n) { 652 | return sqDistByPt(n.position(), mousePos); 653 | }; 654 | 655 | var sqThreshold = function sqThreshold(n) { 656 | var r = radius(n);var t = r + threshold;return t * t; 657 | }; 658 | var isWithinThreshold = function isWithinThreshold(n) { 659 | return nodeSqDist(n) <= sqThreshold(n); 660 | }; 661 | 662 | var bbSqDist = function bbSqDist(n) { 663 | var p = n.position(); 664 | var halfW = n.outerWidth() / 2; 665 | var halfH = n.outerHeight() / 2; 666 | 667 | // node and mouse positions, line is formed from node to mouse 668 | var nx = p.x; 669 | var ny = p.y; 670 | var mx = mousePos.x; 671 | var my = mousePos.y; 672 | 673 | // bounding box 674 | var x1 = nx - halfW; 675 | var x2 = nx + halfW; 676 | var y1 = ny - halfH; 677 | var y2 = ny + halfH; 678 | 679 | var insideXBounds = x1 <= mx && mx <= x2; 680 | var insideYBounds = y1 <= my && my <= y2; 681 | 682 | if (insideXBounds && insideYBounds) { 683 | // inside box 684 | return 0; 685 | } else if (insideXBounds) { 686 | // perpendicular distance to box, top or bottom 687 | var dy1 = my - y1; 688 | var dy2 = my - y2; 689 | 690 | return Math.min(dy1 * dy1, dy2 * dy2); 691 | } else if (insideYBounds) { 692 | // perpendicular distance to box, left or right 693 | var dx1 = mx - x1; 694 | var dx2 = mx - x2; 695 | 696 | return Math.min(dx1 * dx1, dx2 * dx2); 697 | } else if (mx < x1 && my < y1) { 698 | // top-left corner distance 699 | return sqDist(mx, my, x1, y1); 700 | } else if (mx > x2 && my < y1) { 701 | // top-right corner distance 702 | return sqDist(mx, my, x2, y1); 703 | } else if (mx < x1 && my > y2) { 704 | // bottom-left corner distance 705 | return sqDist(mx, my, x1, y2); 706 | } else { 707 | // bottom-right corner distance 708 | return sqDist(mx, my, x2, y2); 709 | } 710 | }; 711 | 712 | var cmpBbSqDist = function cmpBbSqDist(n1, n2) { 713 | return bbSqDist(n1) - bbSqDist(n2); 714 | }; 715 | 716 | var cmp = cmpBbSqDist; 717 | 718 | var allowHoverDelay = false; 719 | 720 | var mouseIsInside = function mouseIsInside(n) { 721 | var mp = mousePos; 722 | var w = n.outerWidth(); 723 | var halfW = w / 2; 724 | var h = n.outerHeight(); 725 | var halfH = h / 2; 726 | var p = n.position(); 727 | var x1 = p.x - halfW; 728 | var x2 = p.x + halfW; 729 | var y1 = p.y - halfH; 730 | var y2 = p.y + halfH; 731 | 732 | return x1 <= mp.x && mp.x <= x2 && y1 <= mp.y && mp.y <= y2; 733 | }; 734 | 735 | var isEhEle = function isEhEle(n) { 736 | return n.same(previewEles) || n.same(ghostNode); 737 | }; 738 | 739 | var nodesByDist = cy.nodes(function (n) { 740 | return !isEhEle(n) && isWithinThreshold(n); 741 | }).sort(cmp); 742 | var snapped = false; 743 | 744 | if (tgt.nonempty() && !isWithinThreshold(tgt)) { 745 | this.unpreview(tgt); 746 | } 747 | 748 | for (var i = 0; i < nodesByDist.length; i++) { 749 | var n = nodesByDist[i]; 750 | 751 | // skip a parent node when the mouse is inside it 752 | if (n.isParent() && mouseIsInside(n)) { 753 | continue; 754 | } 755 | 756 | // skip a child node when the mouse is not inside the parent 757 | if (n.isChild() && !mouseIsInside(n.parent())) { 758 | continue; 759 | } 760 | 761 | if (n.same(tgt) || this.preview(n, allowHoverDelay)) { 762 | snapped = true; 763 | break; 764 | } 765 | } 766 | 767 | return snapped; 768 | } 769 | 770 | function preview(target) { 771 | var _this2 = this; 772 | 773 | var allowHoverDelay = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; 774 | var options = this.options, 775 | sourceNode = this.sourceNode, 776 | ghostNode = this.ghostNode, 777 | ghostEles = this.ghostEles, 778 | presumptiveTargets = this.presumptiveTargets, 779 | previewEles = this.previewEles, 780 | active = this.active; 781 | 782 | var source = sourceNode; 783 | var isGhost = target.same(ghostNode); 784 | var noEdge = !this.canConnect(target); 785 | var isExistingTgt = target.same(this.targetNode); 786 | 787 | if (!active || isGhost || noEdge || isExistingTgt 788 | // || (target.isParent()) 789 | ) { 790 | return false; 791 | } 792 | 793 | if (this.targetNode.nonempty()) { 794 | this.unpreview(this.targetNode); 795 | } 796 | 797 | clearTimeout(this.previewTimeout); 798 | 799 | var applyPreview = function applyPreview() { 800 | _this2.targetNode = target; 801 | 802 | presumptiveTargets.merge(target); 803 | 804 | target.addClass('eh-presumptive-target'); 805 | target.addClass('eh-target'); 806 | 807 | _this2.emit('hoverover', _this2.mp(), source, target); 808 | 809 | target.addClass('eh-preview'); 810 | 811 | ghostEles.addClass('eh-preview-active'); 812 | sourceNode.addClass('eh-preview-active'); 813 | target.addClass('eh-preview-active'); 814 | 815 | _this2.makePreview(); 816 | 817 | _this2.emit('previewon', _this2.mp(), source, target, previewEles); 818 | }; 819 | 820 | if (allowHoverDelay && options.hoverDelay > 0) { 821 | this.previewTimeout = setTimeout(applyPreview, options.hoverDelay); 822 | } else { 823 | applyPreview(); 824 | } 825 | 826 | return true; 827 | } 828 | 829 | function unpreview(target) { 830 | if (!this.active) { 831 | return; 832 | } 833 | 834 | var previewTimeout = this.previewTimeout, 835 | sourceNode = this.sourceNode, 836 | previewEles = this.previewEles, 837 | ghostEles = this.ghostEles, 838 | cy = this.cy; 839 | 840 | clearTimeout(previewTimeout); 841 | this.previewTimeout = null; 842 | 843 | var source = sourceNode; 844 | 845 | target.removeClass('eh-preview eh-target eh-presumptive-target eh-preview-active'); 846 | ghostEles.removeClass('eh-preview-active'); 847 | sourceNode.removeClass('eh-preview-active'); 848 | 849 | this.targetNode = cy.collection(); 850 | 851 | this.removePreview(source, target); 852 | 853 | this.emit('hoverout', this.mp(), source, target); 854 | this.emit('previewoff', this.mp(), source, target, previewEles); 855 | 856 | return this; 857 | } 858 | 859 | function stop() { 860 | if (!this.active) { 861 | return; 862 | } 863 | 864 | var sourceNode = this.sourceNode, 865 | targetNode = this.targetNode, 866 | ghostEles = this.ghostEles, 867 | presumptiveTargets = this.presumptiveTargets; 868 | 869 | 870 | clearTimeout(this.previewTimeout); 871 | 872 | sourceNode.removeClass('eh-source eh-preview-active'); 873 | targetNode.removeClass('eh-target eh-preview eh-hover eh-preview-active'); 874 | presumptiveTargets.removeClass('eh-presumptive-target'); 875 | 876 | this.makeEdges(); 877 | 878 | ghostEles.remove(); 879 | 880 | this.clearCollections(); 881 | 882 | this.resetGestures(); 883 | this.enableEdgeEvents(); 884 | 885 | this.active = false; 886 | 887 | this.emit('stop', this.mp(), sourceNode); 888 | 889 | return this; 890 | } 891 | 892 | module.exports = { 893 | start: start, update: update, preview: preview, unpreview: unpreview, stop: stop, snap: snap, 894 | canStartOn: canStartOn, canStartDrawModeOn: canStartDrawModeOn, canStartNonDrawModeOn: canStartNonDrawModeOn 895 | }; 896 | 897 | /***/ }), 898 | /* 10 */ 899 | /***/ (function(module, exports, __webpack_require__) { 900 | 901 | "use strict"; 902 | 903 | 904 | var defaults = __webpack_require__(4); 905 | var assign = __webpack_require__(0); 906 | var throttle = __webpack_require__(14); 907 | 908 | var cyGesturesToggle = __webpack_require__(2); 909 | var cyListeners = __webpack_require__(3); 910 | var drawMode = __webpack_require__(5); 911 | var drawing = __webpack_require__(6); 912 | var enabling = __webpack_require__(8); 913 | var gestureLifecycle = __webpack_require__(9); 914 | var listeners = __webpack_require__(11); 915 | var edgeEvents = __webpack_require__(7); 916 | 917 | function Edgehandles(options) { 918 | var cy = options.cy; 919 | 920 | this.cy = cy; 921 | this.listeners = []; 922 | 923 | // edgehandles gesture state 924 | this.enabled = true; 925 | this.drawMode = false; 926 | this.active = false; 927 | this.grabbingNode = false; 928 | 929 | // edgehandles elements 930 | this.clearCollections(); 931 | 932 | // mouse position 933 | this.mx = 0; 934 | this.my = 0; 935 | 936 | this.options = assign({}, defaults, options); 937 | 938 | this.saveGestureState(); 939 | this.addListeners(); 940 | 941 | this.throttledSnap = throttle(this.snap.bind(this), 1000 / options.snapFrequency); 942 | 943 | this.preventDefault = function (e) { 944 | return e.preventDefault(); 945 | }; 946 | 947 | // disabled until start() 948 | this.canConnect = function () { 949 | return false; 950 | }; 951 | 952 | var supportsPassive = false; 953 | try { 954 | var opts = Object.defineProperty({}, 'passive', { 955 | get: function get() { 956 | supportsPassive = true; 957 | } 958 | }); 959 | 960 | window.addEventListener('test', null, opts); 961 | } catch (err) { 962 | // swallow 963 | } 964 | 965 | if (supportsPassive) { 966 | this.windowListenerOptions = { capture: true, passive: false }; 967 | } else { 968 | this.windowListenerOptions = true; 969 | } 970 | } 971 | 972 | var proto = Edgehandles.prototype = {}; 973 | var extend = function extend(obj) { 974 | return assign(proto, obj); 975 | }; 976 | 977 | proto.destroy = function () { 978 | this.removeListeners(); 979 | }; 980 | 981 | proto.setOptions = function (options) { 982 | assign(this.options, options); 983 | }; 984 | 985 | proto.mp = function () { 986 | return { x: this.mx, y: this.my }; 987 | }; 988 | 989 | proto.hp = function () { 990 | return { x: this.hx, y: this.hy }; 991 | }; 992 | 993 | proto.clearCollections = function () { 994 | var cy = this.cy; 995 | 996 | 997 | this.previewEles = cy.collection(); 998 | this.ghostEles = cy.collection(); 999 | this.ghostNode = cy.collection(); 1000 | this.sourceNode = cy.collection(); 1001 | this.targetNode = cy.collection(); 1002 | this.presumptiveTargets = cy.collection(); 1003 | }; 1004 | 1005 | [cyGesturesToggle, cyListeners, drawMode, drawing, enabling, gestureLifecycle, listeners, edgeEvents].forEach(extend); 1006 | 1007 | module.exports = Edgehandles; 1008 | 1009 | /***/ }), 1010 | /* 11 */ 1011 | /***/ (function(module, exports, __webpack_require__) { 1012 | 1013 | "use strict"; 1014 | 1015 | 1016 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; 1017 | 1018 | function addListeners() { 1019 | var _this = this; 1020 | 1021 | this.addCytoscapeListeners(); 1022 | 1023 | this.addListener(this.cy, 'destroy', function () { 1024 | return _this.destroy(); 1025 | }); 1026 | 1027 | return this; 1028 | } 1029 | 1030 | function removeListeners() { 1031 | for (var i = this.listeners.length - 1; i >= 0; i--) { 1032 | var l = this.listeners[i]; 1033 | 1034 | this.removeListener(l.target, l.event, l.selector, l.callback, l.options); 1035 | } 1036 | 1037 | return this; 1038 | } 1039 | 1040 | function getListener(target, event, selector, callback, options) { 1041 | if ((typeof selector === 'undefined' ? 'undefined' : _typeof(selector)) !== _typeof('')) { 1042 | callback = selector; 1043 | options = callback; 1044 | selector = null; 1045 | } 1046 | 1047 | if (options == null) { 1048 | options = false; 1049 | } 1050 | 1051 | return { target: target, event: event, selector: selector, callback: callback, options: options }; 1052 | } 1053 | 1054 | function isDom(target) { 1055 | return target instanceof Element; 1056 | } 1057 | 1058 | function addListener(target, event, selector, callback, options) { 1059 | var l = getListener(target, event, selector, callback, options); 1060 | 1061 | this.listeners.push(l); 1062 | 1063 | if (isDom(l.target)) { 1064 | l.target.addEventListener(l.event, l.callback, l.options); 1065 | } else { 1066 | if (l.selector) { 1067 | l.target.addListener(l.event, l.selector, l.callback, l.options); 1068 | } else { 1069 | l.target.addListener(l.event, l.callback, l.options); 1070 | } 1071 | } 1072 | 1073 | return this; 1074 | } 1075 | 1076 | function removeListener(target, event, selector, callback, options) { 1077 | var l = getListener(target, event, selector, callback, options); 1078 | 1079 | for (var i = this.listeners.length - 1; i >= 0; i--) { 1080 | var l2 = this.listeners[i]; 1081 | 1082 | if (l.target === l2.target && l.event === l2.event && (l.selector == null || l.selector === l2.selector) && (l.callback == null || l.callback === l2.callback)) { 1083 | this.listeners.splice(i, 1); 1084 | 1085 | if (isDom(l.target)) { 1086 | l.target.removeEventListener(l.event, l.callback, l.options); 1087 | } else { 1088 | if (l.selector) { 1089 | l.target.removeListener(l.event, l.selector, l.callback, l.options); 1090 | } else { 1091 | l.target.removeListener(l.event, l.callback, l.options); 1092 | } 1093 | } 1094 | 1095 | break; 1096 | } 1097 | } 1098 | 1099 | return this; 1100 | } 1101 | 1102 | function emit(type, position) { 1103 | var cy = this.cy; 1104 | 1105 | for (var _len = arguments.length, args = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { 1106 | args[_key - 2] = arguments[_key]; 1107 | } 1108 | 1109 | cy.emit({ type: 'eh' + type, position: position }, args); 1110 | 1111 | return this; 1112 | } 1113 | 1114 | module.exports = { addListener: addListener, addListeners: addListeners, removeListener: removeListener, removeListeners: removeListeners, emit: emit }; 1115 | 1116 | /***/ }), 1117 | /* 12 */ 1118 | /***/ (function(module, exports, __webpack_require__) { 1119 | 1120 | "use strict"; 1121 | 1122 | 1123 | var impl = __webpack_require__(1); 1124 | 1125 | // registers the extension on a cytoscape lib ref 1126 | var register = function register(cytoscape) { 1127 | if (!cytoscape) { 1128 | return; 1129 | } // can't register if cytoscape unspecified 1130 | 1131 | cytoscape('core', 'edgehandles', impl); // register with cytoscape.js 1132 | }; 1133 | 1134 | if (typeof cytoscape !== 'undefined') { 1135 | // expose to global cytoscape (i.e. window.cytoscape) 1136 | register(cytoscape); // eslint-disable-line no-undef 1137 | } 1138 | 1139 | module.exports = register; 1140 | 1141 | /***/ }), 1142 | /* 13 */ 1143 | /***/ (function(module, exports) { 1144 | 1145 | module.exports = __WEBPACK_EXTERNAL_MODULE_13__; 1146 | 1147 | /***/ }), 1148 | /* 14 */ 1149 | /***/ (function(module, exports) { 1150 | 1151 | module.exports = __WEBPACK_EXTERNAL_MODULE_14__; 1152 | 1153 | /***/ }) 1154 | /******/ ]); 1155 | }); -------------------------------------------------------------------------------- /demo-compound-snap.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |