├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── LICENSE ├── README.md ├── build ├── rollup.config.js └── webpack.config.js ├── docs ├── App.vue ├── components │ └── Frame.vue ├── examples │ ├── DragAround │ │ ├── CustomDragLayer │ │ │ ├── Box.vue │ │ │ ├── BoxDragPreview.vue │ │ │ ├── Container.vue │ │ │ ├── CustomDragLayer.vue │ │ │ ├── CustomDragLayerContainer.vue │ │ │ ├── DraggableBox.vue │ │ │ ├── ItemTypes.js │ │ │ └── snapToGrid.js │ │ └── Naive │ │ │ ├── Box.vue │ │ │ ├── BoxContainer.vue │ │ │ ├── Container.vue │ │ │ └── ItemTypes.js │ ├── Dustbin │ │ ├── CopyOrMove │ │ │ ├── Box.vue │ │ │ ├── Container.vue │ │ │ └── Dustbin.vue │ │ ├── MultipleTargets │ │ │ ├── Box.vue │ │ │ ├── Container.vue │ │ │ ├── Dustbin.vue │ │ │ └── ItemTypes.js │ │ ├── SingleTarget │ │ │ ├── Box.vue │ │ │ ├── Container.vue │ │ │ ├── Dustbin.vue │ │ │ └── ItemTypes.js │ │ └── SingleTargetInFrame │ │ │ └── Container.vue │ ├── Nesting │ │ ├── DragSources │ │ │ ├── Colors.js │ │ │ ├── Container.vue │ │ │ ├── SourceBox.vue │ │ │ ├── StatefulSourceBox.vue │ │ │ ├── StatefulTargetBox.vue │ │ │ └── TargetBox.vue │ │ └── DropTargets │ │ │ ├── Box.vue │ │ │ ├── Container.vue │ │ │ ├── Dustbin.vue │ │ │ └── ItemTypes.js │ └── Sortable │ │ ├── CancelOnDropOutside │ │ ├── Card.vue │ │ ├── CardList.vue │ │ ├── Container.vue │ │ └── ItemTypes.js │ │ └── Simple │ │ ├── Card.vue │ │ ├── Container.vue │ │ └── ItemTypes.js ├── index.html ├── index.js ├── pages │ └── Home.vue └── routes.js ├── package-lock.json ├── package.json └── src ├── DragDropContext.js ├── DragDropContextProvider.vue ├── DragLayer.js ├── DragSource.js ├── DropTarget.js ├── areOptionsEqual.js ├── arrayEqual.js ├── createSourceConnector.js ├── createSourceFactory.js ├── createSourceMonitor.js ├── createTargetConnector.js ├── createTargetFactory.js ├── createTargetMonitor.js ├── index.js ├── registerSource.js └── registerTarget.js /.eslintignore: -------------------------------------------------------------------------------- 1 | *.md 2 | node_modules 3 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // https://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parserOptions: { 6 | parser: 'babel-eslint' 7 | }, 8 | env: { 9 | browser: true, 10 | }, 11 | extends: [ 12 | // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention 13 | // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules. 14 | 'plugin:vue/essential', 15 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md 16 | 'standard' 17 | ], 18 | // required to lint *.vue files 19 | plugins: [ 20 | 'vue' 21 | ], 22 | // add your custom rules here 23 | rules: { 24 | // allow async-await 25 | 'generator-star-spacing': 'off', 26 | // allow debugger during development 27 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | dist/ 61 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Jens Haase 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 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-react-dnd 2 | 3 | Vue Drag and Drop Library based on [react-dnd](https://github.com/react-dnd/react-dnd). 4 | 5 | vue-react-dnd has no dependency to React itself, since it only uses 6 | `dnd-core` as dependency. 7 | 8 | ## Install 9 | 10 | ``` 11 | npm install --save vue-react-dnd 12 | npm install --save react-dnd-html5-backend 13 | ``` 14 | 15 | ## Usage 16 | 17 | Make sure you are familiar with react-dnd concepts. An overview of all 18 | the concepts can be found here: 19 | 20 | 21 | ### DragSource 22 | 23 | To make a vue component draggable you need to add the `DragSource` 24 | mixin, define the `dragSource` configuration and add the `v-dragSource` 25 | directive to the HTML. 26 | 27 | ```html 28 | 32 |
Drag me
33 | ``` 34 | 35 | ```javascript 36 | // 1. Import DragSource Mixin 37 | import { DragSource } from 'vue-react-dnd' 38 | 39 | new Vue({ 40 | 41 | // ... 42 | 43 | // 2. Add DragSource Mixin to Component 44 | mixins: [DragSource], 45 | 46 | data () { 47 | return { 48 | isDragging: false 49 | } 50 | }, 51 | 52 | // 3. Definine type function, specs object and collect function 53 | dragSource: { 54 | // Required. Returns either a string, an ES6 symbol. 55 | // Only the drop targets registered for the same type will react to the 56 | // items produced by this drag source. 57 | // Read the react-dnd documentation to learn more about the items and types. 58 | type () { 59 | return ItemTypes.BOX 60 | }, 61 | 62 | // Required. A plain JavaScript object with a few allowed methods on it. 63 | // It describes how the drag source reacts to the drag and drop events. 64 | // Read the react-dnd documentation to learn more about the drag source specification. 65 | specs: { 66 | beginDrag () { 67 | return { 68 | name: 'Drag me' 69 | } 70 | }, 71 | 72 | endDrag (monitor) { 73 | const item = monitor.getItem() 74 | const dropResult = monitor.getDropResult() 75 | 76 | if (dropResult) { 77 | alert('You dropped me!') 78 | } 79 | } 80 | }, 81 | // Optional. The collecting function receives two parameters: connect and monitor. 82 | // It can update a components internal state. 83 | // Read the react-dnd documentation an introduction to the monitors, the connectors. 84 | collect (connect, monitor) { 85 | this.isDragging = monitor.isDragging() 86 | } 87 | } 88 | 89 | // ... 90 | 91 | }) 92 | 93 | ``` 94 | 95 | ### DropTarget 96 | 97 | To make a vue component a drop traget you need to add the `DropTarget` 98 | mixin, define the `dropTarget` configuration and add a `v-dropTarget` 99 | directive to the HTML. 100 | 101 | ```html 102 | 106 |
107 | Drag a box here 108 |
109 | ``` 110 | 111 | ```javascript 112 | // 1. Import DropTarget Mixin 113 | import { DropTarget } from 'vue-react-dnd' 114 | 115 | new Vue({ 116 | 117 | // ... 118 | 119 | // 2. Add DropTarget Mixin to Component 120 | mixins: [DropTarget], 121 | 122 | data () { 123 | return { 124 | isOver: false, 125 | canDrop: false 126 | } 127 | }, 128 | 129 | computed: { 130 | isActive () { 131 | return this.canDrop && this.isOver 132 | } 133 | }, 134 | 135 | // 2. Definine type function, specs object and collect function 136 | dropTarget: { 137 | // Required. Returns a string, an ES6 symbol, an array of either. 138 | // This drop target will only react to the items produced by the drag sources 139 | // of the specified type or types. 140 | // Read the react-dnd documentation to learn more about the items and types. 141 | type () { 142 | return ItemsTypes.BOX 143 | }, 144 | 145 | // Required. A plain JavaScript object with a few allowed methods on it. 146 | // It describes how the drop target reacts to the drag and drop events. 147 | // See the react-dnd documentaion where the drop target specification is 148 | // described in detail. 149 | specs: { 150 | drop () { 151 | return { name: 'Dustbin' } 152 | } 153 | }, 154 | // Optional. The collecting function receives two parameters: connect and monitor. 155 | // It can update a components internal state. 156 | // Read the react-dnd documentation an introduction to the monitors, the connectors. 157 | collect (connect, monitor) { 158 | this.isOver = monitor.isOver() 159 | this.canDrop = monitor.canDrop() 160 | } 161 | } 162 | 163 | // ... 164 | 165 | }) 166 | 167 | ``` 168 | 169 | ### DragLayer 170 | 171 | see: `docs/examples/DragAround/CustomDragLayer/CustomDragLayer.vue` 172 | 173 | ### DragDropContext 174 | 175 | Use the `DragDropContext` mixin to add a Drag and Drop Backend to your 176 | application. 177 | 178 | ```html 179 | 180 | ``` 181 | 182 | ```javascript 183 | import { DragDropContext } from 'vue-react-dnd' 184 | import HTML5Backend from 'react-dnd-html5-backend' 185 | 186 | new Vue({ 187 | 188 | // ... 189 | 190 | mixins: [DragDropContext(HTML5Backend)] 191 | 192 | // ... 193 | 194 | }) 195 | 196 | ``` 197 | 198 | ### DragDropContextProvider 199 | 200 | As alternative to the `DragDropContext` use the 201 | `DragDropContextProvider` component to add a Drag and Drop Backend to 202 | your application. 203 | 204 | ```html 205 | 206 | 207 | 208 | ``` 209 | 210 | ```javascript 211 | import { DragDropContextProvider } from 'vue-react-dnd' 212 | import HTML5Backend from 'react-dnd-html5-backend' 213 | 214 | new Vue({ 215 | 216 | // ... 217 | 218 | components: { 219 | DragDropContextProvider 220 | }, 221 | 222 | data () { 223 | return { 224 | html5Backend: HTML5Backend 225 | } 226 | } 227 | 228 | // ... 229 | 230 | }) 231 | 232 | ``` 233 | 234 | ## Differences to react-dnd 235 | 236 | ### Specs function without `props` parameter 237 | 238 | The specs functions in `vue-react-dnd` do not have the `props` 239 | parameter, since all values of the component can be directly accessed 240 | using `this`. 241 | 242 | ### Collect Function 243 | 244 | In `react-dnd` the collect function always has to return a plain 245 | JavaScript Object that updates the internal state. In `vue-react-dnd` 246 | the internal state can be set directly using `this`. Therefore the 247 | collect function is optional in `vue-react-dnd`. 248 | 249 | ## Examples 250 | 251 | Examples can be found in the `docs/examples` directory. Most of the 252 | examples are reimplementations of the examples provided by 253 | react-dnd. A live version of the examples can be found here: 254 | 255 | 256 | 257 | ## Development 258 | 259 | Run examples: `npm run dev` 260 | 261 | Lint code: `npm run lint` 262 | 263 | Publish documentation to gh-pages: `npm run publish:doc` 264 | 265 | # Publish new version 266 | 267 | 1. `npm run publish:doc` 268 | 2. `npm run build:lib` 269 | 3. `npm version ` 270 | 4. `npm publish` 271 | -------------------------------------------------------------------------------- /build/rollup.config.js: -------------------------------------------------------------------------------- 1 | import vue from 'rollup-plugin-vue' 2 | import buble from 'rollup-plugin-buble' 3 | 4 | export default { 5 | input: 'src/index.js', // Path relative to package.json 6 | output: { 7 | name: 'VueReactDnd', 8 | exports: 'named' 9 | }, 10 | plugins: [ 11 | vue({ 12 | css: true, // Dynamically inject css as a 27 | -------------------------------------------------------------------------------- /docs/examples/DragAround/CustomDragLayer/BoxDragPreview.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 39 | 40 | 47 | -------------------------------------------------------------------------------- /docs/examples/DragAround/CustomDragLayer/Container.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 25 | 26 | 29 | -------------------------------------------------------------------------------- /docs/examples/DragAround/CustomDragLayer/CustomDragLayer.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 69 | 70 | 81 | -------------------------------------------------------------------------------- /docs/examples/DragAround/CustomDragLayer/CustomDragLayerContainer.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 75 | 76 | 84 | -------------------------------------------------------------------------------- /docs/examples/DragAround/CustomDragLayer/DraggableBox.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 59 | 60 | 70 | -------------------------------------------------------------------------------- /docs/examples/DragAround/CustomDragLayer/ItemTypes.js: -------------------------------------------------------------------------------- 1 | export default { 2 | BOX: 'box' 3 | } 4 | -------------------------------------------------------------------------------- /docs/examples/DragAround/CustomDragLayer/snapToGrid.js: -------------------------------------------------------------------------------- 1 | export default function snapToGrid (x, y) { 2 | const snappedX = Math.round(x / 32) * 32 3 | const snappedY = Math.round(y / 32) * 32 4 | 5 | return [snappedX, snappedY] 6 | } 7 | -------------------------------------------------------------------------------- /docs/examples/DragAround/Naive/Box.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 44 | 45 | 54 | -------------------------------------------------------------------------------- /docs/examples/DragAround/Naive/BoxContainer.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 64 | 65 | 73 | -------------------------------------------------------------------------------- /docs/examples/DragAround/Naive/Container.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 25 | 26 | 29 | -------------------------------------------------------------------------------- /docs/examples/DragAround/Naive/ItemTypes.js: -------------------------------------------------------------------------------- 1 | export default { 2 | BOX: 'box' 3 | } 4 | -------------------------------------------------------------------------------- /docs/examples/Dustbin/CopyOrMove/Box.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 58 | 59 | 74 | -------------------------------------------------------------------------------- /docs/examples/Dustbin/CopyOrMove/Container.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 31 | 32 | 35 | -------------------------------------------------------------------------------- /docs/examples/Dustbin/CopyOrMove/Dustbin.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 56 | 57 | 80 | -------------------------------------------------------------------------------- /docs/examples/Dustbin/MultipleTargets/Box.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 37 | 38 | 57 | -------------------------------------------------------------------------------- /docs/examples/Dustbin/MultipleTargets/Container.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 70 | 71 | 74 | -------------------------------------------------------------------------------- /docs/examples/Dustbin/MultipleTargets/Dustbin.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 53 | 54 | 77 | -------------------------------------------------------------------------------- /docs/examples/Dustbin/MultipleTargets/ItemTypes.js: -------------------------------------------------------------------------------- 1 | export default { 2 | FOOD: 'food', 3 | GLASS: 'glass', 4 | PAPER: 'paper' 5 | } 6 | -------------------------------------------------------------------------------- /docs/examples/Dustbin/SingleTarget/Box.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 46 | 47 | 62 | -------------------------------------------------------------------------------- /docs/examples/Dustbin/SingleTarget/Container.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 29 | 30 | 33 | -------------------------------------------------------------------------------- /docs/examples/Dustbin/SingleTarget/Dustbin.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 49 | 50 | 73 | -------------------------------------------------------------------------------- /docs/examples/Dustbin/SingleTarget/ItemTypes.js: -------------------------------------------------------------------------------- 1 | export default { 2 | BOX: 'box' 3 | } 4 | -------------------------------------------------------------------------------- /docs/examples/Dustbin/SingleTargetInFrame/Container.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 40 | 41 | 44 | -------------------------------------------------------------------------------- /docs/examples/Nesting/DragSources/Colors.js: -------------------------------------------------------------------------------- 1 | export default { 2 | YELLOW: 'yellow', 3 | BLUE: 'blue' 4 | } 5 | -------------------------------------------------------------------------------- /docs/examples/Nesting/DragSources/Container.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 42 | 43 | 60 | -------------------------------------------------------------------------------- /docs/examples/Nesting/DragSources/SourceBox.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 65 | 66 | 77 | -------------------------------------------------------------------------------- /docs/examples/Nesting/DragSources/StatefulSourceBox.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 24 | -------------------------------------------------------------------------------- /docs/examples/Nesting/DragSources/StatefulTargetBox.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 29 | 30 | 33 | -------------------------------------------------------------------------------- /docs/examples/Nesting/DragSources/TargetBox.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 60 | 61 | 75 | -------------------------------------------------------------------------------- /docs/examples/Nesting/DropTargets/Box.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 25 | 26 | 35 | -------------------------------------------------------------------------------- /docs/examples/Nesting/DropTargets/Container.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 37 | 38 | 41 | -------------------------------------------------------------------------------- /docs/examples/Nesting/DropTargets/Dustbin.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 70 | 71 | 90 | -------------------------------------------------------------------------------- /docs/examples/Nesting/DropTargets/ItemTypes.js: -------------------------------------------------------------------------------- 1 | export default { 2 | BOX: 'box' 3 | } 4 | -------------------------------------------------------------------------------- /docs/examples/Sortable/CancelOnDropOutside/Card.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 67 | 68 | 81 | -------------------------------------------------------------------------------- /docs/examples/Sortable/CancelOnDropOutside/CardList.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 65 | 66 | 69 | -------------------------------------------------------------------------------- /docs/examples/Sortable/CancelOnDropOutside/Container.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 18 | 19 | 22 | -------------------------------------------------------------------------------- /docs/examples/Sortable/CancelOnDropOutside/ItemTypes.js: -------------------------------------------------------------------------------- 1 | export default { 2 | CARD: 'card' 3 | } 4 | -------------------------------------------------------------------------------- /docs/examples/Sortable/Simple/Card.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 90 | 91 | 104 | -------------------------------------------------------------------------------- /docs/examples/Sortable/Simple/Container.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 46 | 47 | 50 | -------------------------------------------------------------------------------- /docs/examples/Sortable/Simple/ItemTypes.js: -------------------------------------------------------------------------------- 1 | export default { 2 | CARD: 'card' 3 | } 4 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | vue-react-dnd 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | import App from './App' 4 | import {routes} from './routes' 5 | 6 | const debug = process.env.NODE_ENV !== 'production' 7 | 8 | Vue.use(VueRouter) 9 | 10 | export const router = new VueRouter({ 11 | mode: debug ? 'hash' : 'history', 12 | base: debug ? '/' : 'vue-react-dnd', 13 | routes: routes 14 | }) 15 | 16 | /* eslint-disable no-new */ 17 | new Vue({ 18 | el: '#app', 19 | components: { App }, 20 | render: mount => mount(App), 21 | router 22 | }) 23 | -------------------------------------------------------------------------------- /docs/pages/Home.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 38 | 39 | 42 | -------------------------------------------------------------------------------- /docs/routes.js: -------------------------------------------------------------------------------- 1 | import DustbinSingleTargetContainer from './examples/Dustbin/SingleTarget/Container' 2 | import DustbinSingleTargetInFrameContainer from './examples/Dustbin/SingleTargetInFrame/Container' 3 | import DustbinCopyOrMoveContainer from './examples/Dustbin/CopyOrMove/Container' 4 | import DustbinMultipleTargetsContainer from './examples/Dustbin/MultipleTargets/Container' 5 | import DragAroundNavieContainer from './examples/DragAround/Naive/Container' 6 | import DragAroundCustomDragLayerContainer from './examples/DragAround/CustomDragLayer/Container' 7 | import NestingDragSourcesContainer from './examples/Nesting/DragSources/Container' 8 | import NestingDropTargetsContainer from './examples/Nesting/DropTargets/Container' 9 | import SortableSimpleContainer from './examples/Sortable/Simple/Container' 10 | import SortableCancelOnDropOutsideContainer from './examples/Sortable/CancelOnDropOutside/Container' 11 | import PageHome from './pages/Home' 12 | 13 | export const routes = [ 14 | { 15 | path: '/', 16 | name: 'home', 17 | component: PageHome 18 | }, 19 | { 20 | path: '/example/dustbin/single-target', 21 | name: 'example/dustbin/single-target', 22 | component: DustbinSingleTargetContainer 23 | }, 24 | { 25 | path: '/example/dustbin/single-target-in-frame', 26 | name: 'example/dustbin/single-target-in-frame', 27 | component: DustbinSingleTargetInFrameContainer 28 | }, 29 | { 30 | path: '/example/dustbin/copy-or-move', 31 | name: 'example/dustbin/copy-or-move', 32 | component: DustbinCopyOrMoveContainer 33 | }, 34 | { 35 | path: '/example/dustbin/multiple-targets', 36 | name: 'example/dustbin/multiple-targets', 37 | component: DustbinMultipleTargetsContainer 38 | }, 39 | 40 | { 41 | path: '/example/drag-around/naive', 42 | name: 'example/drag-around/naive', 43 | component: DragAroundNavieContainer 44 | }, 45 | { 46 | path: '/example/drag-around/custom-drag-layer', 47 | name: 'example/drag-around/custom-drag-layer', 48 | component: DragAroundCustomDragLayerContainer 49 | }, 50 | 51 | { 52 | path: '/example/nesting/drag-sources', 53 | name: 'example/nesting/drag-sources', 54 | component: NestingDragSourcesContainer 55 | }, 56 | { 57 | path: '/example/nesting/drop-targets', 58 | name: 'example/nesting/drop-targets', 59 | component: NestingDropTargetsContainer 60 | }, 61 | 62 | { 63 | path: '/example/sortable/simple', 64 | name: 'example/sortable/simple', 65 | component: SortableSimpleContainer 66 | }, 67 | { 68 | path: '/example/sortable/cancel-on-drop-outside', 69 | name: 'example/sortable/cancel-on-drop-outside', 70 | component: SortableCancelOnDropOutsideContainer 71 | }, 72 | ] 73 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-react-dnd", 3 | "version": "1.1.0", 4 | "description": "Vue Drag and Drop Library based on react-dnd", 5 | "main": "dist/vue-react-dnd.umd.js", 6 | "module": "dist/vue-react-dnd.esm.js", 7 | "unpkg": "dist/vue-react-dnd.min.js", 8 | "scripts": { 9 | "dev": "webpack-dev-server --inline --progress --config build/webpack.config.js", 10 | "build:umd": "rollup --config build/rollup.config.js --format umd --file dist/vue-react-dnd.umd.js", 11 | "build:es": "rollup --config build/rollup.config.js --format es --file dist/vue-react-dnd.es.js", 12 | "build:unpkg": "rollup --config build/rollup.config.js --format iife --file dist/vue-react-dnd.min.js", 13 | "build:lib": "npm run build:umd && npm run build:es && npm run build:unpkg", 14 | "build:doc": "webpack --env.production --config build/webpack.config.js", 15 | "lint": "eslint --ext .js,.vue src/**/* docs/**/* build/**/* --quiet", 16 | "publish:doc": "npm run build:doc && gh-pages -d dist" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/jenshaase/vue-react-dnd.git" 21 | }, 22 | "keywords": [ 23 | "drag", 24 | "drop", 25 | "dnd", 26 | "drag", 27 | "and", 28 | "drop", 29 | "vue", 30 | "vuejs" 31 | ], 32 | "author": "Jens Haase", 33 | "license": "MIT", 34 | "bugs": { 35 | "url": "https://github.com/jenshaase/vue-react-dnd/issues" 36 | }, 37 | "homepage": "https://github.com/jenshaase/vue-react-dnd#readme", 38 | "dependencies": { 39 | "disposables": "^1.0.2", 40 | "dnd-core": "^2.5.4" 41 | }, 42 | "peerDependencies": { 43 | "vue": "^2.5.2" 44 | }, 45 | "devDependencies": { 46 | "assets-webpack-plugin": "^3.5.1", 47 | "babel-core": "^6.26.0", 48 | "babel-eslint": "^8.2.1", 49 | "babel-loader": "^7.1.2", 50 | "css-loader": "^0.28.9", 51 | "eslint": "^4.16.0", 52 | "eslint-config-standard": "^11.0.0-beta.0", 53 | "eslint-loader": "^1.9.0", 54 | "eslint-plugin-import": "^2.8.0", 55 | "eslint-plugin-node": "^5.2.1", 56 | "eslint-plugin-promise": "^3.6.0", 57 | "eslint-plugin-standard": "^3.0.1", 58 | "eslint-plugin-vue": "^4.2.0", 59 | "extract-text-webpack-plugin": "^3.0.2", 60 | "gh-pages": "^1.1.0", 61 | "html-webpack-plugin": "^2.30.1", 62 | "node-sass": "^4.7.2", 63 | "react-dnd-html5-backend": "^2.5.4", 64 | "rollup": "^0.65.0", 65 | "rollup-plugin-buble": "^0.19.2", 66 | "rollup-plugin-vue": "^4.3.2", 67 | "sass-loader": "^6.0.6", 68 | "style-loader": "^0.19.1", 69 | "uglifyjs-webpack-plugin": "^1.1.8", 70 | "vue": "^2.5.13", 71 | "vue-loader": "^13.7.0", 72 | "vue-router": "^3.0.1", 73 | "vue-template-compiler": "^2.5.13", 74 | "webpack": "^3.10.0", 75 | "webpack-dev-server": "^2.11.1" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/DragDropContext.js: -------------------------------------------------------------------------------- 1 | import { DragDropManager } from 'dnd-core' 2 | 3 | export default function DragDropContext (backendOrModule, context = {}) { 4 | let backend = backendOrModule 5 | if (typeof backend === 'object' && typeof backend.default === 'function') { 6 | backend = backend.default 7 | } 8 | 9 | return { 10 | provide: { 11 | dragDropManager: new DragDropManager(backend, context) 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/DragDropContextProvider.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 44 | -------------------------------------------------------------------------------- /src/DragLayer.js: -------------------------------------------------------------------------------- 1 | export default { 2 | inject: ['dragDropManager'], 3 | 4 | data () { 5 | return { 6 | dragLayerUnsubscribeFromOffsetChange: null, 7 | dragLayerUnsubscribeFromStateChange: null, 8 | dragLayerIsCurrentlyMounted: false 9 | } 10 | }, 11 | 12 | mounted () { 13 | this.dragLayerIsCurrentlyMounted = true 14 | 15 | let monitor = this.dragDropManager.getMonitor() 16 | this.dragLayerUnsubscribeFromOffsetChange = monitor.subscribeToOffsetChange( 17 | this.dragLayerHandleChange 18 | ) 19 | 20 | this.dragLayerUnsubscribeFromStateChange = monitor.subscribeToStateChange( 21 | this.dragLayerHandleChange 22 | ) 23 | 24 | this.dragLayerHandleChange() 25 | }, 26 | 27 | beforeDestroy () { 28 | this.dragLayerIsCurrentlyMounted = false 29 | this.dragLayerUnsubscribeFromOffsetChange() 30 | this.dragLayerUnsubscribeFromStateChange() 31 | }, 32 | 33 | methods: { 34 | dragLayerHandleChange () { 35 | if (!this.dragLayerIsCurrentlyMounted) { 36 | return 37 | } 38 | 39 | if (this.$options && this.$options.dragLayer && this.$options.dragLayer.collect) { 40 | let f = this.$options.dragLayer.collect.bind(this) 41 | f(this.dragDropManager.getMonitor()) 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/DragSource.js: -------------------------------------------------------------------------------- 1 | import registerSource from './registerSource' 2 | import createSourceFactory from './createSourceFactory' 3 | import createSourceMonitor from './createSourceMonitor' 4 | import createSourceConnector from './createSourceConnector' 5 | import arrayEqual from './arrayEqual' 6 | import { Disposable, CompositeDisposable, SerialDisposable } from 'disposables' 7 | 8 | export default { 9 | inject: ['dragDropManager'], 10 | 11 | data () { 12 | let sourceMonitor = createSourceMonitor(this.dragDropManager) 13 | const createSource = createSourceFactory(this.$options.dragSource.specs, this) 14 | 15 | return { 16 | dragSourceHandler: createSource(sourceMonitor), 17 | dragSourceHandlerMonitor: sourceMonitor, 18 | dragSourceHandlerConnector: createSourceConnector(this.dragDropManager.getBackend()), 19 | dragSourceDisposable: new SerialDisposable(), 20 | isDragSourceCurrentlyMounted: false, 21 | currentDragSourceType: null, 22 | dragSourceHandlerId: null 23 | } 24 | }, 25 | 26 | mounted () { 27 | this.isDragSourceCurrentlyMounted = true 28 | this.dragSourceDisposable = new SerialDisposable() 29 | this.currentDragSourceType = null 30 | 31 | this.receiveDragSourceProps() 32 | this.handleDragSourceChange() 33 | }, 34 | 35 | beforeUpdate () { 36 | this.receiveDragSourceProps() 37 | this.handleDragSourceChange() 38 | }, 39 | 40 | beforeDestroy () { 41 | this.disposeDragSource() 42 | this.isDragSourceCurrentlyMounted = false 43 | }, 44 | 45 | directives: { 46 | dragSource: { 47 | inserted: function (el, binding, vnode) { 48 | vnode.context.dragSourceHandlerConnector.hooks.dragSource(el) 49 | }, 50 | componentUpdated: function (el, binding, vnode) { 51 | vnode.context.dragSourceHandlerConnector.hooks.dragSource(el) 52 | } 53 | }, 54 | dragPreview: { 55 | inserted: function (el, binding, vnode) { 56 | vnode.context.dragSourceHandlerConnector.hooks.dragPreview(el) 57 | }, 58 | componentUpdated: function (el, binding, vnode) { 59 | vnode.context.dragSourceHandlerConnector.hooks.dragPreview(el) 60 | } 61 | } 62 | }, 63 | 64 | methods: { 65 | disposeDragSource () { 66 | this.dragSourceDisposable.dispose() 67 | this.dragSourceHandlerConnector.receiveHandlerId(null) 68 | }, 69 | 70 | handleDragSourceChange () { 71 | if (this.$options && this.$options.dragSource && this.$options.dragSource.collect) { 72 | let f = this.$options.dragSource.collect.bind(this) 73 | f(this.dragSourceHandlerConnector.hooks, this.dragSourceHandlerMonitor) 74 | } 75 | }, 76 | 77 | receiveDragSourceProps () { 78 | let typeF = this.$options.dragSource.type.bind(this) 79 | this.receiveDragSourceType(typeF()) 80 | }, 81 | 82 | receiveDragSourceType (type) { 83 | if (arrayEqual(type, this.currentDragSourceType)) { 84 | return 85 | } 86 | 87 | this.currentDragSourceType = type 88 | 89 | const { handlerId, unregister } = registerSource( 90 | type, 91 | this.dragSourceHandler, 92 | this.dragDropManager 93 | ) 94 | 95 | this.dragSourceHandlerId = handlerId 96 | this.dragSourceHandlerMonitor.receiveHandlerId(handlerId) 97 | this.dragSourceHandlerConnector.receiveHandlerId(handlerId) 98 | 99 | const globalMonitor = this.dragDropManager.getMonitor() 100 | const unsubscribe = globalMonitor.subscribeToStateChange( 101 | this.handleDragSourceChange, 102 | { handlerIds: [handlerId] } 103 | ) 104 | 105 | this.dragSourceDisposable.setDisposable( 106 | new CompositeDisposable( 107 | new Disposable(unsubscribe), 108 | new Disposable(unregister) 109 | ) 110 | ) 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/DropTarget.js: -------------------------------------------------------------------------------- 1 | import registerTarget from './registerTarget' 2 | import createTargetFactory from './createTargetFactory' 3 | import createTargetMonitor from './createTargetMonitor' 4 | import createTargetConnector from './createTargetConnector' 5 | import arrayEqual from './arrayEqual' 6 | import { Disposable, CompositeDisposable, SerialDisposable } from 'disposables' 7 | 8 | export default { 9 | inject: ['dragDropManager'], 10 | 11 | data () { 12 | let targetMonitor = createTargetMonitor(this.dragDropManager) 13 | let createTarget = createTargetFactory(this.$options.dropTarget.specs, this) 14 | 15 | return { 16 | dropTargetHandler: createTarget(targetMonitor), 17 | dropTargetHandlerMonitor: targetMonitor, 18 | dropTargetHandlerConnector: createTargetConnector(this.dragDropManager.getBackend()), 19 | dropTargetDisposable: new SerialDisposable(), 20 | isDropTargetCurrentlyMounted: false, 21 | currentDropTargetType: null, 22 | dropTargetHandlerId: null 23 | } 24 | }, 25 | 26 | mounted () { 27 | this.isDropTargetCurrentlyMounted = true 28 | this.dropTargetDisposable = new SerialDisposable() 29 | this.currentDropTargetType = null 30 | 31 | this.receiveDropTargetProps(this.$props) 32 | this.handleDropTargetChange() 33 | }, 34 | 35 | beforeUpdate () { 36 | this.receiveDropTargetProps(this.$props) 37 | this.handleDropTargetChange() 38 | }, 39 | 40 | beforeDestroy () { 41 | this.disposeDropTarget() 42 | this.isDropTargetCurrentlyMounted = false 43 | }, 44 | 45 | directives: { 46 | dropTarget: { 47 | inserted: function (el, binding, vnode) { 48 | vnode.context.dropTargetHandlerConnector.hooks.dropTarget(el) 49 | }, 50 | componentUpdated: function (el, binding, vnode) { 51 | vnode.context.dropTargetHandlerConnector.hooks.dropTarget(el) 52 | } 53 | } 54 | }, 55 | 56 | methods: { 57 | disposeDropTarget () { 58 | this.dropTargetDisposable.dispose() 59 | this.dropTargetHandlerConnector.receiveHandlerId(null) 60 | }, 61 | 62 | handleDropTargetChange () { 63 | if (this.$options && this.$options.dropTarget && this.$options.dropTarget.collect) { 64 | let f = this.$options.dropTarget.collect.bind(this) 65 | f(this.dropTargetHandlerConnector.hooks, this.dropTargetHandlerMonitor) 66 | } 67 | }, 68 | 69 | receiveDropTargetProps () { 70 | let typeF = this.$options.dropTarget.type.bind(this) 71 | this.receiveDropTargetType(typeF()) 72 | }, 73 | 74 | receiveDropTargetType (type) { 75 | if (arrayEqual(type, this.currentDropTargetType)) { 76 | return 77 | } 78 | 79 | this.currentDropTargetType = type 80 | 81 | const { handlerId, unregister } = registerTarget( 82 | type, 83 | this.dropTargetHandler, 84 | this.dragDropManager 85 | ) 86 | 87 | this.dropTargetHandlerId = handlerId 88 | this.dropTargetHandlerMonitor.receiveHandlerId(handlerId) 89 | this.dropTargetHandlerConnector.receiveHandlerId(handlerId) 90 | 91 | const globalMonitor = this.dragDropManager.getMonitor() 92 | const unsubscribe = globalMonitor.subscribeToStateChange( 93 | this.handleDropTargetChange, 94 | { handlerIds: [handlerId] } 95 | ) 96 | 97 | this.dropTargetDisposable.setDisposable( 98 | new CompositeDisposable( 99 | new Disposable(unsubscribe), 100 | new Disposable(unregister) 101 | ) 102 | ) 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/areOptionsEqual.js: -------------------------------------------------------------------------------- 1 | let shallowEqual = function (objA, objB) { 2 | if (objA === objB) { 3 | return true 4 | } 5 | 6 | const keysA = Object.keys(objA) 7 | const keysB = Object.keys(objB) 8 | 9 | if (keysA.length !== keysB.length) { 10 | return false 11 | } 12 | 13 | // Test for A's keys different from B. 14 | const hasOwn = Object.prototype.hasOwnProperty 15 | for (let i = 0; i < keysA.length; i += 1) { 16 | if (!hasOwn.call(objB, keysA[i]) || objA[keysA[i]] !== objB[keysA[i]]) { 17 | return false 18 | } 19 | 20 | const valA = objA[keysA[i]] 21 | const valB = objB[keysA[i]] 22 | 23 | if (valA !== valB) { 24 | return false 25 | } 26 | } 27 | 28 | return true 29 | } 30 | 31 | export default function areOptionsEqual (nextOptions, currentOptions) { 32 | if (currentOptions === nextOptions) { 33 | return true 34 | } 35 | 36 | return ( 37 | currentOptions !== null && 38 | nextOptions !== null && 39 | shallowEqual(currentOptions, nextOptions) 40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /src/arrayEqual.js: -------------------------------------------------------------------------------- 1 | export default function (a, b) { 2 | if (Array.isArray(a) && Array.isArray(b) && a.length === b.length) { 3 | let equal = true 4 | for (var i = 0; i < a.length; i++) { 5 | equal = equal && a[i] === b[i] 6 | } 7 | return equal 8 | } else { 9 | return a === b 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/createSourceConnector.js: -------------------------------------------------------------------------------- 1 | import areOptionsEqual from './areOptionsEqual' 2 | 3 | export default function createSourceConnector (backend) { 4 | let currentHandlerId 5 | 6 | let currentDragSourceNode 7 | let currentDragSourceOptions 8 | let disconnectCurrentDragSource 9 | 10 | let currentDragPreviewNode 11 | let currentDragPreviewOptions 12 | let disconnectCurrentDragPreview 13 | 14 | function reconnectDragSource () { 15 | if (disconnectCurrentDragSource) { 16 | disconnectCurrentDragSource() 17 | disconnectCurrentDragSource = null 18 | } 19 | 20 | if (currentHandlerId && currentDragSourceNode) { 21 | disconnectCurrentDragSource = backend.connectDragSource( 22 | currentHandlerId, 23 | currentDragSourceNode, 24 | currentDragSourceOptions 25 | ) 26 | } 27 | } 28 | 29 | function reconnectDragPreview () { 30 | if (disconnectCurrentDragPreview) { 31 | disconnectCurrentDragPreview() 32 | disconnectCurrentDragPreview = null 33 | } 34 | 35 | if (currentHandlerId && currentDragPreviewNode) { 36 | disconnectCurrentDragPreview = backend.connectDragPreview( 37 | currentHandlerId, 38 | currentDragPreviewNode, 39 | currentDragPreviewOptions 40 | ) 41 | } 42 | } 43 | 44 | function receiveHandlerId (handlerId) { 45 | if (handlerId === currentHandlerId) { 46 | return 47 | } 48 | 49 | currentHandlerId = handlerId 50 | reconnectDragSource() 51 | reconnectDragPreview() 52 | } 53 | 54 | const hooks = { 55 | dragSource: function connectDragSource (node, options) { 56 | if ( 57 | node === currentDragSourceNode && 58 | areOptionsEqual(options, currentDragSourceOptions) 59 | ) { 60 | return 61 | } 62 | 63 | currentDragSourceNode = node 64 | currentDragSourceOptions = options 65 | 66 | reconnectDragSource() 67 | }, 68 | 69 | dragPreview: function connectDragPreview (node, options) { 70 | if ( 71 | node === currentDragPreviewNode && 72 | areOptionsEqual(options, currentDragPreviewOptions) 73 | ) { 74 | return 75 | } 76 | 77 | currentDragPreviewNode = node 78 | currentDragPreviewOptions = options 79 | 80 | reconnectDragPreview() 81 | } 82 | } 83 | 84 | return { 85 | receiveHandlerId, 86 | hooks 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/createSourceFactory.js: -------------------------------------------------------------------------------- 1 | const ALLOWED_SPEC_METHODS = ['canDrag', 'beginDrag', 'isDragging', 'endDrag'] 2 | const REQUIRED_SPEC_METHODS = ['beginDrag'] 3 | 4 | export default function createSourceFactory (spec, context) { 5 | class Source { 6 | constructor (monitor) { 7 | this.monitor = monitor 8 | } 9 | 10 | canDrag () { 11 | if (!spec.canDrag) { 12 | return true 13 | } 14 | 15 | let f = spec.canDrag.bind(context) 16 | 17 | return f(this.monitor) 18 | } 19 | 20 | isDragging (globalMonitor, sourceId) { 21 | if (!spec.isDragging) { 22 | return sourceId === globalMonitor.getSourceId() 23 | } 24 | 25 | let f = spec.isDragging.bind(context) 26 | 27 | return f(this.monitor) 28 | } 29 | 30 | beginDrag () { 31 | let f = spec.beginDrag.bind(context) 32 | 33 | return f(this.monitor, this.component) 34 | } 35 | 36 | endDrag () { 37 | if (!spec.endDrag) { 38 | return 39 | } 40 | 41 | let f = spec.endDrag.bind(context) 42 | 43 | f(this.monitor) 44 | } 45 | } 46 | 47 | return function createSource (monitor) { 48 | return new Source(monitor) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/createSourceMonitor.js: -------------------------------------------------------------------------------- 1 | import invariant from 'invariant' 2 | 3 | let isCallingCanDrag = false 4 | let isCallingIsDragging = false 5 | 6 | class SourceMonitor { 7 | constructor (manager) { 8 | this.internalMonitor = manager.getMonitor() 9 | } 10 | 11 | receiveHandlerId (sourceId) { 12 | this.sourceId = sourceId 13 | } 14 | 15 | canDrag () { 16 | try { 17 | isCallingCanDrag = true 18 | return this.internalMonitor.canDragSource(this.sourceId) 19 | } finally { 20 | isCallingCanDrag = false 21 | } 22 | } 23 | 24 | isDragging () { 25 | try { 26 | isCallingIsDragging = true 27 | return this.internalMonitor.isDraggingSource(this.sourceId) 28 | } finally { 29 | isCallingIsDragging = false 30 | } 31 | } 32 | 33 | getItemType () { 34 | return this.internalMonitor.getItemType() 35 | } 36 | 37 | getItem () { 38 | return this.internalMonitor.getItem() 39 | } 40 | 41 | getDropResult () { 42 | return this.internalMonitor.getDropResult() 43 | } 44 | 45 | didDrop () { 46 | return this.internalMonitor.didDrop() 47 | } 48 | 49 | getInitialClientOffset () { 50 | return this.internalMonitor.getInitialClientOffset() 51 | } 52 | 53 | getInitialSourceClientOffset () { 54 | return this.internalMonitor.getInitialSourceClientOffset() 55 | } 56 | 57 | getSourceClientOffset () { 58 | return this.internalMonitor.getSourceClientOffset() 59 | } 60 | 61 | getClientOffset () { 62 | return this.internalMonitor.getClientOffset() 63 | } 64 | 65 | getDifferenceFromInitialOffset () { 66 | return this.internalMonitor.getDifferenceFromInitialOffset() 67 | } 68 | } 69 | 70 | export default function createSourceMonitor (manager) { 71 | return new SourceMonitor(manager) 72 | } 73 | -------------------------------------------------------------------------------- /src/createTargetConnector.js: -------------------------------------------------------------------------------- 1 | import areOptionsEqual from './areOptionsEqual' 2 | 3 | export default function createTargetConnector (backend) { 4 | let currentHandlerId 5 | 6 | let currentDropTargetNode 7 | let currentDropTargetOptions 8 | let disconnectCurrentDropTarget 9 | 10 | function reconnectDropTarget () { 11 | if (disconnectCurrentDropTarget) { 12 | disconnectCurrentDropTarget() 13 | disconnectCurrentDropTarget = null 14 | } 15 | 16 | if (currentHandlerId && currentDropTargetNode) { 17 | disconnectCurrentDropTarget = backend.connectDropTarget( 18 | currentHandlerId, 19 | currentDropTargetNode, 20 | currentDropTargetOptions 21 | ) 22 | } 23 | } 24 | 25 | function receiveHandlerId (handlerId) { 26 | if (handlerId === currentHandlerId) { 27 | return 28 | } 29 | 30 | currentHandlerId = handlerId 31 | reconnectDropTarget() 32 | } 33 | 34 | const hooks = { 35 | dropTarget: function connectDropTarget (node, options) { 36 | if ( 37 | node === currentDropTargetNode && 38 | areOptionsEqual(options, currentDropTargetOptions) 39 | ) { 40 | return 41 | } 42 | 43 | currentDropTargetNode = node 44 | currentDropTargetOptions = options 45 | 46 | reconnectDropTarget() 47 | } 48 | } 49 | 50 | return { 51 | receiveHandlerId, 52 | hooks 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/createTargetFactory.js: -------------------------------------------------------------------------------- 1 | const ALLOWED_SPEC_METHODS = ['canDrop', 'hover', 'drop'] 2 | 3 | export default function createTargetFactory (spec, context) { 4 | class Target { 5 | constructor (monitor) { 6 | this.monitor = monitor 7 | } 8 | 9 | receiveMonitor (monitor) { 10 | this.monitor = monitor 11 | } 12 | 13 | canDrop () { 14 | if (!spec.canDrop) { 15 | return true 16 | } 17 | 18 | let f = spec.canDrop.bind(context) 19 | 20 | return f(this.monitor) 21 | } 22 | 23 | hover () { 24 | if (!spec.hover) { 25 | return 26 | } 27 | 28 | let f = spec.hover.bind(context) 29 | 30 | f(this.monitor) 31 | } 32 | 33 | drop () { 34 | if (!spec.drop) { 35 | return undefined 36 | } 37 | 38 | let f = spec.drop.bind(context) 39 | 40 | return f(this.monitor) 41 | } 42 | } 43 | 44 | return function createTarget (monitor) { 45 | return new Target(monitor) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/createTargetMonitor.js: -------------------------------------------------------------------------------- 1 | let isCallingCanDrop = false 2 | 3 | class TargetMonitor { 4 | constructor (manager) { 5 | this.internalMonitor = manager.getMonitor() 6 | } 7 | 8 | receiveHandlerId (targetId) { 9 | this.targetId = targetId 10 | } 11 | 12 | canDrop () { 13 | try { 14 | isCallingCanDrop = true 15 | return this.internalMonitor.canDropOnTarget(this.targetId) 16 | } finally { 17 | isCallingCanDrop = false 18 | } 19 | } 20 | 21 | isOver (options) { 22 | return this.internalMonitor.isOverTarget(this.targetId, options) 23 | } 24 | 25 | getItemType () { 26 | return this.internalMonitor.getItemType() 27 | } 28 | 29 | getItem () { 30 | return this.internalMonitor.getItem() 31 | } 32 | 33 | getDropResult () { 34 | return this.internalMonitor.getDropResult() 35 | } 36 | 37 | didDrop () { 38 | return this.internalMonitor.didDrop() 39 | } 40 | 41 | getInitialClientOffset () { 42 | return this.internalMonitor.getInitialClientOffset() 43 | } 44 | 45 | getInitialSourceClientOffset () { 46 | return this.internalMonitor.getInitialSourceClientOffset() 47 | } 48 | 49 | getSourceClientOffset () { 50 | return this.internalMonitor.getSourceClientOffset() 51 | } 52 | 53 | getClientOffset () { 54 | return this.internalMonitor.getClientOffset() 55 | } 56 | 57 | getDifferenceFromInitialOffset () { 58 | return this.internalMonitor.getDifferenceFromInitialOffset() 59 | } 60 | } 61 | 62 | export default function createTargetMonitor (manager) { 63 | return new TargetMonitor(manager) 64 | } 65 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | // Import vue component 2 | import DragDropContextProvider from './DragDropContextProvider.vue' 3 | import DropTarget from './DropTarget.js' 4 | import DragSource from './DragSource.js' 5 | import DragDropContext from './DragDropContext.js' 6 | import DragLayer from './DragLayer.js' 7 | 8 | // Declare install function executed by Vue.use() 9 | export function install (Vue) { 10 | if (install.installed) return 11 | install.installed = true 12 | Vue.component('DragDropContextProvider', DragDropContextProvider) 13 | } 14 | 15 | // Create module definition for Vue.use() 16 | const plugin = { 17 | install 18 | } 19 | 20 | // Auto-install when vue is found (eg. in browser via