├── .babelrc
├── .eslintignore
├── .eslintrc.js
├── .github
└── FUNDING.yml
├── .gitignore
├── LICENSE
├── README.md
├── build
├── banner.txt
└── rollup.js
├── demo
├── .babelrc
├── .editorconfig
├── .gitignore
├── README.md
├── index.html
├── package.json
├── src
│ ├── assets
│ │ ├── demos.css
│ │ ├── form.css
│ │ ├── header.css
│ │ ├── layout.css
│ │ └── nav.css
│ ├── config
│ │ ├── navigation.js
│ │ └── router.js
│ ├── index.vue
│ ├── main.js
│ ├── pages
│ │ ├── cards.vue
│ │ ├── copy.vue
│ │ ├── drag-class.vue
│ │ ├── drag-delay.vue
│ │ ├── drag-handle.vue
│ │ ├── events.vue
│ │ ├── form.vue
│ │ ├── groups.vue
│ │ ├── lock-axis.vue
│ │ ├── nested.vue
│ │ ├── simple-horizontal.vue
│ │ ├── simple-scroller.vue
│ │ ├── simple.vue
│ │ ├── table.vue
│ │ └── transition-duration.vue
│ └── utils
│ │ ├── helpers.js
│ │ └── polyfills.js
├── webpack.config.js
└── yarn.lock
├── package.json
├── src
├── Container.js
├── Draggable.js
├── main.js
└── utils.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "testing": {
4 | "presets":[
5 | ["env", { "modules": false }],
6 | "react",
7 | ],
8 | "plugins": [
9 | "transform-es2015-modules-commonjs",
10 | ]
11 | }
12 | }
13 | }
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | dist/
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | parserOptions: {
4 | parser: 'babel-eslint'
5 | },
6 | env: {
7 | browser: true,
8 | },
9 | extends: 'standard',
10 | rules: {
11 | 'comma-dangle': 0,
12 | "semi": [2, "always"]
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [kutlugsahin]
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | dist/
4 | npm-debug.log
5 | yarn-error.log
6 |
7 | build/.rpt2_cache
8 | tests/coverage
9 | /coverage
10 |
11 |
12 | # Editor directories and files
13 | .vscode
14 | .idea
15 | *.suo
16 | *.ntvs*
17 | *.njsproj
18 | *.sln
19 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016, Claudéric Demers
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.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Vue Smooth DnD
2 |
3 | A fast and lightweight drag&drop, sortable library for Vue.js with many configuration options covering many d&d scenarios.
4 |
5 | This library consists wrapper Vue.js components over [smooth-dnd](https://github.com/kutlugsahin/smooth-dnd) library.
6 |
7 | ## Demo
8 |
9 | View the demo here:
10 |
11 | - https://kutlugsahin.github.io/vue-smooth-dnd
12 |
13 | ## Installation
14 |
15 | ```shell
16 | npm i vue-smooth-dnd
17 | ```
18 |
19 | ## Usage
20 |
21 | ### Vue
22 |
23 | ```jsx
24 |
25 |
26 |
27 |
28 |
29 |
30 | {{item.data}}
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
56 | ```
57 |
58 | ## API: Container
59 |
60 | Component that contains the draggable elements or components. Each of its children should be wrapped by **Draggable** component
61 |
62 |
63 | ## Properties
64 |
65 | Properties define the visual behaviour of the library:
66 |
67 | | Property | Type | Default | Description |
68 | | - | :-: | :-: | - |
69 | | :orientation | string | `vertical` | Orientation of the container. Can be **horizontal** or **vertical**. |
70 | | :behaviour | string | `move` | Property to describe weather the dragging item will be moved or copied to target container. Can be **move** or **copy** or **drop-zone** or **contain**.
71 | | :tag | string or NodeDescription | `div` | *See descriptions below*
72 | | :group-name | string | `undefined` | Draggables can be moved between the containers having the same group names. If not set container will not accept drags from outside. This behaviour can be overriden by shouldAcceptDrop function. See below.
73 | | :lock-axis | string | `undefined` | Locks the movement axis of the dragging. Possible values are **x**, **y** or **undefined**.
74 | | :drag-handle-selector | string | `undefined` | Css selector to test for enabling dragging. If not given item can be grabbed from anywhere in its boundaries.
75 | | :non-drag-area-selector | string | `undefined` | Css selector to prevent dragging. Can be useful when you have form elements or selectable text somewhere inside your draggable item. It has a precedence over **dragHandleSelector**.
76 | | :drag-begin-delay | number | `0` (`200` for touch devices) | Time in milisecond. Delay to start dragging after item is pressed. Moving cursor before the delay more than 5px will cancel dragging.
77 | | :animation-duration | number | `250` | Animation duration in milisecond. To be consistent this animation duration will be applied to both drop and reorder animations.
78 | | :auto-scroll-enabled | boolean | `true` | First scrollable parent will scroll automatically if dragging item is close to boundaries.
79 | | :drag-class | string | `undefined` | Class to be added to the ghost item being dragged. The class will be added after it's added to the DOM so any transition in the class will be applied as intended.
80 | | :drop-class | string | `undefined` | Class to be added to the ghost item just before the drop animation begins.
81 | |:remove-on-drop-out|boolean|`undefined`|When set true onDrop will be called with a removedIndex if you drop element out of any relevant container|
82 | |:drop-placeholder|boolean,object|`undefined`|Options for drop placeholder. **className**, **animationDuration**, **showOnTop**|
83 |
84 |
85 | ### `tag`
86 |
87 | Tag name or the node definition to render the root element of the Container.
88 | Default value is 'div'.
89 |
90 | ```ts
91 | :tag="{value: 'table', props: {class: 'my-table'}}"
92 | ```
93 | ```ts
94 | tag="table"
95 | ```
96 |
97 | #### possible values
98 | - string : The tag name of the root element to be created
99 | - object : Node definition
100 | - value: string : tag name
101 | - props: data object to define element properties. see [https://vuejs.org/v2/guide/render-function.html#The-Data-Object-In-Depth](https://vuejs.org/v2/guide/render-function.html#The-Data-Object-In-Depth)
102 |
103 | ---
104 |
105 |
106 | ## Lifecycle
107 |
108 | The lifecycle of a drag is both described, and can be controlled, by a series of [callbacks](#callbacks) and [events](#events), which are illustrated below for a example **containing 3 containers**:
109 |
110 | ```
111 | Mouse Calls Callback / Event Parameters Notes
112 |
113 | down o Initial click
114 |
115 | move o Initial drag
116 | |
117 | | get-child-payload() index Function should return payload
118 | |
119 | | 3 x should-accept-drop() srcOptions, payload Fired for all containers
120 | |
121 | | 3 x drag-start dragResult Fired for all containers
122 | |
123 | | drag-enter
124 | v
125 |
126 | move o Drag over containers
127 | |
128 | | n x drag-leave Fired as draggable leaves container
129 | | n x drag-enter Fired as draggable enters container
130 | v
131 |
132 | up o Finish drag
133 |
134 | should-animate-drop() srcOptions, payload Fires once for dropped container
135 |
136 | 3 x drag-end dragResult Fired for all containers
137 |
138 | n x drop dropResult Fired only for droppable containers
139 | ```
140 |
141 | Note that `should-accept-drop` is fired before every `drag-start`, and before every `drag-end`, but has been omitted here for clarity.
142 |
143 | The `dragResult` parameter has the format:
144 |
145 | ```js
146 | dragResult: {
147 | payload,
148 | isSource,
149 | willAcceptDrop,
150 | }
151 | ```
152 |
153 | The `dropResult` parameter has the format:
154 |
155 | ```js
156 | dropResult: {
157 | addedIndex,
158 | removedIndex,
159 | payload,
160 | droppedElement,
161 | }
162 | ```
163 |
164 | Note that additional parameters can be passed to callbacks and event handlers by writing an interim handler *inline* in the markup:
165 |
166 | ```jsx
167 |
171 | ...
172 |
173 |
174 | ```
175 |
176 | This can provide handler functions context-sensitive data, such as the loop index or current item.
177 |
178 | ## Callbacks
179 |
180 | Callbacks provide additional logic and checks before and during user interaction.
181 |
182 | ### `get-child-payload()`
183 |
184 | The function to be called to get the payload object to be passed **onDrop** function.
185 |
186 | ```jsx
187 |
188 | ```
189 | ```ts
190 | getChildPayload (index) {
191 | return {
192 | // generate custom payload data here
193 | }
194 | }
195 | ```
196 |
197 | #### Parameters
198 | - **index** : `number` : index of the child item
199 |
200 | #### Returns
201 | - **payload** : `object`
202 |
203 |
204 | ### `should-accept-drop()`
205 |
206 | The function to be called by all containers before drag starts to determine the containers to which the drop is possible. Setting this function will override the **group-name** property and only the return value of this function will be taken into account.
207 |
208 | ```jsx
209 |
210 | ```
211 | ```ts
212 | shouldAcceptDrop (sourceContainerOptions, payload) {
213 | return true;
214 | }
215 | ```
216 |
217 | #### Parameters
218 |
219 | - **sourceContainerOptions** : `object` : options of the source container. (parent container of the dragged item)
220 | - **payload** : `object` : the payload object retrieved by calling [get-child-payload](#get-child-payload) function.
221 |
222 | #### Returns
223 | - **boolean** : **true / false**
224 |
225 |
226 | ### `should-animate-drop()`
227 |
228 | The function to be called by the target container to which the dragged item will be dropped.
229 | Sometimes dragged item's dimensions are not suitable with the target container and dropping animation can be wierd. So it can be disabled by returning **false**. If not set drop animations are enabled.
230 |
231 | ```jsx
232 |
233 | ```
234 | ```ts
235 | shouldAnimateDrop (sourceContainerOptions, payload) {
236 | return false;
237 | }
238 | ```
239 |
240 | #### Parameters
241 |
242 | - **sourceContainerOptions** : `object` : options of the source container. (parent container of the dragged item)
243 | - **payload** : `object` : the payload object retrieved by calling [get-child-payload](#get-child-payload) function.
244 |
245 | #### Returns
246 |
247 | - **boolean** : **true / false**
248 |
249 | ### `get-ghost-parent()`
250 |
251 | The function to be called to get the element that the dragged ghost will be appended. Default parent element is the container itself.
252 | The ghost element is positioned as 'fixed' and appended to given parent element.
253 | But if any anchestor of container has a transform property, ghost element will be positioned relative to that element which breaks the calculations. Thats why if you have any transformed parent element of Containers you should set this property so that it returns any element that has not transformed parent element.
254 | ```jsx
255 |
256 | ```
257 | ```ts
258 | getGhostParent() {
259 | // i.e return document.body;
260 | }
261 | ```
262 |
263 | #### Returns
264 |
265 | - **Element** : **Any DOM element that the ghost will be appended in**
266 |
267 | ---
268 |
269 | ## Events
270 |
271 | Events may call user-defined handlers at particular points in the drag-and-drop lifecycle.
272 |
273 | ### `@drag-start`
274 |
275 | Event to be emitted by all containers on drag start.
276 |
277 | ```jsx
278 |
279 | ```
280 | ```ts
281 | onDragStart (dragResult) {
282 | const { isSource, payload, willAcceptDrop } = dragResult
283 | }
284 | ```
285 |
286 | #### Parameters
287 |
288 | - **dragResult** : `object`
289 |
290 | - **payload** : `object` : the payload object that is returned by [get-child-payload](#get-child-payload). It will be undefined in case get-child-payload is not set.
291 | - **isSource** : `boolean` : true if it is called by the container which drag starts from otherwise false.
292 | - **willAcceptDrop** : `boolean` : true if the dragged item can be dropped into the container, otherwise false.
293 |
294 | ### `@drag-end`
295 |
296 | The function to be called by all containers on drag end. Called before [drop](#drop) event.
297 |
298 | ```jsx
299 |
300 | ```
301 | ```ts
302 | onDragEnd (dragResult) {
303 | const { isSource, payload, willAcceptDrop } = dragResult
304 | }
305 | ```
306 |
307 | #### Parameters
308 |
309 | - **dragResult** : `object`
310 |
311 | - **isSource** : `boolean` : true if it is called by the container which drag starts from, otherwise false.
312 | - **payload** : `object` : the payload object that is returned by [get-child-payload](#get-child-payload) function. It will be undefined in case get-child-payload is not set.
313 | - **willAcceptDrop** : `boolean` : true if the dragged item can be dropped into the container, otherwise false.
314 |
315 | ### `@drag-enter`
316 |
317 | The event to be emitted by the relevant container whenever a dragged item enters its boundaries while dragging.
318 |
319 | ```jsx
320 |
321 | ```
322 | ```ts
323 | onDragEnter () {
324 | ...
325 | }
326 | ```
327 |
328 | ### `@drag-leave`
329 |
330 | The event to be emitted by the relevant container whenever a dragged item leaves its boundaries while dragging.
331 |
332 | ```jsx
333 |
334 | ```
335 | ```ts
336 | onDragLeave () {
337 | ...
338 | }
339 | ```
340 |
341 | ### `@drop-ready`
342 |
343 | The function to be called by the container which is being drag over, when the index of possible drop position changed in container. Basically it is called each time the draggables in a container slides for opening a space for dragged item. **dropResult** is the only parameter passed to the function which contains the following properties.
344 |
345 | ```jsx
346 |
347 | ```
348 | ```js
349 | onDropReady(dropResult) {
350 | const { removedIndex, addedIndex, payload, element } = dropResult;
351 | ...
352 | }
353 | ```
354 | #### Parameters
355 | - **dropResult** : `object`
356 | - **removedIndex** : `number` : index of the removed children. Will be `null` if no item is removed.
357 | - **addedIndex** : `number` : index to add droppped item. Will be `null` if no item is added.
358 | - **payload** : `object` : the payload object retrieved by calling *getChildPayload* function.
359 | - **element** : `DOMElement` : the DOM element that is moved
360 |
361 | ### `@drop`
362 |
363 | The event to be emitted by any relevant container when drop is over. (After drop animation ends). Source container and any container that could accept drop is considered relevant.
364 |
365 | ```jsx
366 |
367 | ```
368 | ```ts
369 | onDrop (dropResult) {
370 | const { removedIndex, addedIndex, payload, element } = dropResult;
371 | ...
372 | }
373 | ```
374 |
375 | #### Parameters
376 |
377 | - **dropResult** : `object`
378 |
379 | - **removedIndex** : `number` : index of the removed child. Will be `null` if no item is removed.
380 | - **addedIndex** : `number` : index to add dropped item. Will be `null` if no item is added.
381 | - **payload** : `object` : the payload object retrieved by calling [get-child-payload](#get-child-payload) function.
382 | - **droppedElement** : `DOMElement` : the DOM element that is moved
383 |
384 | ## API: Draggable
385 |
386 | Wrapper component for Container's children. Every child element should be wrapped with **Draggable** component.
387 |
388 | ## Properties
389 |
390 | ### `tag`
391 |
392 | Tag name or the node definition to render the root element of the Draggable.
393 | Default value is 'div'.
394 |
395 | ```jsx
396 | :tag="{value: 'tr', props: {class: 'my-table-row'}}"
397 | ```
398 | ```jsx
399 | tag="tr"
400 | ```
401 |
402 | #### possible values
403 | - string : The tag name of the root element to be created
404 | - object : Node definition
405 | - value: string : tag name
406 | - props: data object to define element properties. see [https://vuejs.org/v2/guide/render-function.html#The-Data-Object-In-Depth](https://vuejs.org/v2/guide/render-function.html#The-Data-Object-In-Depth)
407 |
--------------------------------------------------------------------------------
/build/banner.txt:
--------------------------------------------------------------------------------
1 | Bundle of: <%= pkg.name %>
2 | Generated: <%= moment().format('YYYY-MM-DD') %>
3 | Version: <%= pkg.version %>
4 |
--------------------------------------------------------------------------------
/build/rollup.js:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------------------
2 | // setup
3 | // ------------------------------------------------------------------------------------------
4 |
5 | import path from 'path'
6 | import license from 'rollup-plugin-license'
7 | import commonjs from 'rollup-plugin-commonjs'
8 | import uglify from 'rollup-plugin-uglify'
9 | import buble from 'rollup-plugin-buble'
10 |
11 | const pkg = require('../package.json')
12 | const external = Object.keys(pkg.dependencies || {})
13 | const name = pkg.name
14 | const className = name.replace(/(^\w|-\w)/g, c => c.replace('-', '').toUpperCase())
15 |
16 | function output (ext, format = 'umd') {
17 | return {
18 | name: className,
19 | file: `dist/${name}.${ext}`,
20 | format: format,
21 | exports: 'named',
22 | globals: {
23 | 'smooth-dnd': 'SmoothDnD'
24 | }
25 | }
26 | }
27 |
28 | // ------------------------------------------------------------------------------------------
29 | // build
30 | // ------------------------------------------------------------------------------------------
31 |
32 | const umd = {
33 | input: 'src/main.js',
34 | external: external,
35 | output: output('js'),
36 | plugins: [
37 | license({
38 | banner: {
39 | file: path.join(__dirname, 'banner.txt')
40 | },
41 | }),
42 | commonjs(),
43 | buble()
44 | ]
45 | }
46 |
47 | const min = Object.assign({}, umd, {
48 | output: output('min.js'),
49 | plugins: [...umd.plugins, uglify()]
50 | })
51 |
52 | const es = Object.assign({}, umd, {
53 | output: output('esm.js', 'es')
54 | })
55 |
56 | export default [umd, min, es]
57 |
--------------------------------------------------------------------------------
/demo/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["env", { "modules": false }],
4 | "stage-3"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/demo/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/demo/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/demo/README.md:
--------------------------------------------------------------------------------
1 | # Vue Smooth DnD Demo
2 |
3 | View the demo:
4 |
5 | - [kutlugsahin.github.io/vue-smooth-dnd](https://kutlugsahin.github.io/vue-smooth-dnd)
6 |
7 | View and edit the demo on Code Sandbox:
8 |
9 | - [codesandbox.io/s/github/kutlugsahin/vue-smooth-dnd/tree/master/demo](https://codesandbox.io/s/github/kutlugsahin/vue-smooth-dnd/tree/master/demo)
10 |
11 | View the source code on GitHub:
12 |
13 | - [github.com/kutlugsahin/vue-smooth-dnd/tree/master/demo](https://github.com/kutlugsahin/vue-smooth-dnd/tree/master/demo)
14 |
15 | Run the demo locally:
16 |
17 | ```bash
18 | npm install
19 | npm run dev
20 | ```
21 |
22 | View at [http://localhost:8080](http://localhost:8080)
23 |
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Vue Demo
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-smooth-dnd-demo",
3 | "version": "1.0.0",
4 | "description": "Vue Smooth DnD - Demo project",
5 | "scripts": {
6 | "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot",
7 | "demo": "cross-env NODE_ENV=production webpack --progress --hide-modules",
8 | "deploy": "npm run demo && gh-pages -d dist"
9 | },
10 | "dependencies": {
11 | "vue": "^2.5.16",
12 | "vue-router": "^3.0.1"
13 | },
14 | "browserslist": [
15 | "> 1%",
16 | "last 2 versions",
17 | "not ie <= 8"
18 | ],
19 | "devDependencies": {
20 | "babel-core": "^6.26.3",
21 | "babel-loader": "^7.1.4",
22 | "babel-preset-env": "^1.6.0",
23 | "babel-preset-stage-3": "^6.24.1",
24 | "copy-webpack-plugin": "^4.5.1",
25 | "cross-env": "^5.1.5",
26 | "css-loader": "^0.28.11",
27 | "file-loader": "^1.1.4",
28 | "gh-pages": "^2.0.1",
29 | "node-sass": "^4.9.2",
30 | "object-assign": "4.1.1",
31 | "raw-loader": "^0.5.1",
32 | "sass-loader": "^6.0.6",
33 | "uglifyjs-webpack-plugin": "^1.2.5",
34 | "vue-loader": "^13.7.2",
35 | "vue-markdown-loader": "^2.4.1",
36 | "vue-template-compiler": "^2.5.16",
37 | "webpack": "^3.6.0",
38 | "webpack-dev-server": "^2.9.1"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/demo/src/assets/demos.css:
--------------------------------------------------------------------------------
1 |
2 | .draggable-item {
3 | height: 50px;
4 | line-height: 50px;
5 | text-align: center;
6 | display: block;
7 | background-color: #fff;
8 | outline: 0;
9 | border: 1px solid rgba(0, 0, 0, .125);
10 | margin-bottom: 2px;
11 | margin-top: 2px;
12 | cursor: default;
13 | user-select: none;
14 | }
15 |
16 | .draggable-item-horizontal {
17 | height: 300px;
18 | padding: 10px;
19 | line-height: 100px;
20 | text-align: center;
21 | /* width : 200px; */
22 | display: block;
23 | background-color: #fff;
24 | outline: 0;
25 | border: 1px solid rgba(0, 0, 0, .125);
26 | margin-right: 4px;
27 | cursor: default;
28 | }
29 |
30 | .dragging {
31 | background-color: yellow;
32 | }
33 |
34 | .card-scene {
35 | padding: 50px;
36 | /* background-color: #fff; */
37 | }
38 |
39 | .card-container {
40 | width: 320px;
41 | padding: 10px;
42 | margin: 5px;
43 | margin-right: 45px;
44 | background-color: #f3f3f3;
45 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.12), 0 1px 1px rgba(0, 0, 0, 0.24);
46 | }
47 |
48 | .card {
49 | margin: 5px;
50 | /* border: 1px solid #ccc; */
51 | background-color: white;
52 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.12), 0 1px 1px rgba(0, 0, 0, 0.24);
53 | padding: 10px;
54 | }
55 |
56 | .card-column-header {
57 | font-size: 18px;
58 | }
59 |
60 | .column-drag-handle {
61 | cursor: move;
62 | padding: 5px;
63 | }
64 |
65 | .card-ghost {
66 | transition: transform 0.18s ease;
67 | transform: rotateZ(5deg)
68 | }
69 |
70 | .card-ghost-drop {
71 | transition: transform 0.18s ease-in-out;
72 | transform: rotateZ(0deg)
73 | }
74 |
75 | .opacity-ghost {
76 | transition: all .18s ease;
77 | opacity: 0.8;
78 | /* transform: rotateZ(5deg); */
79 | background-color: cornflowerblue;
80 | box-shadow: 3px 3px 10px 3px rgba(0, 0, 0, 0.3);
81 | }
82 |
83 | .opacity-ghost-drop {
84 | opacity: 1;
85 | /* transform: rotateZ(0deg); */
86 | background-color: white;
87 | box-shadow: 3px 3px 10px 3px rgba(0, 0, 0, 0.0);
88 | }
89 |
90 |
91 | .form-demo {
92 | width: 650px;
93 | margin-left: auto;
94 | margin-right: auto;
95 | margin-top: 50px;
96 | display: flex
97 | }
98 |
99 | .form {
100 | flex: 3;
101 | /* width: 500px; */
102 | /* background-color: #f3f3f3; */
103 | border: 1px solid rgba(0, 0, 0, .125);
104 | border-radius: 6px;
105 | box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.08), 0px 3px 3px rgba(0, 0, 0, 0.08);
106 | }
107 |
108 | .form-fields-panel {
109 | flex: 1;
110 | margin-right: 50px;
111 | }
112 |
113 |
114 | .form-ghost {
115 | transition: 0.18s ease;
116 | box-shadow: 1px 1px 5px 2px rgba(0, 0, 0, 0.08);
117 | }
118 |
119 | .form-ghost-drop {
120 | box-shadow: 0 0 2px 5px rgba(0, 0, 0, 0.0);
121 | }
122 |
123 | .drop-preview {
124 | background-color: rgba(150, 150, 200, 0.1);
125 | border: 1px dashed #abc;
126 | margin: 5px;
127 | }
128 |
129 | .cards-drop-preview {
130 | background-color: rgba(150, 150, 200, 0.1);
131 | border: 1px dashed #abc;
132 | margin: 5px 45px 5px 5px;
133 | }
134 |
--------------------------------------------------------------------------------
/demo/src/assets/form.css:
--------------------------------------------------------------------------------
1 |
2 |
3 | .form-field {
4 | height: 50px;
5 | width: 250px;
6 | line-height: 30px;
7 | vertical-align: middle;
8 | padding: 10px;
9 | background-color: #fff;
10 | border: 1px solid #eee;
11 | border-top: 1px solid #fff;
12 | border-bottom: 1px solid #ddd;
13 | /* background-color: #eee; */
14 | cursor: move;
15 |
16 | }
17 |
18 | .form-line {
19 | padding: 20px 30px;
20 | background-color: #f8f9fa;
21 | border: 1px solid transparent;
22 | border-radius: 6px;
23 | transition: all .3s ease;
24 | transition-property: border-color, background-color;
25 | cursor: move;
26 | }
27 |
28 | .form-line.selected {
29 | /* border: 1px solid #ddd; */
30 | background-color: #f8f9fa;
31 | /* box-shadow: 0px 0px 10px 10px #ccc; */
32 | }
33 |
34 |
35 | button {
36 | padding: .8em 1.2em;
37 | border: 0;
38 | outline: 0;
39 | margin-bottom: .5em;
40 | vertical-align: middle;
41 | color: #eee;
42 | background-color: cornflowerblue;
43 | cursor: pointer;
44 | }
45 |
46 | button[disabled] {
47 | opacity: .5;
48 | }
49 |
50 | .form-submit-button {
51 | width: 100%;
52 | height: 40px;
53 | }
54 |
55 | button,
56 | label {
57 | user-select: none;
58 | }
59 |
60 | .label {
61 | margin-bottom: 5px;
62 | }
63 |
64 | .field {
65 | cursor: auto;
66 | }
67 |
68 | .field input, .field textarea, .field select {
69 | width: 100%;
70 | padding: 10px;
71 | border: 1px solid #ddd;
72 | border-radius: 3px;
73 | outline: none;
74 | transition: border-color .3s ease;
75 | }
76 |
77 | .field input[type="radio"], .field input[type="checkbox"] {
78 | width: auto;
79 | }
80 |
81 | .field input:focus, textarea:focus {
82 | border: 1px solid #80bdff;
83 | box-shadow: 0 0 3px 0px #80bdff;
84 | }
85 |
86 | .field-group input, .field-group textarea, .field-group select {
87 | width: 49%;
88 | margin-right: 2%;
89 | }
90 |
91 | input:last-child {
92 | margin-right: 0;
93 | }
94 |
95 |
--------------------------------------------------------------------------------
/demo/src/assets/header.css:
--------------------------------------------------------------------------------
1 | .header {
2 | height: 60px;
3 | background-color: #41b883;
4 | box-shadow: 2px 0px 4px #ccc;
5 | z-index: 0;
6 | color: white;
7 | font-size: 20px;
8 | padding: 0 50px;
9 | vertical-align: middle;
10 | line-height: 60px;
11 | }
12 |
13 | .header.open {
14 | padding: 0 20px;
15 | }
16 |
17 | .source-code {
18 | float: right;
19 | height: 60px;
20 | line-height: 60px;
21 | vertical-align: middle;
22 | cursor: pointer;
23 | }
24 |
25 | .source-code img {
26 | width: 30px;
27 | height: 30px;
28 | vertical-align: middle;
29 | }
30 |
31 | .source-code span {
32 | font-size: 14px;
33 | color: #eee;
34 | margin-left: 10px;
35 | vertical-align: middle;
36 | }
37 |
--------------------------------------------------------------------------------
/demo/src/assets/layout.css:
--------------------------------------------------------------------------------
1 | * {
2 | font-family: 'Oxygen', sans-serif;
3 | }
4 |
5 | body {
6 | height: 100%;
7 | width: 100%;
8 | padding: 0;
9 | margin: 0;
10 | }
11 |
12 | .app {
13 | position: fixed;
14 | width: 100%;
15 | height: 100%;
16 | display: flex;
17 | }
18 |
19 | .content {
20 | display: flex;
21 | flex-direction: column;
22 | flex: 1 1 auto;
23 | background-color: #f8f9fa;
24 | min-width: 0;
25 | }
26 |
27 | .demo {
28 | flex: 1;
29 | overflow: auto;
30 | min-width: 0;
31 | }
32 |
33 |
34 | .wide-page {
35 | margin: 50px auto;
36 | padding: 0 50px;
37 | }
38 |
39 | .simple-page {
40 | max-width: 500px;
41 | margin: 50px auto;
42 | }
43 |
--------------------------------------------------------------------------------
/demo/src/assets/nav.css:
--------------------------------------------------------------------------------
1 | .nav-button {
2 | display: none;
3 | position: fixed;
4 | left: 10px;
5 | top: 20px;
6 | z-index: 3;
7 | width: 30px;
8 | height: 30px;
9 | -webkit-transform: rotate(0deg);
10 | -moz-transform: rotate(0deg);
11 | -o-transform: rotate(0deg);
12 | transform: rotate(0deg);
13 | -webkit-transition: .3s ease-in-out;
14 | -moz-transition: .3s ease-in-out;
15 | -o-transition: .3s ease-in-out;
16 | transition: .3s ease-in-out;
17 | cursor: pointer;
18 | }
19 |
20 | .nav-button.open {
21 | left: 310px;
22 | }
23 |
24 | .nav-button.open span {
25 | background: #ccc;
26 | }
27 |
28 | .nav-button span {
29 | display: block;
30 | position: absolute;
31 | height: 4px;
32 | width: 100%;
33 | background: #fff;
34 | border-radius: 4px;
35 | opacity: 1;
36 | left: 0;
37 | -webkit-transform: rotate(0deg);
38 | -moz-transform: rotate(0deg);
39 | -o-transform: rotate(0deg);
40 | transform: rotate(0deg);
41 | -webkit-transition: .25s ease-in-out;
42 | -moz-transition: .25s ease-in-out;
43 | -o-transition: .25s ease-in-out;
44 | transition: .25s ease-in-out;
45 | }
46 |
47 | /* Icon 3 */
48 |
49 | .nav-button span:nth-child(1) {
50 | top: 0px;
51 | }
52 |
53 | .nav-button span:nth-child(2), .nav-button span:nth-child(3) {
54 | top: 8px;
55 | }
56 |
57 | .nav-button span:nth-child(4) {
58 | top: 16px;
59 | }
60 |
61 | .nav-button.open span:nth-child(1) {
62 | top: 18px;
63 | width: 0%;
64 | left: 50%;
65 | }
66 |
67 | .nav-button.open span:nth-child(2) {
68 | -webkit-transform: rotate(45deg);
69 | -moz-transform: rotate(45deg);
70 | -o-transform: rotate(45deg);
71 | transform: rotate(45deg);
72 | }
73 |
74 | .nav-button.open span:nth-child(3) {
75 | -webkit-transform: rotate(-45deg);
76 | -moz-transform: rotate(-45deg);
77 | -o-transform: rotate(-45deg);
78 | transform: rotate(-45deg);
79 | }
80 |
81 | .nav-button.open span:nth-child(4) {
82 | top: 8px;
83 | width: 0%;
84 | left: 50%;
85 | }
86 |
87 | .navigator {
88 | width: 350px;
89 | flex-shrink: 0;
90 | box-shadow: 0px 2px 5px #ccc;
91 | background-color: white;
92 | z-index: 1;
93 | transition: width .3s ease-in-out;
94 | overflow: hidden;
95 | }
96 |
97 | @media (max-width: 700px) {
98 | .navigator {
99 | position: fixed;
100 | height: 100%;
101 | left: 0;
102 | top: 0;
103 | bottom: 0;
104 | }
105 |
106 | .nav-button {
107 | display: block;
108 | }
109 | }
110 |
111 |
112 | .nav-panel {
113 | flex-shrink: 0;
114 | width: 300px;
115 | }
116 |
117 |
118 | .nav.closed {
119 | width: 0;
120 | }
121 |
122 | .nav-content {
123 | padding-top: 0px;
124 | width: 350px;
125 | height: 100%;
126 | overflow-y: auto;
127 | }
128 |
129 | .nav-header h3 {
130 | padding-left: 10px;
131 | }
132 |
133 | .divider {
134 | height: 1px;
135 | border-bottom: 1px solid #ddd;
136 | margin-top: -2px;
137 | }
138 |
139 | .menu-section h4 {
140 | color: #444;
141 | padding-left: 10px;
142 | }
143 |
144 | .menu-section ul {
145 | color: #666;
146 | list-style: none;
147 | padding: 0;
148 | margin: 0;
149 | }
150 |
151 | .menu-section li {
152 | font-size: 14px;
153 | margin: 0;
154 | padding: .5em .5em .5em 30px;
155 | cursor: pointer;
156 | margin-bottom: 5px;
157 | }
158 |
159 | .menu-section li:hover {
160 | background-color: ghostwhite;
161 | }
162 |
163 | .router-link-active,
164 | .menu-section li.selected {
165 | background-color: ghostwhite;
166 | }
167 |
168 |
--------------------------------------------------------------------------------
/demo/src/config/navigation.js:
--------------------------------------------------------------------------------
1 | function section (title, pages) {
2 | return { title, pages };
3 | }
4 |
5 | function page (name, title) {
6 | return { name, title, component: require(`../pages/${name}.vue`).default };
7 | }
8 |
9 | export default [
10 | section('Showcase', [
11 | page('cards', 'Card board'),
12 | page('form', 'Form elements')
13 | ]),
14 | section('Sortables', [
15 | page('simple', 'Sortable with default options'),
16 | page('simple-scroller', 'Sortable inside scroller'),
17 | page('simple-horizontal', 'Horizontal sortable')
18 | ]),
19 | section('Groups', [
20 | page('groups', 'DnD between groups'),
21 | page('copy', 'Copy draggable'),
22 | page('nested', 'Nested vertical sortable')
23 | ]),
24 | section('Properties', [
25 | page('lock-axis', 'Lock axis'),
26 | page('drag-handle', 'Drag handle'),
27 | page('drag-class', 'Drag and Drop classes'),
28 | page('drag-delay', 'Drag begin delay of 500ms'),
29 | page('transition-duration', 'Animation duration 1000ms'),
30 | page('table', 'Custom tag (table)')
31 | ]),
32 | section('Interaction', [
33 | page('events', 'Callbacks and Events')
34 | ])
35 | ];
36 |
--------------------------------------------------------------------------------
/demo/src/config/router.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import VueRouter from 'vue-router';
3 | import navigation from './navigation';
4 |
5 | // convert navigation to routes
6 | const routes = navigation.reduce((routes, section) => {
7 | section.pages.forEach(page => {
8 | const name = page.name;
9 | routes.push({
10 | name,
11 | path: `/${name}`,
12 | component: page.component,
13 | meta: {
14 | title: page.title
15 | }
16 | });
17 | });
18 | return routes;
19 | }, []);
20 |
21 | // set up router
22 | Vue.use(VueRouter);
23 | export default new VueRouter({
24 | routes: [
25 | {
26 | path: '/',
27 | redirect: 'cards'
28 | },
29 | ...routes
30 | ]
31 | });
32 |
--------------------------------------------------------------------------------
/demo/src/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
32 |
33 |
47 |
48 |
49 |
50 |
51 |
80 |
--------------------------------------------------------------------------------
/demo/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | import './utils/polyfills'
4 |
5 | import './assets/layout.css'
6 | import './assets/header.css'
7 | import './assets/nav.css'
8 | import './assets/demos.css'
9 | import './assets/form.css'
10 |
11 | import router from './config/router'
12 | import Demo from './index.vue'
13 |
14 | Vue.config.productionTip = false
15 |
16 | /* eslint-disable no-new */
17 | window.app = new Vue({
18 | el: '#app',
19 | router,
20 | components: { Demo },
21 | template: ' '
22 | })
23 |
--------------------------------------------------------------------------------
/demo/src/pages/cards.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
12 |
13 | ☰
14 | {{ column.name }}
15 |
16 |
onCardDrop(column.id, e)"
19 | @drag-start="(e) => log('drag start', e)"
20 | @drag-end="(e) => log('drag end', e)"
21 | :get-child-payload="getCardPayload(column.id)"
22 | drag-class="card-ghost"
23 | drop-class="card-ghost-drop"
24 | :drop-placeholder="dropPlaceholderOptions"
25 | >
26 |
27 |
28 |
{{ card.data }}
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
149 |
--------------------------------------------------------------------------------
/demo/src/pages/copy.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {{item.data}}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | {{item.data}}
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | {{item.data}}
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
78 |
--------------------------------------------------------------------------------
/demo/src/pages/drag-class.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{item.data}}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
35 |
--------------------------------------------------------------------------------
/demo/src/pages/drag-delay.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{item.data}}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
35 |
--------------------------------------------------------------------------------
/demo/src/pages/drag-handle.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | ☰
7 | {{item.data}}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
36 |
--------------------------------------------------------------------------------
/demo/src/pages/events.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
39 |
40 |
55 |
56 |
57 |
58 |
59 |
176 |
177 |
215 |
--------------------------------------------------------------------------------
/demo/src/pages/form.vue:
--------------------------------------------------------------------------------
1 |
2 |
62 |
63 |
64 |
97 |
--------------------------------------------------------------------------------
/demo/src/pages/groups.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {{item.data}}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | {{item.data}}
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | {{item.data}}
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | {{item.data}}
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
95 |
96 |
109 |
--------------------------------------------------------------------------------
/demo/src/pages/lock-axis.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{item.data}}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
35 |
--------------------------------------------------------------------------------
/demo/src/pages/nested.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{item.data}}
7 |
8 |
9 |
10 |
Sortable List
11 |
12 |
13 |
14 |
15 | {{q.data}}
16 |
17 |
18 |
19 |
Sortable List
20 |
21 |
22 |
23 |
24 | {{t.data}}
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
126 |
127 |
132 |
--------------------------------------------------------------------------------
/demo/src/pages/simple-horizontal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{item.data}}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
35 |
--------------------------------------------------------------------------------
/demo/src/pages/simple-scroller.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{item.data}}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
35 |
--------------------------------------------------------------------------------
/demo/src/pages/simple.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{item.data}}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
41 |
--------------------------------------------------------------------------------
/demo/src/pages/table.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Lorem
7 | Ipsum
8 | Sit
9 | Amed
10 |
11 |
12 |
13 |
14 | Row {{item.data}} Column 1
15 | Row {{item.data}} Column 2
16 | Row {{item.data}} Column 3
17 | Row {{item.data}} Column 4
18 |
19 |
20 |
21 |
22 |
23 |
24 |
46 |
47 |
67 |
--------------------------------------------------------------------------------
/demo/src/pages/transition-duration.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{item.data}}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
35 |
--------------------------------------------------------------------------------
/demo/src/utils/helpers.js:
--------------------------------------------------------------------------------
1 |
2 | export const applyDrag = (arr, dragResult) => {
3 | const { removedIndex, addedIndex, payload } = dragResult
4 | if (removedIndex === null && addedIndex === null) return arr
5 |
6 | const result = [...arr]
7 | let itemToAdd = payload
8 |
9 | if (removedIndex !== null) {
10 | itemToAdd = result.splice(removedIndex, 1)[0]
11 | }
12 |
13 | if (addedIndex !== null) {
14 | result.splice(addedIndex, 0, itemToAdd)
15 | }
16 |
17 | return result
18 | }
19 |
20 | export const generateItems = (count, creator) => {
21 | const result = []
22 | for (let i = 0; i < count; i++) {
23 | result.push(creator(i))
24 | }
25 | return result
26 | }
27 |
--------------------------------------------------------------------------------
/demo/src/utils/polyfills.js:
--------------------------------------------------------------------------------
1 | Object.assign = require('object-assign')
2 |
--------------------------------------------------------------------------------
/demo/webpack.config.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 | var path = require('path');
3 | var webpack = require('webpack');
4 | var UglifyJsPlugin = require('uglifyjs-webpack-plugin');
5 | var CopyWebpackPlugin = require('copy-webpack-plugin');
6 |
7 | var src = path.resolve(__dirname, './src');
8 | var dist = path.resolve(__dirname, './dist');
9 | var lib = path.resolve(__dirname, '../src/main.js');
10 | // var smoothDnDlib = path.resolve(__dirname, '../dist/vue-smooth-dnd.js')
11 | function resolve (path) {
12 | return src + '/' + (path || '');
13 | }
14 |
15 | module.exports = {
16 | entry: './src/main.js',
17 | output: {
18 | path: dist,
19 | publicPath: '',
20 | filename: 'build.js'
21 | },
22 | module: {
23 | rules: [
24 | {
25 | test: /\.txt$/,
26 | use: 'raw-loader'
27 | },
28 | {
29 | test: /\.css$/,
30 | use: [
31 | 'vue-style-loader',
32 | 'css-loader'
33 | ],
34 | },
35 | {
36 | test: /\.scss$/,
37 | use: [
38 | 'vue-style-loader',
39 | 'css-loader',
40 | 'sass-loader'
41 | ],
42 | },
43 | {
44 | test: /\.sass$/,
45 | use: [
46 | 'vue-style-loader',
47 | 'css-loader',
48 | 'sass-loader?indentedSyntax'
49 | ],
50 | },
51 | {
52 | test: /\.vue$/,
53 | loader: 'vue-loader',
54 | options: {
55 | loaders: {
56 | // Since sass-loader (weirdly) has SCSS as its default parse mode, we map
57 | // the "scss" and "sass" values for the lang attribute to the right configs here.
58 | // other preprocessors should work out of the box, no loader config like this necessary.
59 | 'scss': [
60 | 'vue-style-loader',
61 | 'css-loader',
62 | 'sass-loader'
63 | ],
64 | 'sass': [
65 | 'vue-style-loader',
66 | 'css-loader',
67 | 'sass-loader?indentedSyntax'
68 | ]
69 | }
70 | // other vue-loader options go here
71 | }
72 | },
73 | {
74 | test: /\.js$/,
75 | loader: 'babel-loader',
76 | exclude: /node_modules/
77 | },
78 | {
79 | test: /\.(png|jpg|gif|svg)$/,
80 | loader: 'file-loader',
81 | options: {
82 | name: '[name].[ext]?[hash]'
83 | }
84 | },
85 | {
86 | test: /\.md$/,
87 | loader: 'vue-markdown-loader',
88 | options: {
89 | wrapper: 'markdown-page',
90 | }
91 | }
92 | ]
93 | },
94 | resolve: {
95 | alias: {
96 | '@': resolve(''),
97 | 'vue$': 'vue/dist/vue.esm.js',
98 | },
99 | extensions: ['*', '.js', '.vue', '.json']
100 | },
101 | devServer: {
102 | historyApiFallback: true,
103 | noInfo: true,
104 | overlay: true
105 | },
106 | performance: {
107 | hints: false
108 | },
109 | devtool: '#eval-source-map'
110 | };
111 |
112 | if (fs.existsSync(lib)) {
113 | module.exports.resolve.alias['vue-smooth-dnd'] = lib;
114 | // module.exports.resolve.alias['smooth-dnd'] = smoothDnDlib;
115 | }
116 |
117 | if (process.env.NODE_ENV === 'production') {
118 | module.exports.devtool = '#source-map';
119 | // http://vue-loader.vuejs.org/en/workflow/production.html
120 | module.exports.plugins = (module.exports.plugins || []).concat([
121 | new CopyWebpackPlugin(['index.html'], { from: './', to: dist }),
122 | new webpack.DefinePlugin({
123 | 'process.env': {
124 | NODE_ENV: '"production"'
125 | }
126 | }),
127 | new UglifyJsPlugin({
128 | sourceMap: true,
129 | uglifyOptions: {
130 | warnings: false
131 | }
132 | }),
133 | new webpack.LoaderOptionsPlugin({
134 | minimize: true
135 | })
136 | ]);
137 | }
138 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-smooth-dnd",
3 | "version": "0.8.1",
4 | "description": "Vue wrappers for smooth-dnd",
5 | "author": "Kutlu Sahin",
6 | "homepage": "https://kutlugsahin.github.io/vue-smooth-dnd/",
7 | "repository": {
8 | "type": "git",
9 | "url": "https://github.com/kutlugsahin/vue-smooth-dnd"
10 | },
11 | "contributors": [
12 | {
13 | "name": "Dave Stewart",
14 | "url": "https://github.com/davestewart"
15 | }
16 | ],
17 | "keywords": [
18 | "Vue",
19 | "Vue.js",
20 | "sortable",
21 | "drag and drop",
22 | "drag&drop",
23 | "drag",
24 | "drop",
25 | "draggable",
26 | "dnd",
27 | "smooth-dnd"
28 | ],
29 | "main": "dist/vue-smooth-dnd.js",
30 | "module": "dist/vue-smooth-dnd.esm.js",
31 | "jsnext:main": "dist/vue-smooth-dnd.esm.js",
32 | "files": [
33 | "dist/*"
34 | ],
35 | "scripts": {
36 | "dev": "rollup -c build/rollup.js -w",
37 | "build": "rollup -c build/rollup.js",
38 | "demo": "cd demo && npm run dev",
39 | "lint": "./node_modules/.bin/eslint src/*",
40 | "test": "jest --watchAll --verbose",
41 | "unit": "jest --config jest.config.js --coverage"
42 | },
43 | "license": "MIT",
44 | "bugs": {
45 | "url": "https://github.com/kutlugsahin/vue-smooth-dnd/issues"
46 | },
47 | "dependencies": {
48 | "smooth-dnd": "0.12.1"
49 | },
50 | "devDependencies": {
51 | "babel-jest": "^23.4.0",
52 | "cross-env": "^5.2.0",
53 | "eslint": "^5.6.0",
54 | "eslint-config-standard": "^12.0.0",
55 | "eslint-plugin-import": "^2.14.0",
56 | "eslint-plugin-node": "^7.0.1",
57 | "eslint-plugin-promise": "^4.0.1",
58 | "eslint-plugin-standard": "^4.0.0",
59 | "jest": "^23.4.1",
60 | "rollup": "^0.67.3",
61 | "rollup-plugin-buble": "^0.19.1",
62 | "rollup-plugin-commonjs": "^8.4.1",
63 | "rollup-plugin-license": "^0.6.0",
64 | "rollup-plugin-typescript2": "0.14.0",
65 | "rollup-plugin-uglify": "^3.0.0"
66 | },
67 | "jest": {
68 | "transform": {
69 | "^.+\\.tsx?$": "ts-jest"
70 | },
71 | "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
72 | "moduleFileExtensions": [
73 | "ts",
74 | "tsx",
75 | "js",
76 | "jsx",
77 | "json",
78 | "node"
79 | ]
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/Container.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable curly */
2 | import { smoothDnD, dropHandlers } from 'smooth-dnd';
3 | import { getTagProps, validateTagProp } from './utils';
4 |
5 | smoothDnD.dropHandler = dropHandlers.reactDropHandler().handler;
6 | smoothDnD.wrapChild = false;
7 |
8 | const eventEmitterMap = {
9 | 'drag-start': 'onDragStart',
10 | 'drag-end': 'onDragEnd',
11 | 'drop': 'onDrop',
12 | 'drag-enter': 'onDragEnter',
13 | 'drag-leave': 'onDragLeave',
14 | 'drop-ready': 'onDropReady'
15 | };
16 |
17 | function getContainerOptions (props, context) {
18 | const options = Object.keys(props).reduce((result, key) => {
19 | const optionName = key;
20 | const prop = props[optionName];
21 |
22 | if (prop !== undefined) {
23 | if (typeof prop === 'function') {
24 | if (eventEmitterMap[optionName]) {
25 | result[eventEmitterMap[optionName]] = (params) => {
26 | context.$emit(optionName, params);
27 | };
28 | } else {
29 | result[optionName] = (...params) => {
30 | return (prop)(...params);
31 | };
32 | }
33 | } else {
34 | result[optionName] = prop;
35 | }
36 | }
37 |
38 | return result;
39 | }, {});
40 |
41 | return options;
42 | }
43 |
44 | const mapOptions = context => {
45 | const props = Object.assign({}, context.$props, context.$listeners);
46 | return getContainerOptions(props, context);
47 | };
48 |
49 | export default {
50 | name: 'Container',
51 | mounted () {
52 | this.containerElement = this.$refs.container || this.$el;
53 | this.container = smoothDnD(this.containerElement, mapOptions(this));
54 | },
55 | updated () {
56 | if (
57 | this.$refs.container !== this.containerElement &&
58 | this.$el !== this.containerElement
59 | ) {
60 | if (this.container) {
61 | this.container.dispose();
62 | }
63 | this.containerElement = this.$refs.container || this.$el;
64 | this.container = smoothDnD(this.containerElement, mapOptions(this));
65 | return;
66 | }
67 |
68 | this.container.setOptions(mapOptions(this));
69 | },
70 | destroyed () {
71 | if (this.container) {
72 | this.container.dispose();
73 | }
74 | },
75 | props: {
76 | behaviour: String,
77 | groupName: String,
78 | orientation: String,
79 | dragHandleSelector: String,
80 | nonDragAreaSelector: String,
81 | dragBeginDelay: Number,
82 | animationDuration: Number,
83 | autoScrollEnabled: { type: Boolean, default: true },
84 | lockAxis: String,
85 | dragClass: String,
86 | dropClass: String,
87 | removeOnDropOut: { type: Boolean, default: false },
88 | 'drag-start': Function,
89 | 'drag-end': Function,
90 | drop: Function,
91 | getChildPayload: Function,
92 | shouldAnimateDrop: Function,
93 | shouldAcceptDrop: Function,
94 | 'drag-enter': Function,
95 | 'drag-leave': Function,
96 | tag: {
97 | validator: validateTagProp,
98 | default: 'div',
99 | },
100 | getGhostParent: Function,
101 | 'drop-ready': Function,
102 | dropPlaceholder: [Object, Boolean]
103 | },
104 | render: function (createElement) {
105 | const tagProps = getTagProps(this);
106 | return createElement(
107 | tagProps.value,
108 | Object.assign({}, { ref: 'container' }, tagProps.props),
109 | this.$slots.default,
110 | );
111 | }
112 | };
113 |
--------------------------------------------------------------------------------
/src/Draggable.js:
--------------------------------------------------------------------------------
1 | import { constants } from 'smooth-dnd';
2 | import { getTagProps, validateTagProp } from './utils';
3 |
4 | const wrapChild = (createElement, ctx) => {
5 | const tagProps = getTagProps(ctx, constants.wrapperClass);
6 | return createElement(
7 | tagProps.value,
8 | Object.assign({}, tagProps.props),
9 | ctx.$slots.default
10 | );
11 | };
12 |
13 | export default {
14 | name: 'Draggable',
15 | props: {
16 | tag: {
17 | validator: validateTagProp,
18 | default: 'div'
19 | }
20 | },
21 | render: function (createElement) {
22 | return wrapChild(createElement, this);
23 | }
24 | };
25 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import Container from './Container';
2 | import Draggable from './Draggable';
3 | export * from 'smooth-dnd';
4 |
5 | export {
6 | Container,
7 | Draggable,
8 | };
9 |
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | const isArray = function (obj) {
2 | return Object.prototype.toString.call(obj) === '[object Array]';
3 | };
4 |
5 | export function getTagProps (ctx, tagClasses) {
6 | const tag = ctx.$props.tag;
7 | if (tag) {
8 | if (typeof tag === 'string') {
9 | const result = { value: tag };
10 | if (tagClasses) {
11 | result.props = { class: tagClasses };
12 | }
13 | return result;
14 | } else if (typeof tag === 'object') {
15 | const result = { value: tag.value || 'div', props: tag.props || {} };
16 |
17 | if (tagClasses) {
18 | if (result.props.class) {
19 | if (isArray(result.props.class)) {
20 | result.props.class.push(tagClasses);
21 | } else {
22 | result.props.class = [tagClasses, result.props.class];
23 | }
24 | } else {
25 | result.props.class = tagClasses;
26 | }
27 | }
28 |
29 | return result;
30 | }
31 | }
32 | return { value: 'div' };
33 | }
34 |
35 | export function validateTagProp (tag) {
36 | if (tag) {
37 | if (typeof tag === 'string') return true;
38 | if (typeof tag === 'object') {
39 | if (
40 | typeof tag.value === 'string' ||
41 | typeof tag.value === 'function' ||
42 | typeof tag.value === 'object'
43 | ) {
44 | return true;
45 | }
46 | }
47 | return false;
48 | }
49 | return true;
50 | }
51 |
--------------------------------------------------------------------------------