├── .eslintrc.js ├── .gitignore ├── LICENSE ├── README.md ├── build.js ├── dist ├── angular-drag-drop.js └── angular-drag-drop.min.js ├── example ├── build │ └── dragular.js ├── index.html ├── src │ ├── controllers │ │ └── game.js │ ├── dragular.css │ ├── dragular.js │ └── services │ │ └── board.js └── webpack.config.js ├── package.json ├── src ├── .eslintrc.js └── angular-drag-drop.js └── webpack.config.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | 'eslint:recommended', 4 | ], 5 | parserOptions: { 6 | ecmaVersion: 6, 7 | }, 8 | env: { 9 | node: true, 10 | }, 11 | rules: { 12 | } 13 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | *.swp 10 | *.swo 11 | .DS_Store 12 | 13 | node_modules 14 | 15 | .vscode 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2014-2015 the Geoffrey Goodman, https://github.com/ggoodman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Angular drag-and-drop 2 | ===================== 3 | 4 | [](https://gitter.im/ggoodman/angular-drag-drop?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 5 | 6 | Declarative drag and drop with zero dependencies in Angular.js 7 | 8 | Copyright (C) 2015, Geoff Goodman (https://github.com/ggoodman) 9 | 10 | 11 | 12 | Installation 13 | ------------ 14 | 15 | Several installation options: 16 | * npm: `npm install --save angular-drag-drop` 17 | * Download from github: [angular-drag-drop.min.js](https://raw.github.com/ggoodman/angular-drag-drop/master/dist/angular-drag-drop.min.js) 18 | 19 | 20 | 21 | Demo 22 | ---- 23 | 24 | [Dragular](http://bit.ly/17E25d2) - Drag tiles around like the famous 25 | [Sliding Puzzle](http://en.wikipedia.org/wiki/Sliding_puzzle) using angular drag and drop. 26 | 27 | Usage 28 | ----- 29 | 30 | Using Webpack or Browserify: 31 | 32 | ```js 33 | var Angular = require('angular'); 34 | 35 | var mod = Angular.module('yourModule', [ 36 | require('angular-drag-drop'), 37 | ]); 38 | 39 | // Now use `drag-container`, `drop-container` and `drop-target` in your templates 40 | ``` 41 | 42 | 43 | 44 | ### `drag-container` 45 | 46 | Define a DOM element that will become draggable and determines what the data associated with the drag event is. 47 | 48 | **Example** 49 | 50 | ```html 51 |
56 | ``` 57 | 58 | Attribute | Required? | Description 59 | ----------|-----------|------------ 60 | `drag-container` | Yes | Defines when the drag action is allowed or blocked for the draggable. Can be true or false. 61 | `drag-data` | No | Bind the data to be associated with dragging this element. When not specified the jqLite element on which the directive is placed will be used as the $dragData. 62 | 63 | The following callbacks are optional. 64 | Each can allow you to inject two special objects, `$event` and `$dragData`. 65 | `$event` is the original browser event. 66 | This can be helpful for setting the browser-level drag data using `$event.dataTransfer.setData('mime/type', data)`) 67 | or for setting the drag image / drop effect like `$event.dataTransfer.dropEffect = 'copy'`. 68 | `$dragData` is the data associated with dragging this element. 69 | It is optionally set by providing a reference via the `drag-container` attribute. 70 | 71 | * `on-drag-start` 72 | * `on-drag-end` 73 | 74 | 75 | 76 | ### `drop-container` 77 | 78 | Define a DOM element that will accept draggable elements that match pass an optional acceptance callback. 79 | 80 | **Example** 81 | 82 | ```html 83 | 90 | ``` 91 | 92 | Attribute | Required? | Description 93 | ----------|-----------|------------ 94 | `drop-accepts` | No | Define a call to check if the data being dragged is allowed 95 | 96 | The following callbacks are optional. 97 | Each can allow you to inject two special objects, `$event` and `$dragData`. 98 | `$event` is the original browser event. 99 | `$dragData` is the data associated with dragging this element. 100 | 101 | * `on-drag-enter` 102 | * `on-drag-over` 103 | * `on-drag-leave` 104 | * `on-drop` 105 | 106 | 107 | 108 | ### `drop-target` 109 | 110 | Define a region of the parent `drop-container` that can independently accept drag and drop events in a logical region. 111 | 112 | This module will only consider those logical regions that have `drop-targets` bound in determining which region 113 | should receive events at any point in time. The algorithm to determine which logical region is active is based 114 | on the proximity of the cursor to the virtual center-point of each logical region. 115 | 116 | **Must be a child of a `drop-container`** 117 | 118 | **Example** 119 | 120 | ```html 121 | 127 | ``` 128 | 129 | Attribute | Required? | Description 130 | ----------|-----------|------------ 131 | `drop-target` | Yes | Defines the logical region of the parent `drop-container` that will accept events. Can be one of: `center`, `top`, `top-right`, `right`, `bottom-right`, `bottom`, `bottom-left`, `left`, `top-left` 132 | 133 | The following callbacks are optional. 134 | Each can allow you to inject two special objects, `$event` and `$dragData`. 135 | `$event` is the original browser event. 136 | `$dragData` is the data associated with dragging this element. 137 | 138 | * `on-drag-enter` 139 | * `on-drag-over` 140 | * `on-drag-leave` 141 | * `on-drop` 142 | 143 | 144 | 145 | License 146 | ------- 147 | 148 | Released under the terms of MIT License: 149 | 150 | Permission is hereby granted, free of charge, to any person obtaining 151 | a copy of this software and associated documentation files (the 152 | 'Software'), to deal in the Software without restriction, including 153 | without limitation the rights to use, copy, modify, merge, publish, 154 | distribute, sublicense, and/or sell copies of the Software, and to 155 | permit persons to whom the Software is furnished to do so, subject to 156 | the following conditions: 157 | 158 | The above copyright notice and this permission notice shall be 159 | included in all copies or substantial portions of the Software. 160 | 161 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 162 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 163 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 164 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 165 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 166 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 167 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 168 | -------------------------------------------------------------------------------- /build.js: -------------------------------------------------------------------------------- 1 | var Config = require("./webpack.config"); 2 | var Package = require("./package.json"); 3 | var Webpack = require("webpack"); 4 | 5 | var buildUnminified = function (config, callback) { 6 | config.plugins = []; 7 | config.output.filename = Package.name + ".js"; 8 | 9 | Webpack(config, callback); 10 | }; 11 | 12 | var buildMinified = function (config, callback) { 13 | config.plugins = [new Webpack.optimize.UglifyJsPlugin()]; 14 | config.output.filename = Package.name + ".min.js"; 15 | 16 | Webpack(config, callback); 17 | }; 18 | 19 | buildUnminified(Config, function (err, stats) { 20 | if (err) { 21 | console.error("[ERR] Build failed: "); 22 | console.trace(err); 23 | 24 | return; 25 | } 26 | 27 | console.log("[OK] Unminified built: " + stats.toString({colors: true})); 28 | 29 | buildMinified(Config, function (err, stats) { 30 | if (err) { 31 | console.error("[ERR] Build failed: "); 32 | console.trace(err); 33 | 34 | return; 35 | } 36 | 37 | console.log("[OK] Minified built: " + stats.toString({colors: true})); 38 | }); 39 | 40 | }); 41 | -------------------------------------------------------------------------------- /dist/angular-drag-drop.js: -------------------------------------------------------------------------------- 1 | (function webpackUniversalModuleDefinition(root, factory) { 2 | if(typeof exports === 'object' && typeof module === 'object') 3 | module.exports = factory(require("angular")); 4 | else if(typeof define === 'function' && define.amd) 5 | define(["angular"], factory); 6 | else if(typeof exports === 'object') 7 | exports["AngularDragDrop"] = factory(require("angular")); 8 | else 9 | root["AngularDragDrop"] = factory(root["angular"]); 10 | })(this, function(__WEBPACK_EXTERNAL_MODULE_1__) { 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 | /******/ exports: {}, 25 | /******/ id: moduleId, 26 | /******/ loaded: false 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.loaded = 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 | /******/ // __webpack_public_path__ 47 | /******/ __webpack_require__.p = ""; 48 | 49 | /******/ // Load entry module and return exports 50 | /******/ return __webpack_require__(0); 51 | /******/ }) 52 | /************************************************************************/ 53 | /******/ ([ 54 | /* 0 */ 55 | /***/ function(module, exports, __webpack_require__) { 56 | 57 | var Angular = __webpack_require__(1); 58 | 59 | module.exports = 'filearts.dragDrop'; 60 | 61 | var mod = Angular.module(module.exports, []); 62 | var stylesheet = 63 | '.drag-active .drop-container{position:relative}.drag-active .drop-container *{pointer-events:none}.drag-active .drop-container:before{position:absolute;top:0;right:0;bottom:0;left:0;z-index:9999;content:""}'; 64 | 65 | mod.factory('dragContext', [ 66 | function() { 67 | var context = {}; 68 | 69 | return reset(); 70 | 71 | function reset() { 72 | return Angular.extend(context, { 73 | data: null, 74 | reset: reset, 75 | start: start, 76 | }); 77 | } 78 | 79 | function start(data) { 80 | context.data = data; 81 | 82 | return data; 83 | } 84 | }, 85 | ]); 86 | 87 | mod.run([ 88 | '$document', 89 | '$rootElement', 90 | '$timeout', 91 | function($document, $rootElement, $timeout) { 92 | $document[0].addEventListener('dragend', onDragEnd, true); 93 | $document[0].addEventListener('drop', onDrop, true); 94 | 95 | loadStyles(stylesheet, $document[0]); 96 | 97 | function onDragEnd() { 98 | clearDragActive(); 99 | } 100 | 101 | function onDrop() { 102 | clearDragActive(); 103 | } 104 | 105 | function clearDragActive() { 106 | $timeout(function() { 107 | var target = findDragActiveTarget($rootElement); 108 | 109 | target.removeClass('drag-active'); 110 | }); 111 | } 112 | 113 | /** 114 | * Load styles into the head element 115 | * 116 | * Source: https://github.com/webmodules/load-styles/blob/master/index.js 117 | */ 118 | function loadStyles(css, doc) { 119 | // default to the global `document` object 120 | if (!doc) doc = document; 121 | 122 | var head = doc.head || doc.getElementsByTagName('head')[0]; 123 | 124 | // no node? create one... 125 | if (!head) { 126 | head = doc.createElement('head'); 127 | var body = doc.body || doc.getElementsByTagName('body')[0]; 128 | if (body) { 129 | body.parentNode.insertBefore(head, body); 130 | } else { 131 | doc.documentElement.appendChild(head); 132 | } 133 | } 134 | 135 | var style = doc.createElement('style'); 136 | style.type = 'text/css'; 137 | if (style.styleSheet) { 138 | // IE 139 | style.styleSheet.cssText = css; 140 | } else { 141 | // the world 142 | style.appendChild(doc.createTextNode(css)); 143 | } 144 | head.appendChild(style); 145 | 146 | return style; 147 | } 148 | }, 149 | ]); 150 | 151 | mod.directive('dragContainer', [ 152 | '$rootElement', 153 | '$parse', 154 | '$timeout', 155 | 'dragContext', 156 | function($rootElement, $parse, $timeout, dragContext) { 157 | return { 158 | restrict: 'A', 159 | link: function($scope, $element, $attrs) { 160 | var onDragStart = $attrs.onDragStart 161 | ? $parse($attrs.onDragStart) 162 | : null; 163 | var onDragEnd = $attrs.onDragEnd 164 | ? $parse($attrs.onDragEnd) 165 | : null; 166 | 167 | $attrs.$addClass('drag-container'); 168 | 169 | $scope.$watch($attrs.dragContainer, function(draggable) { 170 | $attrs.$set( 171 | 'draggable', 172 | typeof draggable === 'undefined' || draggable 173 | ); 174 | }); 175 | 176 | $element.on('dragstart', handleDragStart); 177 | $element.on('dragend', handleDragEnd); 178 | 179 | function handleDragStart(e) { 180 | $timeout( 181 | function() { 182 | var target = findDragActiveTarget($rootElement); 183 | 184 | target.addClass('drag-active'); 185 | }, 186 | 0, 187 | false 188 | ); 189 | 190 | dragContext.start( 191 | $attrs.dragData 192 | ? $scope.$eval($attrs.dragData) 193 | : $element 194 | ); 195 | $element.addClass('drag-container-active'); 196 | 197 | if (onDragStart) { 198 | var locals = { 199 | $event: e, 200 | $dragData: dragContext.data, 201 | }; 202 | 203 | $scope.$apply(function() { 204 | onDragStart($scope, locals); 205 | }); 206 | } 207 | 208 | var targetEvent = e.originalEvent || e; 209 | 210 | if (targetEvent.dataTransfer) { 211 | if ( 212 | (!targetEvent.dataTransfer.items || 213 | !targetEvent.dataTransfer.items.length) && 214 | (!targetEvent.dataTransfer.files || 215 | !targetEvent.dataTransfer.files.length) 216 | ) { 217 | targetEvent.dataTransfer.setData('text', ''); 218 | } 219 | } 220 | } 221 | 222 | function handleDragEnd(e) { 223 | $timeout( 224 | function() { 225 | var target = findDragActiveTarget($rootElement); 226 | 227 | target.removeClass('drag-active'); 228 | }, 229 | 0, 230 | false 231 | ); 232 | 233 | dragContext.reset(); 234 | $element.removeClass('drag-container-active'); 235 | 236 | if (onDragEnd) { 237 | var locals = { 238 | $event: e, 239 | $dragData: dragContext.data, 240 | }; 241 | 242 | $scope.$apply(function() { 243 | onDragEnd($scope, locals); 244 | }); 245 | } 246 | 247 | if (dragContext.lastTarget) { 248 | dragContext.lastTarget.$attrs.$removeClass('drag-over'); 249 | } 250 | } 251 | }, 252 | }; 253 | }, 254 | ]); 255 | 256 | mod.directive('dropContainer', [ 257 | '$document', 258 | '$parse', 259 | '$window', 260 | 'dragContext', 261 | function($document, $parse, $window, dragContext) { 262 | return { 263 | restrict: 'A', 264 | require: 'dropContainer', 265 | controller: 'DropContainerController', 266 | controllerAs: 'dropContainer', 267 | link: function($scope, $element, $attrs, dropContainer) { 268 | var acceptsFn = $attrs.dropAccepts 269 | ? $parse($attrs.dropAccepts) 270 | : function($scope, locals) { 271 | return typeof locals.$dragData !== 'undefined'; 272 | }; 273 | var onDragEnter = $attrs.onDragEnter 274 | ? $parse($attrs.onDragEnter) 275 | : null; 276 | var onDragOver = $attrs.onDragOver 277 | ? $parse($attrs.onDragOver) 278 | : null; 279 | var onDragLeave = $attrs.onDragLeave 280 | ? $parse($attrs.onDragLeave) 281 | : null; 282 | var onDrop = $attrs.onDrop ? $parse($attrs.onDrop) : null; 283 | 284 | $attrs.$addClass('drop-container'); 285 | 286 | $element.on('dragover', handleDragOver); 287 | $element.on('dragenter', handleDragEnter); 288 | $element.on('dragleave', handleDragLeave); 289 | $element.on('drop', handleDrop); 290 | 291 | function handleDragEnter(e) { 292 | if ( 293 | dragContext.lastTarget && 294 | dragContext.lastTarget !== $element 295 | ) { 296 | dragContext.lastTarget.$attrs.$removeClass('drag-over'); 297 | } 298 | 299 | dragContext.lastTarget = { 300 | $attrs: $attrs, 301 | $element: $element, 302 | }; 303 | 304 | var locals = { 305 | $event: e, 306 | $dragData: dragContext.data, 307 | }; 308 | 309 | if (acceptsFn($scope, locals)) { 310 | e.preventDefault(); 311 | 312 | $attrs.$addClass('drag-over'); 313 | 314 | if (onDragEnter) { 315 | $scope.$apply(function() { 316 | onDragEnter($scope, locals); 317 | }); 318 | } 319 | } 320 | } 321 | 322 | function handleDragOver(e) { 323 | var locals = { 324 | $event: e, 325 | $dragData: dragContext.data, 326 | }; 327 | 328 | if (acceptsFn($scope, locals)) { 329 | e.preventDefault(); 330 | 331 | var pos = offset($element); 332 | 333 | $attrs.$addClass('drag-over'); 334 | 335 | var minDistanceSq = Number.MAX_VALUE; 336 | var width = pos.width; 337 | var height = pos.height; 338 | var x = e.pageX - pos.left; 339 | var y = e.pageY - pos.top; 340 | var closestTarget = dropContainer.lastTarget; 341 | 342 | Angular.forEach(dropContainer.targets, function( 343 | dropTarget, 344 | anchor 345 | ) { 346 | var anchorX = width / 2; 347 | var anchorY = height / 2; 348 | 349 | if (anchor.indexOf('left') >= 0) 350 | anchorX = width * 1 / 4; 351 | if (anchor.indexOf('top') >= 0) 352 | anchorY = height * 1 / 4; 353 | if (anchor.indexOf('right') >= 0) 354 | anchorX = width * 3 / 4; 355 | if (anchor.indexOf('bottom') >= 0) 356 | anchorY = height * 3 / 4; 357 | 358 | var distanceSq = 359 | Math.pow(anchorX - x, 2) + 360 | Math.pow(anchorY - y, 2); 361 | 362 | if (distanceSq < minDistanceSq) { 363 | closestTarget = dropTarget; 364 | minDistanceSq = distanceSq; 365 | } 366 | }); 367 | 368 | $scope.$apply(function() { 369 | if (onDragOver) { 370 | onDragOver($scope, locals); 371 | } 372 | 373 | if (!closestTarget) return; 374 | 375 | if (closestTarget !== dropContainer.lastTarget) { 376 | if (dropContainer.lastTarget) { 377 | $attrs.$removeClass( 378 | 'drop-container-' + 379 | dropContainer.lastTarget.anchor 380 | ); 381 | } 382 | 383 | $attrs.$addClass( 384 | 'drop-container-' + closestTarget.anchor 385 | ); 386 | 387 | if (dropContainer.lastTarget) { 388 | dropContainer.lastTarget.handleDragLeave( 389 | e, 390 | locals 391 | ); 392 | } 393 | 394 | closestTarget.handleDragEnter(e, locals); 395 | 396 | dropContainer.lastTarget = closestTarget; 397 | } 398 | 399 | closestTarget.handleDragOver(e); 400 | }); 401 | } 402 | } 403 | 404 | function handleDragLeave(e) { 405 | $attrs.$removeClass('drag-over'); 406 | 407 | var locals = { 408 | $event: e, 409 | $dragData: dragContext.data, 410 | }; 411 | 412 | $scope.$apply(function() { 413 | if (onDragLeave) { 414 | onDragLeave($scope, locals); 415 | } 416 | 417 | if (dropContainer.lastTarget) { 418 | dropContainer.lastTarget.handleDragLeave(e, locals); 419 | } 420 | 421 | if (dropContainer.lastTarget) { 422 | $attrs.$removeClass( 423 | 'drop-container-' + 424 | dropContainer.lastTarget.anchor 425 | ); 426 | 427 | dropContainer.lastTarget = null; 428 | } 429 | }); 430 | } 431 | 432 | function handleDrop(e) { 433 | if (dragContext.lastTarget) { 434 | dragContext.lastTarget.$attrs.$removeClass('drag-over'); 435 | } 436 | 437 | var locals = { 438 | $event: e, 439 | $dragData: dragContext.data, 440 | }; 441 | 442 | if (acceptsFn($scope, locals)) { 443 | e.preventDefault(); 444 | dragContext.reset(); 445 | 446 | $scope.$apply(function() { 447 | if (onDrop) { 448 | onDrop($scope, locals); 449 | } 450 | 451 | if (dropContainer.lastTarget) { 452 | dropContainer.lastTarget.handleDrop(e, locals); 453 | } 454 | }); 455 | } 456 | 457 | if (dropContainer.lastTarget) { 458 | $attrs.$removeClass( 459 | 'drop-container-' + dropContainer.lastTarget.anchor 460 | ); 461 | } 462 | 463 | dropContainer.lastTarget = null; 464 | } 465 | }, 466 | }; 467 | 468 | // Source: https://github.com/angular-ui/bootstrap/blob/master/src/position/position.js 469 | function getRawNode(elem) { 470 | return elem[0] || elem; 471 | } 472 | 473 | // Source: https://github.com/angular-ui/bootstrap/blob/master/src/position/position.js 474 | function offset(elem) { 475 | elem = getRawNode(elem); 476 | 477 | var elemBCR = elem.getBoundingClientRect(); 478 | return { 479 | width: Math.round( 480 | Angular.isNumber(elemBCR.width) 481 | ? elemBCR.width 482 | : elem.offsetWidth 483 | ), 484 | height: Math.round( 485 | Angular.isNumber(elemBCR.height) 486 | ? elemBCR.height 487 | : elem.offsetHeight 488 | ), 489 | top: Math.round( 490 | elemBCR.top + 491 | ($window.pageYOffset || 492 | $document[0].documentElement.scrollTop) 493 | ), 494 | left: Math.round( 495 | elemBCR.left + 496 | ($window.pageXOffset || 497 | $document[0].documentElement.scrollLeft) 498 | ), 499 | }; 500 | } 501 | }, 502 | ]); 503 | 504 | mod.controller('DropContainerController', [ 505 | function() { 506 | var dropContainer = this; 507 | var validAnchors = 'center top top-right right bottom-right bottom bottom-left left top-left'.split( 508 | ' ' 509 | ); 510 | 511 | dropContainer.targets = {}; 512 | dropContainer.lastTarget = null; 513 | 514 | dropContainer.attach = function(dropTarget) { 515 | var anchor = dropTarget.anchor; 516 | 517 | if (validAnchors.indexOf(anchor) < 0) { 518 | throw new Error('Invalid drop target anchor `' + anchor + '`.'); 519 | } 520 | 521 | dropContainer.targets[anchor] = dropTarget; 522 | 523 | return dropTarget; 524 | }; 525 | 526 | dropContainer.detach = function(dropTarget) { 527 | var anchor = dropTarget.anchor; 528 | 529 | if (validAnchors.indexOf(anchor) < 0) { 530 | throw new Error('Invalid drop target anchor `' + anchor + '`.'); 531 | } 532 | 533 | if (!dropContainer.targets[anchor] === dropTarget) { 534 | throw new Error( 535 | 'The indicated drop target is not attached at ' + 536 | 'the anchor `' + 537 | anchor + 538 | '`.' 539 | ); 540 | } 541 | 542 | delete dropContainer.targets[anchor]; 543 | 544 | return dropTarget; 545 | }; 546 | }, 547 | ]); 548 | 549 | mod.directive('dropTarget', [ 550 | '$parse', 551 | function($parse) { 552 | return { 553 | restrict: 'A', 554 | require: ['^dropContainer', 'dropTarget'], 555 | scope: true, 556 | bindToController: { 557 | anchor: '@dropTarget', 558 | }, 559 | controller: Angular.noop, 560 | controllerAs: 'dropTarget', 561 | link: function($scope, $element, $attrs, ctls) { 562 | var dropContainer = ctls[0]; 563 | var dropTarget = ctls[1]; 564 | 565 | $attrs.$addClass('drop-target'); 566 | 567 | dropTarget.$attrs = $attrs; 568 | dropTarget.$scope = $scope; 569 | 570 | $attrs.$addClass( 571 | 'drop-target drop-target-' + dropTarget.anchor 572 | ); 573 | 574 | dropContainer.attach(dropTarget); 575 | 576 | var onDragEnter = dropTarget.$attrs.onDragEnter 577 | ? $parse(dropTarget.$attrs.onDragEnter) 578 | : Angular.noop; 579 | var onDragLeave = dropTarget.$attrs.onDragLeave 580 | ? $parse(dropTarget.$attrs.onDragLeave) 581 | : Angular.noop; 582 | var onDragOver = dropTarget.$attrs.onDragOver 583 | ? $parse(dropTarget.$attrs.onDragOver) 584 | : Angular.noop; 585 | var onDrop = dropTarget.$attrs.onDrop 586 | ? $parse(dropTarget.$attrs.onDrop) 587 | : Angular.noop; 588 | 589 | dropTarget.handleDragEnter = function(e, locals) { 590 | onDragEnter(dropTarget.$scope, locals); 591 | }; 592 | 593 | dropTarget.handleDragLeave = function(e, locals) { 594 | onDragLeave(dropTarget.$scope, locals); 595 | }; 596 | 597 | dropTarget.handleDragOver = function(e, locals) { 598 | onDragOver(dropTarget.$scope, locals); 599 | }; 600 | 601 | dropTarget.handleDrop = function(e, locals) { 602 | onDrop(dropTarget.$scope, locals); 603 | }; 604 | 605 | $scope.$on('$destroy', function() { 606 | dropContainer.detach(dropTarget); 607 | }); 608 | }, 609 | }; 610 | }, 611 | ]); 612 | 613 | function findDragActiveTarget(jqLite) { 614 | var body = jqLite.find('body'); 615 | 616 | return body.length ? body : jqLite; 617 | } 618 | 619 | 620 | /***/ }, 621 | /* 1 */ 622 | /***/ function(module, exports) { 623 | 624 | module.exports = __WEBPACK_EXTERNAL_MODULE_1__; 625 | 626 | /***/ } 627 | /******/ ]) 628 | }); 629 | ; -------------------------------------------------------------------------------- /dist/angular-drag-drop.min.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("angular")):"function"==typeof define&&define.amd?define(["angular"],e):"object"==typeof exports?exports.AngularDragDrop=e(require("angular")):t.AngularDragDrop=e(t.angular)}(this,function(t){return function(t){function e(a){if(r[a])return r[a].exports;var n=r[a]={exports:{},id:a,loaded:!1};return t[a].call(n.exports,n,n.exports,e),n.loaded=!0,n.exports}var r={};return e.m=t,e.c=r,e.p="",e(0)}([function(t,e,r){function a(t){var e=t.find("body");return e.length?e:t}var n=r(1);t.exports="filearts.dragDrop";var o=n.module(t.exports,[]),d='.drag-active .drop-container{position:relative}.drag-active .drop-container *{pointer-events:none}.drag-active .drop-container:before{position:absolute;top:0;right:0;bottom:0;left:0;z-index:9999;content:""}';o.factory("dragContext",[function(){function t(){return n.extend(r,{data:null,reset:t,start:e})}function e(t){return r.data=t,t}var r={};return t()}]),o.run(["$document","$rootElement","$timeout",function(t,e,r){function n(){i()}function o(){i()}function i(){r(function(){var t=a(e);t.removeClass("drag-active")})}function l(t,e){e||(e=document);var r=e.head||e.getElementsByTagName("head")[0];if(!r){r=e.createElement("head");var a=e.body||e.getElementsByTagName("body")[0];a?a.parentNode.insertBefore(r,a):e.documentElement.appendChild(r)}var n=e.createElement("style");return n.type="text/css",n.styleSheet?n.styleSheet.cssText=t:n.appendChild(e.createTextNode(t)),r.appendChild(n),n}t[0].addEventListener("dragend",n,!0),t[0].addEventListener("drop",o,!0),l(d,t[0])}]),o.directive("dragContainer",["$rootElement","$parse","$timeout","dragContext",function(t,e,r,n){return{restrict:"A",link:function(o,d,i){function l(e){if(r(function(){var e=a(t);e.addClass("drag-active")},0,!1),n.start(i.dragData?o.$eval(i.dragData):d),d.addClass("drag-container-active"),g){var l={$event:e,$dragData:n.data};o.$apply(function(){g(o,l)})}var s=e.originalEvent||e;s.dataTransfer&&(s.dataTransfer.items&&s.dataTransfer.items.length||s.dataTransfer.files&&s.dataTransfer.files.length||s.dataTransfer.setData("text",""))}function s(e){if(r(function(){var e=a(t);e.removeClass("drag-active")},0,!1),n.reset(),d.removeClass("drag-container-active"),c){var i={$event:e,$dragData:n.data};o.$apply(function(){c(o,i)})}n.lastTarget&&n.lastTarget.$attrs.$removeClass("drag-over")}var g=i.onDragStart?e(i.onDragStart):null,c=i.onDragEnd?e(i.onDragEnd):null;i.$addClass("drag-container"),o.$watch(i.dragContainer,function(t){i.$set("draggable","undefined"==typeof t||t)}),d.on("dragstart",l),d.on("dragend",s)}}}]),o.directive("dropContainer",["$document","$parse","$window","dragContext",function(t,e,r,a){function o(t){return t[0]||t}function d(e){e=o(e);var a=e.getBoundingClientRect();return{width:Math.round(n.isNumber(a.width)?a.width:e.offsetWidth),height:Math.round(n.isNumber(a.height)?a.height:e.offsetHeight),top:Math.round(a.top+(r.pageYOffset||t[0].documentElement.scrollTop)),left:Math.round(a.left+(r.pageXOffset||t[0].documentElement.scrollLeft))}}return{restrict:"A",require:"dropContainer",controller:"DropContainerController",controllerAs:"dropContainer",link:function(t,r,o,i){function l(e){a.lastTarget&&a.lastTarget!==r&&a.lastTarget.$attrs.$removeClass("drag-over"),a.lastTarget={$attrs:o,$element:r};var n={$event:e,$dragData:a.data};p(t,n)&&(e.preventDefault(),o.$addClass("drag-over"),u&&t.$apply(function(){u(t,n)}))}function s(e){var l={$event:e,$dragData:a.data};if(p(t,l)){e.preventDefault();var s=d(r);o.$addClass("drag-over");var g=Number.MAX_VALUE,c=s.width,u=s.height,v=e.pageX-s.left,h=e.pageY-s.top,$=i.lastTarget;n.forEach(i.targets,function(t,e){var r=c/2,a=u/2;e.indexOf("left")>=0&&(r=1*c/4),e.indexOf("top")>=0&&(a=1*u/4),e.indexOf("right")>=0&&(r=3*c/4),e.indexOf("bottom")>=0&&(a=3*u/4);var n=Math.pow(r-v,2)+Math.pow(a-h,2);n