├── .babelrc
├── .editorconfig
├── .eslintignore
├── .eslintrc.yml
├── .gitignore
├── README.md
├── config
├── .eslintrc.yml
├── webpack.config.base.js
├── webpack.config.browser.js
├── webpack.config.common.js
└── webpack.config.dev.js
├── package-lock.json
├── package.json
├── src
├── Drag.vue
├── Drop.vue
├── constants.js
├── index.js
├── polyfills.js
└── transferDataStore.js
└── types
└── index.d.ts
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["env"]
3 | }
4 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = tab
5 | indent_size = 2
6 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | dist/
3 |
--------------------------------------------------------------------------------
/.eslintrc.yml:
--------------------------------------------------------------------------------
1 | root: true
2 | parser: babel-eslint
3 | parserOptions:
4 | sourceType: module
5 | extends: eslint-config-genius
6 | plugins:
7 | - html
8 | env:
9 | es6: true
10 | browser: true
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | /dist
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | > ❗ Version 1.0.0 is out! This finally includes support for IE/Edge.
2 |
3 | > Note that this required skipping the native `transferData` event property entirely in favor of a global variable that maintains the current drag transfer info. This could theoretically break some extreme edge cases involving multiple drag events occurring simultaneously, but I think it's very unlikely, or maybe even impossible. Feedback is greatly appreciated.
4 |
5 | # vue-drag-drop
6 |
7 | [ ](https://www.npmjs.com/package/vue-drag-drop)
8 | [](https://vuejs.org/)
9 |
10 | > A lightweight wrapper that abstracts away the wonkier parts of the [Drag and Drop API](https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API). View a [live demo](https://cameronhimself.github.io/vue-drag-drop/).
11 |
12 | ## Table of contents
13 |
14 | - [Introduction](#introduction)
15 | - [Installation](#installation)
16 | - [API](#api)
17 | - [Examples](#examples)
18 |
19 | # Introduction
20 |
21 | The Drag and Drop API is pretty jank. Here are a handful of annoying issues:
22 |
23 | - Data transferred from a draggable element to a dropzone is only available in the dropzone's `drop` event. Want to take a look at the draggable's data during the `dragover` event? Say, to determine whether or not we can allow the drop? Sorry! No helpful UI feedback for your users!
24 | - Got an object or an array you want to transfer between a draggable and a dropzone? Tough. Gotta serialize it. Say goodbye to your references.
25 | - Did you remember to do `event.preventDefault()` on `dragover` for every element you want to be used as a dropzone?
26 |
27 | And so on.
28 |
29 | The goal of this package is to provide a simple, lightweight wrapper around the API so you don't have to fiddle with all that nonsense. There are [plenty of existing Vue components](https://github.com/vuejs/awesome-vue#drag-and-drop) that provide rich handling of drag and drop, usually between or among lists and with tons of bells and whistles. They're great, but sometimes you don't need all that business, or it even gets in the way.
30 |
31 | Take a look at some examples to get started:
32 | - [Live demos of many scenarios](https://cameronhimself.github.io/vue-drag-drop/)
33 | - [Source code for above](https://github.com/cameronhimself/vue-drag-drop-demo/tree/master/src)
34 | - [Minimal JSFiddle](https://jsfiddle.net/cameronhimself/hvvaadk9/)
35 |
36 | # Installation
37 |
38 | ```
39 | npm install --save vue-drag-drop
40 | ```
41 |
42 | ## Default import
43 |
44 | ```javascript
45 | import Vue from 'vue';
46 | import { Drag, Drop } from 'vue-drag-drop';
47 |
48 | Vue.component('drag', Drag);
49 | Vue.component('drop', Drop);
50 | ```
51 |
52 | Or install both:
53 |
54 | ```javascript
55 | import Vue from 'vue';
56 | import VueDragDrop from 'vue-drag-drop';
57 |
58 | Vue.use(VueDragDrop);
59 | ```
60 |
61 | ## Browser
62 |
63 | ```html
64 |
65 |
66 | ```
67 |
68 | The plugin should be auto-installed. If not, you can install it manually with the instructions below.
69 |
70 | ```javascript
71 | Vue.component('drag', VueDragDrop.Drag)
72 | Vue.component('drop', VueDragDrop.Drop)
73 | ```
74 |
75 | Or install both:
76 |
77 | ```javascript
78 | Vue.use(VueDragDrop)
79 | ```
80 |
81 | # API
82 |
83 | ## Components
84 |
85 | ### `Drag`
86 | A draggable element.
87 |
88 | ### `Drop`
89 |
90 | An element onto which a `Drag` can be dropped. All `Drop` elements accept all `Drag` elements, unless you change the behavior in your application.
91 |
92 | ## Properties
93 |
94 | The following properties apply to `Drag` components. `Drop` components don't receive any properties.
95 |
96 | ### `draggable`
97 | **validation** `Boolean`
98 | **default**: `true`
99 | Whether or not the draggable is actually draggable. Useful if you need to disable it temporarily.
100 |
101 | ### `transfer-data`
102 | **validation**: none
103 | **default**: `null`
104 | The data to be transmitted from the `Drag` to the `Drop` via events. This is passed through to every `Drop`-fired event.
105 |
106 | ### `effect-allowed`
107 | **validation**: `null` or one of `['none', 'copy', 'copyLink', 'copyMove', 'link', 'linkMove', 'move', 'all', 'uninitialized']`
108 | **default**: `null`
109 | See https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/effectAllowed.
110 |
111 | ### `drop-effect`
112 | **validation**: `null` or one of `['copy', 'move', 'link', 'none']`
113 | **default**: `null`
114 | See https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/dropEffect.
115 |
116 | ### `image`
117 | **validation**: `null`, `String`
118 | **default**: `null`
119 | A URL for an image to be used for the drag image instead of the default. If you'd like to use HTML for the drag image instead, use the `image` slot. More details in the Slots section of this documentation.
120 |
121 | If both the `image` prop and `image` slot are present, the prop will be used and the slot will be ignored.
122 |
123 | ### `image-x-offset`, `image-y-offset`
124 | **validation**: `Number`
125 | **default**: `0`, `0`
126 | By default, a custom drag image is positioned so that its top-left corner is anchored to the cursor. You can adjust that positioning with these values.
127 |
128 | ### `hide-image-html`
129 | **validation**: `Boolean`
130 | **default**: `true`
131 | If the `Drag` `image` slot is used, toggle whether or not the HTML is rendered off-screen. See the `image` slot documentation for more details.
132 |
133 | ### `tag`
134 | **validation** `String`
135 | **default**: `div`
136 | Drag or Drop element's wrapper, defaults to div.
137 |
138 |
139 | ## Events
140 |
141 | All event are fired with the same arguments:
142 |
143 | - `transferData` _any_
144 | This is the data set on the `Drag`'s `transferData` prop. It _is_ available on all `Drop`-fired events, despite the official spec only permitting it on `drop`.
145 |
146 | - `nativeEvent` [_`DragEvent`_](https://developer.mozilla.org/en-US/docs/Web/API/DragEvent)
147 | The native browser event. Useful particularly for retrieving the [`dataTransfer`](https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer) object, which is needed for handling [dropped files](https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/files).
148 |
149 | If you need to pass additional arguments in your event listener, the preferred method is to use the ES6 spread operator with `arguments`:
150 |
151 | ```vue
152 | Drag Me
153 | ```
154 | ```javascript
155 | myListener(myArg, transferData, nativeEvent) {
156 | // myArg === 'foo'
157 | }
158 | ```
159 |
160 | If you don't have the spread operator in your environment, you can use a wrapping function:
161 |
162 | ```vue
163 |
164 | Drag Me
165 |
166 | ```
167 |
168 | ### `dragstart`
169 | **components:** _`Drag`_
170 | Fired once when dragging starts.
171 |
172 | ### `drag`
173 | **components:** _`Drag`_
174 | Repeatedly fired for the entire duration of the drag operation.
175 |
176 | ### `dragenter`
177 | **components:** _`Drag`_, _`Drop`_
178 | Fired once every time a `Drag` is dragged over a `Drop`.
179 |
180 | ### `dragover`
181 | **components:** _`Drag`_, _`Drop`_
182 | Repeatedly fired while a `Drag` is over a `Drop`.
183 |
184 | ### `dragleave`
185 | **components:** _`Drag`_, _`Drop`_
186 | Fired once every time a `Drag` leaves a `Drop`.
187 |
188 | ### `drop`
189 | **components:** _`Drop`_
190 | Fired once when a `Drag` is dropped on a `Drop`.
191 |
192 | ### `dragend`
193 | **components:** _`Drag`_
194 | Fired once when the drag operation is completed. Occurs after `drop`.
195 |
196 | ## Slots
197 |
198 | ### _default_
199 | **components:** _`Drag`_, _`Drop`_
200 | **example**: `I am the default slot`
201 | **example**: `So am I`
202 | For `Drag`, the content that will be draggable. For `Drop`, the content over which a `Drag` can be dropped.
203 |
204 | Note that this is a [scoped slot](https://vuejs.org/v2/guide/components.html#Scoped-Slots). The scope contains a single key, `transferData`, which will contain exactly what you set in the `transferData` prop on the `Drag`. For `Drag` elements, this will be populated while a drag is in action, and for `Drop` elements, when a `Drag` is being dragged over. Checking for the `transferData` in the `Drop` scope is the simplest way to determine if a drag is in progress over it.
205 |
206 | ### `image`
207 | **components:** _`Drag`_
208 | **example**: `Drag MeI'm being dragged!
`
209 | The contents of this slot will be used as the drag image instead of the browser default. Since the spec likes to be annoying, this content has to be visible in order for it to show up as the drag image, so it's rendered off-screen for you using `position: fixed`. If you need this convenience turned off, or if you need to support a crummy browser that this doesn't work well with, you can set the `hideImageHtml` prop to `false`, which will prevent any additional styling being added. Just be aware that doing so will cause this content to appear inside the `Drag` element. It's up to you how to deal with it.
210 |
211 | Multiple `image` slots do nothing; only the first will be used. If both the `image` prop and `image` slot are present, the prop will be used and the slot will be ignored.
212 |
213 | # Examples
214 |
215 | - [Live demos of many scenarios](https://cameronhimself.github.io/vue-drag-drop/)
216 | - [Source code for above](https://github.com/cameronhimself/vue-drag-drop-demo/tree/master/src)
217 | - [Minimal JSFiddle](https://jsfiddle.net/cameronhimself/hvvaadk9/)
218 |
219 | ---
220 |
221 | # Plugin Development
222 |
223 | ## Installation
224 |
225 | The first time you create or clone your plugin, you need to install the default dependencies:
226 |
227 | ```
228 | npm install
229 | ```
230 |
231 | ## Watch and compile
232 |
233 | This will run webpack in watching mode and output the compiled files in the `dist` folder.
234 |
235 | ```
236 | npm run dev
237 | ```
238 |
239 | ## Use it in another project
240 |
241 | While developing, you can follow the install instructions of your plugin and link it into the project that uses it.
242 |
243 | In the plugin folder:
244 |
245 | ```
246 | npm link
247 | ```
248 |
249 | In the other project folder:
250 |
251 | ```
252 | npm link vue-drag-drop
253 | ```
254 |
255 | This will install it in the dependencies as a symlink, so that it gets any modifications made to the plugin.
256 |
257 |
258 | ## Manual build
259 |
260 | This will build the plugin into the `dist` folder in production mode.
261 |
262 | ```
263 | npm run build
264 | ```
265 |
266 | ---
267 |
268 | ## License
269 |
270 | [MIT](http://opensource.org/licenses/MIT)
271 |
--------------------------------------------------------------------------------
/config/.eslintrc.yml:
--------------------------------------------------------------------------------
1 | env:
2 | node: true
3 | rules:
4 | no-var: off
5 |
--------------------------------------------------------------------------------
/config/webpack.config.base.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 | var ExtractTextPlugin = require('extract-text-webpack-plugin');
3 | var outputFile = 'vue-drag-drop';
4 |
5 | var config = require('../package.json');
6 |
7 | module.exports = {
8 | entry: ['./src/polyfills.js', './src/index.js'],
9 | module: {
10 | rules: [
11 | {
12 | enforce: 'pre',
13 | test: /\.(js|vue)$/,
14 | loader: 'eslint-loader',
15 | exclude: /node_modules/,
16 | },
17 | {
18 | test: /\.js$/,
19 | loader: 'babel-loader',
20 | exclude: /node_modules/,
21 | },
22 | {
23 | test: /\.vue$/,
24 | loader: 'vue-loader',
25 | options: {
26 | optimizeSSR: false,
27 | loaders: {
28 | css: ExtractTextPlugin.extract('css-loader'),
29 | sass: ExtractTextPlugin.extract('css-loader!sass-loader'),
30 | scss: ExtractTextPlugin.extract('css-loader!sass-loader'),
31 | },
32 | },
33 | },
34 | ],
35 | },
36 | plugins: [
37 | new webpack.DefinePlugin({
38 | 'VERSION': JSON.stringify(config.version),
39 | }),
40 | // new ExtractTextPlugin(outputFile + '.css'),
41 | ],
42 | };
43 |
--------------------------------------------------------------------------------
/config/webpack.config.browser.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 | var merge = require('webpack-merge');
3 | var base = require('./webpack.config.base');
4 | var path = require('path');
5 |
6 | var outputFile = 'vue-drag-drop';
7 | var globalName = 'VueDragDrop';
8 |
9 | module.exports = merge(base, {
10 | output: {
11 | path: path.resolve(__dirname, '../dist'),
12 | filename: outputFile + '.browser.js',
13 | library: globalName,
14 | libraryTarget: 'umd',
15 | },
16 | externals: {
17 | // Put external libraries like lodash here
18 | // With their global name
19 | // Example: 'lodash': '_'
20 | },
21 | plugins: [
22 | new webpack.optimize.UglifyJsPlugin({
23 | compress: {
24 | warnings: true,
25 | },
26 | mangle: false,
27 | }),
28 | ],
29 | });
30 |
--------------------------------------------------------------------------------
/config/webpack.config.common.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 | var merge = require('webpack-merge');
3 | var base = require('./webpack.config.base');
4 | var path = require('path');
5 |
6 | var outputFile = 'vue-drag-drop';
7 |
8 | module.exports = merge(base, {
9 | output: {
10 | path: path.resolve(__dirname, '../dist'),
11 | filename: outputFile + '.common.js',
12 | libraryTarget: 'commonjs2',
13 | },
14 | target: 'node',
15 | externals: {
16 | // Put external libraries like lodash here
17 | // With their package name
18 | // Example: 'lodash': 'lodash'
19 | },
20 | plugins: [
21 | new webpack.optimize.UglifyJsPlugin({
22 | compress: {
23 | warnings: true,
24 | },
25 | mangle: false,
26 | }),
27 | new webpack.DefinePlugin({
28 | 'process.env': {
29 | 'NODE_ENV': JSON.stringify('production'),
30 | },
31 | }),
32 | ],
33 | });
34 |
--------------------------------------------------------------------------------
/config/webpack.config.dev.js:
--------------------------------------------------------------------------------
1 | var merge = require('webpack-merge');
2 | var base = require('./webpack.config.base');
3 | var path = require('path');
4 |
5 | var outputFile = 'vue-drag-drop';
6 | var globalName = 'VueDragDrop';
7 |
8 | module.exports = merge(base, {
9 | output: {
10 | path: path.resolve(__dirname, '../dist'),
11 | filename: outputFile + '.common.js',
12 | library: globalName,
13 | libraryTarget: 'umd',
14 | },
15 | devtool: 'eval-source-map',
16 | });
17 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-drag-drop",
3 | "description": "A lightweight Vue wrapper that abstracts away the wonkier parts of the Drag and Drop Browser API",
4 | "version": "1.1.4",
5 | "author": {
6 | "name": "J. Cameron McDonald",
7 | "email": "cameronhimself@gmail.com"
8 | },
9 | "files": [
10 | "dist",
11 | "types/*.d.ts"
12 | ],
13 | "typings": "types/index.d.ts",
14 | "keywords": [
15 | "vue",
16 | "vuejs",
17 | "plugin"
18 | ],
19 | "license": "MIT",
20 | "main": "dist/vue-drag-drop.common.js",
21 | "unpkg": "dist/vue-drag-drop.browser.js",
22 | "module": "dist/vue-drag-drop.common.js",
23 | "scripts": {
24 | "dev": "cross-env NODE_ENV=development webpack --config config/webpack.config.dev.js --progress --watch",
25 | "build": "npm run build:browser && npm run build:common",
26 | "build:browser": "cross-env NODE_ENV=production webpack --config config/webpack.config.browser.js --progress --hide-modules",
27 | "build:common": "cross-env NODE_ENV=production webpack --config config/webpack.config.common.js --progress --hide-modules",
28 | "prepack": "npm run build"
29 | },
30 | "repository": {
31 | "type": "git",
32 | "url": "git+https://github.com/cameronhimself/vue-drag-drop.git"
33 | },
34 | "bugs": {
35 | "url": "https://github.com/cameronhimself/vue-drag-drop/issues"
36 | },
37 | "homepage": "https://github.com/cameronhimself/vue-drag-drop#readme",
38 | "devDependencies": {
39 | "babel-core": "^6.26.0",
40 | "babel-eslint": "^7.2.3",
41 | "babel-loader": "^7.1.2",
42 | "babel-preset-env": "^1.6.0",
43 | "cross-env": "^5.0.5",
44 | "css-loader": "^0.28.5",
45 | "eslint": "^4.5.0",
46 | "eslint-config-genius": "^0.1.4",
47 | "eslint-loader": "^1.9.0",
48 | "eslint-plugin-html": "^3.2.0",
49 | "eslint-plugin-promise": "^3.5.0",
50 | "eslint-plugin-standard": "^3.0.1",
51 | "extract-text-webpack-plugin": "^3.0.0",
52 | "file-loader": "^0.11.2",
53 | "node-sass": "^4.5.3",
54 | "sass-loader": "^6.0.6",
55 | "vue-loader": "^13.0.4",
56 | "vue-template-compiler": "^2.4.2",
57 | "webpack": "^3.5.5",
58 | "webpack-merge": "^4.1.0"
59 | },
60 | "dependencies": {
61 | "core-js": "^2.5.3"
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/Drag.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
103 |
--------------------------------------------------------------------------------
/src/Drop.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
65 |
--------------------------------------------------------------------------------
/src/constants.js:
--------------------------------------------------------------------------------
1 | const keyMirror = keys => keys.reduce((acc, k) => (acc[k] = k) && acc, {});
2 |
3 | export const events = keyMirror([
4 | 'drag', 'dragend', 'dragenter', 'dragleave', 'dragstart', 'dragover', 'drop',
5 | ]);
6 | export const dropEffects = keyMirror(['copy', 'move', 'link', 'none']);
7 | export const effectsAllowed = keyMirror([
8 | 'none', 'copy', 'copyLink', 'copyMove', 'link', 'linkMove', 'move', 'all',
9 | 'uninitialized',
10 | ]);
11 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import Drag from './Drag.vue';
2 | import Drop from './Drop.vue';
3 |
4 | // Install the components
5 | export function install(Vue) {
6 | Vue.component('drag', Drag);
7 | Vue.component('drop', Drop);
8 | }
9 |
10 | // Expose the components
11 | export { Drag, Drop };
12 |
13 | /* -- Plugin definition & Auto-install -- */
14 | /* You shouldn't have to modify the code below */
15 |
16 | // Plugin
17 | const plugin = {
18 | /* eslint-disable no-undef */
19 | version: VERSION,
20 | install,
21 | };
22 |
23 | export default plugin;
24 |
25 | // Auto-install
26 | let GlobalVue = null;
27 | if (typeof window !== 'undefined') {
28 | GlobalVue = window.Vue;
29 | } else if (typeof global !== 'undefined') {
30 | GlobalVue = global.Vue;
31 | }
32 | if (GlobalVue) {
33 | GlobalVue.use(plugin);
34 | }
35 |
--------------------------------------------------------------------------------
/src/polyfills.js:
--------------------------------------------------------------------------------
1 | import 'core-js/fn/array/includes';
2 | import 'core-js/fn/array/find';
3 |
--------------------------------------------------------------------------------
/src/transferDataStore.js:
--------------------------------------------------------------------------------
1 | export default { data: undefined };
2 |
--------------------------------------------------------------------------------
/types/index.d.ts:
--------------------------------------------------------------------------------
1 | import Vue, { PluginObject, VueConstructor } from 'vue';
2 |
3 | export type DropEffect = 'copy' | 'move' | 'link' | 'none';
4 | export type EffectsAllowed = 'none' | 'copy' | 'copyLink' | 'copyMove' | 'link' | 'linkMove' | 'move' | 'all' | 'uninitialized';
5 | export type DragEventName = 'drag' | 'dragend' | 'dragenter' | 'dragleave' | 'dragstart' | 'dragover' | 'drop';
6 | export type DragEventObject = { [s: string]: DragEventName };
7 |
8 | declare class Drag extends Vue {
9 | // props
10 | draggable: boolean;
11 | transferData: Record;
12 | dropEffect: { validator: (value: DropEffect) => boolean };
13 | effectAllowed: { validator: (value: EffectsAllowed) => boolean };
14 | image: string;
15 | imageXOffset: number;
16 | imageYOffset: number;
17 | hideImageHtml: boolean;
18 | tag: string;
19 |
20 | // data
21 | dragging: boolean;
22 |
23 | // computed
24 | events: DragEventObject;
25 | scopedData: Record | boolean;
26 | hideImageStyle: Record;
27 |
28 | // methods
29 | emitEvent: (name: DragEventName, event: DragEvent) => void;
30 | }
31 |
32 | declare class Drop extends Vue {
33 | // props
34 | tag: string;
35 |
36 | // data
37 | transferData: Record | undefined;
38 | isDraggingOver: boolean;
39 |
40 | // computed
41 | events: DragEventObject;
42 | scopedData: Record | boolean;
43 |
44 | // methods
45 | emitEvent: (name: DragEventName, event: DragEvent) => void;
46 | }
47 |
48 | declare const VueDragDrop: PluginObject;
49 |
50 | export { Drag, Drop };
51 |
52 | export default VueDragDrop;
53 |
--------------------------------------------------------------------------------