Merge remote-tracking branch 'upstream/main'

This commit is contained in:
2026-08-26 09:31:34 +08:00
parent 9b8c4bfab6
commit 394eb0285d
34137 changed files with 3589424 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016-2019 David Desmaisons
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+387
View File
@@ -0,0 +1,387 @@
<p align="center"><img width="140"src="https://raw.githubusercontent.com/SortableJS/Vue.Draggable/master/logo.svg?sanitize=true"></p>
<h1 align="center">Vue.Draggable</h1>
[![CircleCI](https://circleci.com/gh/SortableJS/Vue.Draggable.svg?style=shield)](https://circleci.com/gh/SortableJS/Vue.Draggable)
[![Coverage](https://codecov.io/gh/SortableJS/Vue.Draggable/branch/master/graph/badge.svg)](https://codecov.io/gh/SortableJS/Vue.Draggable)
[![codebeat badge](https://codebeat.co/badges/7a6c27c8-2d0b-47b9-af55-c2eea966e713)](https://codebeat.co/projects/github-com-sortablejs-vue-draggable-master)
[![GitHub open issues](https://img.shields.io/github/issues/SortableJS/Vue.Draggable.svg)](https://github.com/SortableJS/Vue.Draggable/issues?q=is%3Aopen+is%3Aissue)
[![npm download](https://img.shields.io/npm/dt/vuedraggable.svg?maxAge=30)](https://www.npmjs.com/package/vuedraggable)
[![npm download per month](https://img.shields.io/npm/dm/vuedraggable.svg)](https://www.npmjs.com/package/vuedraggable)
[![npm version](https://img.shields.io/npm/v/vuedraggable.svg)](https://www.npmjs.com/package/vuedraggable)
[![MIT License](https://img.shields.io/github/license/SortableJS/Vue.Draggable.svg)](https://github.com/SortableJS/Vue.Draggable/blob/master/LICENSE)
Vue component (Vue.js 2.0) or directive (Vue.js 1.0) allowing drag-and-drop and synchronization with view model array.
Based on and offering all features of [Sortable.js](https://github.com/RubaXa/Sortable)
## Demo
![demo gif](https://raw.githubusercontent.com/SortableJS/Vue.Draggable/master/example.gif)
## Live Demos
https://sortablejs.github.io/Vue.Draggable/
https://david-desmaisons.github.io/draggable-example/
## Features
* Full support of [Sortable.js](https://github.com/RubaXa/Sortable) features:
* Supports touch devices
* Supports drag handles and selectable text
* Smart auto-scrolling
* Support drag and drop between different lists
* No jQuery dependency
* Keeps in sync HTML and view model list
* Compatible with Vue.js 2.0 transition-group
* Cancellation support
* Events reporting any changes when full control is needed
* Reuse existing UI library components (such as [vuetify](https://vuetifyjs.com), [element](http://element.eleme.io/), or [vue material](https://vuematerial.io) etc...) and make them draggable using `tag` and `componentData` props
## Backers
<a href="https://flatlogic.com/admin-dashboards">
<img width="190" style="margin-top: 10px;" src="https://flatlogic.com/assets/logo-d9e7751df5fddd11c911945a75b56bf72bcfe809a7f6dca0e32d7b407eacedae.svg">
</a>
Admin Dashboard Templates made with Vue, React and Angular.
## Donate
Find this project useful? You can buy me a :coffee: or a :beer:
[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=GYAEKQZJ4FQT2&currency_code=USD&source=url)
## Installation
### With npm or yarn
```bash
yarn add vuedraggable
npm i -S vuedraggable
```
**Beware it is vuedraggable for Vue 2.0 and not vue-draggable which is for version 1.0**
### with direct link
```html
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.5.2/vue.min.js"></script>
<!-- CDNJS :: Sortable (https://cdnjs.com/) -->
<script src="//cdn.jsdelivr.net/npm/sortablejs@1.8.4/Sortable.min.js"></script>
<!-- CDNJS :: Vue.Draggable (https://cdnjs.com/) -->
<script src="//cdnjs.cloudflare.com/ajax/libs/Vue.Draggable/2.20.0/vuedraggable.umd.min.js"></script>
```
[cf example section](https://github.com/SortableJS/Vue.Draggable/tree/master/example)
## For Vue.js 2.0
Use draggable component:
### Typical use:
``` html
<draggable v-model="myArray" group="people" @start="drag=true" @end="drag=false">
<div v-for="element in myArray" :key="element.id">{{element.name}}</div>
</draggable>
```
.vue file:
``` js
import draggable from 'vuedraggable'
...
export default {
components: {
draggable,
},
...
```
### With `transition-group`:
``` html
<draggable v-model="myArray">
<transition-group>
<div v-for="element in myArray" :key="element.id">
{{element.name}}
</div>
</transition-group>
</draggable>
```
Draggable component should directly wrap the draggable elements, or a `transition-component` containing the draggable elements.
### With footer slot:
``` html
<draggable v-model="myArray" draggable=".item">
<div v-for="element in myArray" :key="element.id" class="item">
{{element.name}}
</div>
<button slot="footer" @click="addPeople">Add</button>
</draggable>
```
### With header slot:
``` html
<draggable v-model="myArray" draggable=".item">
<div v-for="element in myArray" :key="element.id" class="item">
{{element.name}}
</div>
<button slot="header" @click="addPeople">Add</button>
</draggable>
```
### With Vuex:
```html
<draggable v-model='myList'>
```
```javascript
computed: {
myList: {
get() {
return this.$store.state.myList
},
set(value) {
this.$store.commit('updateList', value)
}
}
}
```
### Props
#### value
Type: `Array`<br>
Required: `false`<br>
Default: `null`
Input array to draggable component. Typically same array as referenced by inner element v-for directive.<br>
This is the preferred way to use Vue.draggable as it is compatible with Vuex.<br>
It should not be used directly but only though the `v-model` directive:
```html
<draggable v-model="myArray">
```
#### list
Type: `Array`<br>
Required: `false`<br>
Default: `null`
Alternative to the `value` prop, list is an array to be synchronized with drag-and-drop.<br>
The main difference is that `list` prop is updated by draggable component using splice method, whereas `value` is immutable.<br>
**Do not use in conjunction with value prop.**
#### All sortable options
New in version 2.19
Sortable options can be set directly as vue.draggable props since version 2.19.
This means that all [sortable option](https://github.com/RubaXa/Sortable#options) are valid sortable props with the notable exception of all the method starting by "on" as draggable component expose the same API via events.
kebab-case propery are supported: for example `ghost-class` props will be converted to `ghostClass` sortable option.
Example setting handle, sortable and a group option:
```HTML
<draggable
v-model="list"
handle=".handle"
:group="{ name: 'people', pull: 'clone', put: false }"
ghost-class="ghost"
:sort="false"
@change="log"
>
<!-- -->
</draggable>
```
#### tag
Type: `String`<br>
Default: `'div'`
HTML node type of the element that draggable component create as outer element for the included slot.<br>
It is also possible to pass the name of vue component as element. In this case, draggable attribute will be passed to the create component.<br>
See also [componentData](#componentdata) if you need to set props or event to the created component.
#### clone
Type: `Function`<br>
Required: `false`<br>
Default: `(original) => { return original;}`<br>
Function called on the source component to clone element when clone option is true. The unique argument is the viewModel element to be cloned and the returned value is its cloned version.<br>
By default vue.draggable reuses the viewModel element, so you have to use this hook if you want to clone or deep clone it.
#### move
Type: `Function`<br>
Required: `false`<br>
Default: `null`<br>
If not null this function will be called in a similar way as [Sortable onMove callback](https://github.com/RubaXa/Sortable#move-event-object).
Returning false will cancel the drag operation.
```javascript
function onMoveCallback(evt, originalEvent){
...
// return false; — for cancel
}
```
evt object has same property as [Sortable onMove event](https://github.com/RubaXa/Sortable#move-event-object), and 3 additional properties:
- `draggedContext`: context linked to dragged element
- `index`: dragged element index
- `element`: dragged element underlying view model element
- `futureIndex`: potential index of the dragged element if the drop operation is accepted
- `relatedContext`: context linked to current drag operation
- `index`: target element index
- `element`: target element view model element
- `list`: target list
- `component`: target VueComponent
HTML:
```HTML
<draggable :list="list" :move="checkMove">
```
javascript:
```javascript
checkMove: function(evt){
return (evt.draggedContext.element.name!=='apple');
}
```
See complete example: [Cancel.html](https://github.com/SortableJS/Vue.Draggable/blob/master/examples/Cancel.html), [cancel.js](https://github.com/SortableJS/Vue.Draggable/blob/master/examples/script/cancel.js)
#### componentData
Type: `Object`<br>
Required: `false`<br>
Default: `null`<br>
This props is used to pass additional information to child component declared by [tag props](#tag).<br>
Value:
* `props`: props to be passed to the child component
* `attrs`: attrs to be passed to the child component
* `on`: events to be subscribe in the child component
Example (using [element UI library](http://element.eleme.io/#/en-US)):
```HTML
<draggable tag="el-collapse" :list="list" :component-data="getComponentData()">
<el-collapse-item v-for="e in list" :title="e.title" :name="e.name" :key="e.name">
<div>{{e.description}}</div>
</el-collapse-item>
</draggable>
```
```javascript
methods: {
handleChange() {
console.log('changed');
},
inputChanged(value) {
this.activeNames = value;
},
getComponentData() {
return {
on: {
change: this.handleChange,
input: this.inputChanged
},
attrs:{
wrap: true
},
props: {
value: this.activeNames
}
};
}
}
```
### Events
* Support for Sortable events:
`start`, `add`, `remove`, `update`, `end`, `choose`, `unchoose`, `sort`, `filter`, `clone`<br>
Events are called whenever onStart, onAdd, onRemove, onUpdate, onEnd, onChoose, onUnchoose, onSort, onClone are fired by Sortable.js with the same argument.<br>
[See here for reference](https://github.com/RubaXa/Sortable#event-object-demo)
Note that SortableJS OnMove callback is mapped with the [move prop](https://github.com/SortableJS/Vue.Draggable/blob/master/README.md#move)
HTML:
```HTML
<draggable :list="list" @end="onEnd">
```
* change event
`change` event is triggered when list prop is not null and the corresponding array is altered due to drag-and-drop operation.<br>
This event is called with one argument containing one of the following properties:
- `added`: contains information of an element added to the array
- `newIndex`: the index of the added element
- `element`: the added element
- `removed`: contains information of an element removed from to the array
- `oldIndex`: the index of the element before remove
- `element`: the removed element
- `moved`: contains information of an element moved within the array
- `newIndex`: the current index of the moved element
- `oldIndex`: the old index of the moved element
- `element`: the moved element
### Slots
Limitation: neither header or footer slot works in conjunction with transition-group.
#### Header
Use the `header` slot to add none-draggable element inside the vuedraggable component.
Important: it should be used in conjunction with draggable option to tag draggable element.
Note that header slot will always be added before the default slot regardless its position in the template.
Ex:
``` html
<draggable v-model="myArray" draggable=".item">
<div v-for="element in myArray" :key="element.id" class="item">
{{element.name}}
</div>
<button slot="header" @click="addPeople">Add</button>
</draggable>
```
#### Footer
Use the `footer` slot to add none-draggable element inside the vuedraggable component.
Important: it should be used in conjunction with draggable option to tag draggable elements.
Note that footer slot will always be added after the default slot regardless its position in the template.
Ex:
``` html
<draggable v-model="myArray" draggable=".item">
<div v-for="element in myArray" :key="element.id" class="item">
{{element.name}}
</div>
<button slot="footer" @click="addPeople">Add</button>
</draggable>
```
### Gotchas
- Vue.draggable children should always map the list or value prop using a v-for directive
* You may use [header](https://github.com/SortableJS/Vue.Draggable#header) and [footer](https://github.com/SortableJS/Vue.Draggable#footer) slot to by-pass this limitation.
- Children elements inside v-for should be keyed as any element in Vue.js. Be carefull to provide revelant key values in particular:
* typically providing array index as keys won't work as key should be linked to the items content
* cloned elements should provide updated keys, it is doable using the [clone props](#clone) for example
### Example
* [Clone](https://sortablejs.github.io/Vue.Draggable/#/custom-clone)
* [Handle](https://sortablejs.github.io/Vue.Draggable/#/handle)
* [Transition](https://sortablejs.github.io/Vue.Draggable/#/transition-example-2)
* [Nested](https://sortablejs.github.io/Vue.Draggable/#/nested-example)
* [Table](https://sortablejs.github.io/Vue.Draggable/#/table-example)
### Full demo example
[draggable-example](https://github.com/David-Desmaisons/draggable-example)
## For Vue.js 1.0
[See here](documentation/Vue.draggable.for.ReadME.md)
```
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+93
View File
@@ -0,0 +1,93 @@
{
"name": "vuedraggable",
"version": "2.24.3",
"description": "draggable component for vue",
"license": "MIT",
"main": "dist/vuedraggable.umd.min.js",
"types": "src/vuedraggable.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/SortableJS/Vue.Draggable.git"
},
"private": false,
"scripts": {
"serve": "vue-cli-service serve ./example/main.js --open --mode local",
"build:doc": "vue-cli-service build ./example/main.js --dest docs --mode development",
"build": "vue-cli-service build --name vuedraggable --entry ./src/vuedraggable.js --target lib",
"lint": "vue-cli-service lint src example",
"prepublishOnly": "npm run lint && npm run test:unit && npm run build:doc && npm run build",
"test:unit": "vue-cli-service test:unit --coverage",
"test:coverage": "vue-cli-service test:unit --coverage --verbose && codecov"
},
"keywords": [
"vue",
"vuejs",
"drag",
"and",
"drop",
"list",
"Sortable.js",
"component",
"nested"
],
"dependencies": {
"sortablejs": "1.10.2"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^3.11.0",
"@vue/cli-plugin-eslint": "^3.11.0",
"@vue/cli-plugin-unit-jest": "^3.11.0",
"@vue/cli-service": "^3.11.0",
"@vue/eslint-config-prettier": "^4.0.1",
"@vue/test-utils": "^1.1.0",
"babel-core": "7.0.0-bridge.0",
"babel-eslint": "^10.0.1",
"babel-jest": "^23.6.0",
"bootstrap": "^4.3.1",
"codecov": "^3.2.0",
"component-fixture": "^0.4.1",
"element-ui": "^2.5.4",
"eslint": "^5.8.0",
"eslint-plugin-vue": "^5.0.0",
"font-awesome": "^4.7.0",
"jquery": "^3.5.1",
"vue": "^2.6.12",
"vue-cli-plugin-component": "^1.10.5",
"vue-router": "^3.0.2",
"vue-server-renderer": "^2.6.12",
"vue-template-compiler": "^2.6.12",
"vuetify": "^1.5.16",
"vuex": "^3.1.1"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"@vue/prettier"
],
"rules": {},
"parserOptions": {
"parser": "babel-eslint"
}
},
"postcss": {
"plugins": {
"autoprefixer": {}
}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
],
"files": [
"dist/*.css",
"dist/*.map",
"dist/*.js",
"src/*"
],
"module": "dist/vuedraggable.umd.js"
}
+36
View File
@@ -0,0 +1,36 @@
function getConsole() {
if (typeof window !== "undefined") {
return window.console;
}
return global.console;
}
const console = getConsole();
function cached(fn) {
const cache = Object.create(null);
return function cachedFn(str) {
const hit = cache[str];
return hit || (cache[str] = fn(str));
};
}
const regex = /-(\w)/g;
const camelize = cached(str =>
str.replace(regex, (_, c) => (c ? c.toUpperCase() : ""))
);
function removeNode(node) {
if (node.parentElement !== null) {
node.parentElement.removeChild(node);
}
}
function insertNodeAt(fatherNode, node, position) {
const refNode =
position === 0
? fatherNode.children[0]
: fatherNode.children[position - 1].nextSibling;
fatherNode.insertBefore(node, refNode);
}
export { insertNodeAt, camelize, console, removeNode };
+75
View File
@@ -0,0 +1,75 @@
declare module 'vuedraggable' {
import Vue, { VueConstructor } from 'vue';
type CombinedVueInstance<
Instance extends Vue,
Data,
Methods,
Computed,
Props
> = Data & Methods & Computed & Props & Instance;
type ExtendedVue<
Instance extends Vue,
Data,
Methods,
Computed,
Props
> = VueConstructor<
CombinedVueInstance<Instance, Data, Methods, Computed, Props> & Vue
>;
export type DraggedContext<T> = {
index: number;
futureIndex: number;
element: T;
};
export type DropContext<T> = {
index: number;
component: Vue;
element: T;
};
export type Rectangle = {
top: number;
right: number;
bottom: number;
left: number;
width: number;
height: number;
};
export type MoveEvent<T> = {
originalEvent: DragEvent;
dragged: Element;
draggedContext: DraggedContext<T>;
draggedRect: Rectangle;
related: Element;
relatedContext: DropContext<T>;
relatedRect: Rectangle;
from: Element;
to: Element;
willInsertAfter: boolean;
isTrusted: boolean;
};
const draggable: ExtendedVue<
Vue,
{},
{},
{},
{
options: any;
list: any[];
value: any[];
noTransitionOnDrag?: boolean;
clone: any;
tag?: string | null;
move: any;
componentData: any;
}
>;
export default draggable;
}
+485
View File
@@ -0,0 +1,485 @@
import Sortable from "sortablejs";
import { insertNodeAt, camelize, console, removeNode } from "./util/helper";
function buildAttribute(object, propName, value) {
if (value === undefined) {
return object;
}
object = object || {};
object[propName] = value;
return object;
}
function computeVmIndex(vnodes, element) {
return vnodes.map(elt => elt.elm).indexOf(element);
}
function computeIndexes(slots, children, isTransition, footerOffset) {
if (!slots) {
return [];
}
const elmFromNodes = slots.map(elt => elt.elm);
const footerIndex = children.length - footerOffset;
const rawIndexes = [...children].map((elt, idx) =>
idx >= footerIndex ? elmFromNodes.length : elmFromNodes.indexOf(elt)
);
return isTransition ? rawIndexes.filter(ind => ind !== -1) : rawIndexes;
}
function emit(evtName, evtData) {
this.$nextTick(() => this.$emit(evtName.toLowerCase(), evtData));
}
function delegateAndEmit(evtName) {
return evtData => {
if (this.realList !== null) {
this["onDrag" + evtName](evtData);
}
emit.call(this, evtName, evtData);
};
}
function isTransitionName(name) {
return ["transition-group", "TransitionGroup"].includes(name);
}
function isTransition(slots) {
if (!slots || slots.length !== 1) {
return false;
}
const [{ componentOptions }] = slots;
if (!componentOptions) {
return false;
}
return isTransitionName(componentOptions.tag);
}
function getSlot(slot, scopedSlot, key) {
return slot[key] || (scopedSlot[key] ? scopedSlot[key]() : undefined);
}
function computeChildrenAndOffsets(children, slot, scopedSlot) {
let headerOffset = 0;
let footerOffset = 0;
const header = getSlot(slot, scopedSlot, "header");
if (header) {
headerOffset = header.length;
children = children ? [...header, ...children] : [...header];
}
const footer = getSlot(slot, scopedSlot, "footer");
if (footer) {
footerOffset = footer.length;
children = children ? [...children, ...footer] : [...footer];
}
return { children, headerOffset, footerOffset };
}
function getComponentAttributes($attrs, componentData) {
let attributes = null;
const update = (name, value) => {
attributes = buildAttribute(attributes, name, value);
};
const attrs = Object.keys($attrs)
.filter(key => key === "id" || key.startsWith("data-"))
.reduce((res, key) => {
res[key] = $attrs[key];
return res;
}, {});
update("attrs", attrs);
if (!componentData) {
return attributes;
}
const { on, props, attrs: componentDataAttrs } = componentData;
update("on", on);
update("props", props);
Object.assign(attributes.attrs, componentDataAttrs);
return attributes;
}
const eventsListened = ["Start", "Add", "Remove", "Update", "End"];
const eventsToEmit = ["Choose", "Unchoose", "Sort", "Filter", "Clone"];
const readonlyProperties = ["Move", ...eventsListened, ...eventsToEmit].map(
evt => "on" + evt
);
var draggingElement = null;
const props = {
options: Object,
list: {
type: Array,
required: false,
default: null
},
value: {
type: Array,
required: false,
default: null
},
noTransitionOnDrag: {
type: Boolean,
default: false
},
clone: {
type: Function,
default: original => {
return original;
}
},
element: {
type: String,
default: "div"
},
tag: {
type: String,
default: null
},
move: {
type: Function,
default: null
},
componentData: {
type: Object,
required: false,
default: null
}
};
const draggableComponent = {
name: "draggable",
inheritAttrs: false,
props,
data() {
return {
transitionMode: false,
noneFunctionalComponentMode: false
};
},
render(h) {
const slots = this.$slots.default;
this.transitionMode = isTransition(slots);
const { children, headerOffset, footerOffset } = computeChildrenAndOffsets(
slots,
this.$slots,
this.$scopedSlots
);
this.headerOffset = headerOffset;
this.footerOffset = footerOffset;
const attributes = getComponentAttributes(this.$attrs, this.componentData);
return h(this.getTag(), attributes, children);
},
created() {
if (this.list !== null && this.value !== null) {
console.error(
"Value and list props are mutually exclusive! Please set one or another."
);
}
if (this.element !== "div") {
console.warn(
"Element props is deprecated please use tag props instead. See https://github.com/SortableJS/Vue.Draggable/blob/master/documentation/migrate.md#element-props"
);
}
if (this.options !== undefined) {
console.warn(
"Options props is deprecated, add sortable options directly as vue.draggable item, or use v-bind. See https://github.com/SortableJS/Vue.Draggable/blob/master/documentation/migrate.md#options-props"
);
}
},
mounted() {
this.noneFunctionalComponentMode =
this.getTag().toLowerCase() !== this.$el.nodeName.toLowerCase() &&
!this.getIsFunctional();
if (this.noneFunctionalComponentMode && this.transitionMode) {
throw new Error(
`Transition-group inside component is not supported. Please alter tag value or remove transition-group. Current tag value: ${this.getTag()}`
);
}
const optionsAdded = {};
eventsListened.forEach(elt => {
optionsAdded["on" + elt] = delegateAndEmit.call(this, elt);
});
eventsToEmit.forEach(elt => {
optionsAdded["on" + elt] = emit.bind(this, elt);
});
const attributes = Object.keys(this.$attrs).reduce((res, key) => {
res[camelize(key)] = this.$attrs[key];
return res;
}, {});
const options = Object.assign({}, this.options, attributes, optionsAdded, {
onMove: (evt, originalEvent) => {
return this.onDragMove(evt, originalEvent);
}
});
!("draggable" in options) && (options.draggable = ">*");
this._sortable = new Sortable(this.rootContainer, options);
this.computeIndexes();
},
beforeDestroy() {
if (this._sortable !== undefined) this._sortable.destroy();
},
computed: {
rootContainer() {
return this.transitionMode ? this.$el.children[0] : this.$el;
},
realList() {
return this.list ? this.list : this.value;
}
},
watch: {
options: {
handler(newOptionValue) {
this.updateOptions(newOptionValue);
},
deep: true
},
$attrs: {
handler(newOptionValue) {
this.updateOptions(newOptionValue);
},
deep: true
},
realList() {
this.computeIndexes();
}
},
methods: {
getIsFunctional() {
const { fnOptions } = this._vnode;
return fnOptions && fnOptions.functional;
},
getTag() {
return this.tag || this.element;
},
updateOptions(newOptionValue) {
for (var property in newOptionValue) {
const value = camelize(property);
if (readonlyProperties.indexOf(value) === -1) {
this._sortable.option(value, newOptionValue[property]);
}
}
},
getChildrenNodes() {
if (this.noneFunctionalComponentMode) {
return this.$children[0].$slots.default;
}
const rawNodes = this.$slots.default;
return this.transitionMode ? rawNodes[0].child.$slots.default : rawNodes;
},
computeIndexes() {
this.$nextTick(() => {
this.visibleIndexes = computeIndexes(
this.getChildrenNodes(),
this.rootContainer.children,
this.transitionMode,
this.footerOffset
);
});
},
getUnderlyingVm(htmlElt) {
const index = computeVmIndex(this.getChildrenNodes() || [], htmlElt);
if (index === -1) {
//Edge case during move callback: related element might be
//an element different from collection
return null;
}
const element = this.realList[index];
return { index, element };
},
getUnderlyingPotencialDraggableComponent({ __vue__: vue }) {
if (
!vue ||
!vue.$options ||
!isTransitionName(vue.$options._componentTag)
) {
if (
!("realList" in vue) &&
vue.$children.length === 1 &&
"realList" in vue.$children[0]
)
return vue.$children[0];
return vue;
}
return vue.$parent;
},
emitChanges(evt) {
this.$nextTick(() => {
this.$emit("change", evt);
});
},
alterList(onList) {
if (this.list) {
onList(this.list);
return;
}
const newList = [...this.value];
onList(newList);
this.$emit("input", newList);
},
spliceList() {
const spliceList = list => list.splice(...arguments);
this.alterList(spliceList);
},
updatePosition(oldIndex, newIndex) {
const updatePosition = list =>
list.splice(newIndex, 0, list.splice(oldIndex, 1)[0]);
this.alterList(updatePosition);
},
getRelatedContextFromMoveEvent({ to, related }) {
const component = this.getUnderlyingPotencialDraggableComponent(to);
if (!component) {
return { component };
}
const list = component.realList;
const context = { list, component };
if (to !== related && list && component.getUnderlyingVm) {
const destination = component.getUnderlyingVm(related);
if (destination) {
return Object.assign(destination, context);
}
}
return context;
},
getVmIndex(domIndex) {
const indexes = this.visibleIndexes;
const numberIndexes = indexes.length;
return domIndex > numberIndexes - 1 ? numberIndexes : indexes[domIndex];
},
getComponent() {
return this.$slots.default[0].componentInstance;
},
resetTransitionData(index) {
if (!this.noTransitionOnDrag || !this.transitionMode) {
return;
}
var nodes = this.getChildrenNodes();
nodes[index].data = null;
const transitionContainer = this.getComponent();
transitionContainer.children = [];
transitionContainer.kept = undefined;
},
onDragStart(evt) {
this.context = this.getUnderlyingVm(evt.item);
evt.item._underlying_vm_ = this.clone(this.context.element);
draggingElement = evt.item;
},
onDragAdd(evt) {
const element = evt.item._underlying_vm_;
if (element === undefined) {
return;
}
removeNode(evt.item);
const newIndex = this.getVmIndex(evt.newIndex);
this.spliceList(newIndex, 0, element);
this.computeIndexes();
const added = { element, newIndex };
this.emitChanges({ added });
},
onDragRemove(evt) {
insertNodeAt(this.rootContainer, evt.item, evt.oldIndex);
if (evt.pullMode === "clone") {
removeNode(evt.clone);
return;
}
const oldIndex = this.context.index;
this.spliceList(oldIndex, 1);
const removed = { element: this.context.element, oldIndex };
this.resetTransitionData(oldIndex);
this.emitChanges({ removed });
},
onDragUpdate(evt) {
removeNode(evt.item);
insertNodeAt(evt.from, evt.item, evt.oldIndex);
const oldIndex = this.context.index;
const newIndex = this.getVmIndex(evt.newIndex);
this.updatePosition(oldIndex, newIndex);
const moved = { element: this.context.element, oldIndex, newIndex };
this.emitChanges({ moved });
},
updateProperty(evt, propertyName) {
evt.hasOwnProperty(propertyName) &&
(evt[propertyName] += this.headerOffset);
},
computeFutureIndex(relatedContext, evt) {
if (!relatedContext.element) {
return 0;
}
const domChildren = [...evt.to.children].filter(
el => el.style["display"] !== "none"
);
const currentDOMIndex = domChildren.indexOf(evt.related);
const currentIndex = relatedContext.component.getVmIndex(currentDOMIndex);
const draggedInList = domChildren.indexOf(draggingElement) !== -1;
return draggedInList || !evt.willInsertAfter
? currentIndex
: currentIndex + 1;
},
onDragMove(evt, originalEvent) {
const onMove = this.move;
if (!onMove || !this.realList) {
return true;
}
const relatedContext = this.getRelatedContextFromMoveEvent(evt);
const draggedContext = this.context;
const futureIndex = this.computeFutureIndex(relatedContext, evt);
Object.assign(draggedContext, { futureIndex });
const sendEvt = Object.assign({}, evt, {
relatedContext,
draggedContext
});
return onMove(sendEvt, originalEvent);
},
onDragEnd() {
this.computeIndexes();
draggingElement = null;
}
}
};
if (typeof window !== "undefined" && "Vue" in window) {
window.Vue.component("draggable", draggableComponent);
}
export default draggableComponent;