数通智联化工云平台
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

319 lines
7.8 KiB

import Sortable from "sortablejs";
import { insertNodeAt, removeNode } from "./util/htmlHelper";
import { console } from "./util/console";
import {
getComponentAttributes,
createSortableOption,
getValidSortableEntries
} from "./core/componentBuilderHelper";
import { computeComponentStructure } from "./core/renderHelper";
import { events } from "./core/sortableEvents";
import { h, defineComponent, nextTick } from "vue";
function emit(evtName, evtData) {
nextTick(() => this.$emit(evtName.toLowerCase(), evtData));
}
function manage(evtName) {
return (evtData, originalElement) => {
if (this.realList !== null) {
return this[`onDrag${evtName}`](evtData, originalElement);
}
};
}
function manageAndEmit(evtName) {
const delegateCallBack = manage.call(this, evtName);
return (evtData, originalElement) => {
delegateCallBack.call(this, evtData, originalElement);
emit.call(this, evtName, evtData);
};
}
let draggingElement = null;
const props = {
list: {
type: Array,
required: false,
default: null
},
modelValue: {
type: Array,
required: false,
default: null
},
itemKey: {
type: [String, Function],
required: true
},
clone: {
type: Function,
default: original => {
return original;
}
},
tag: {
type: String,
default: "div"
},
move: {
type: Function,
default: null
},
componentData: {
type: Object,
required: false,
default: null
}
};
const emits = [
"update:modelValue",
"change",
...[...events.manageAndEmit, ...events.emit].map(evt => evt.toLowerCase())
];
const draggableComponent = defineComponent({
name: "draggable",
inheritAttrs: false,
props,
emits,
data() {
return {
error: false
};
},
render() {
try {
this.error = false;
const { $slots, $attrs, tag, componentData, realList, getKey } = this;
const componentStructure = computeComponentStructure({
$slots,
tag,
realList,
getKey
});
this.componentStructure = componentStructure;
const attributes = getComponentAttributes({ $attrs, componentData });
return componentStructure.render(h, attributes);
} catch (err) {
this.error = true;
return h("pre", { style: { color: "red" } }, err.stack);
}
},
created() {
if (this.list !== null && this.modelValue !== null) {
console.error(
"modelValue and list props are mutually exclusive! Please set one or another."
);
}
},
mounted() {
if (this.error) {
return;
}
const { $attrs, $el, componentStructure } = this;
componentStructure.updated();
const sortableOptions = createSortableOption({
$attrs,
callBackBuilder: {
manageAndEmit: event => manageAndEmit.call(this, event),
emit: event => emit.bind(this, event),
manage: event => manage.call(this, event)
}
});
const targetDomElement = $el.nodeType === 1 ? $el : $el.parentElement;
this._sortable = new Sortable(targetDomElement, sortableOptions);
this.targetDomElement = targetDomElement;
targetDomElement.__draggable_component__ = this;
},
updated() {
this.componentStructure.updated();
},
beforeUnmount() {
if (this._sortable !== undefined) this._sortable.destroy();
},
computed: {
realList() {
const { list } = this;
return list ? list : this.modelValue;
},
getKey() {
const { itemKey } = this;
if (typeof itemKey === "function") {
return itemKey;
}
return element => element[itemKey];
}
},
watch: {
$attrs: {
handler(newOptionValue) {
const { _sortable } = this;
if (!_sortable) return;
getValidSortableEntries(newOptionValue).forEach(([key, value]) => {
_sortable.option(key, value);
});
},
deep: true
}
},
methods: {
getUnderlyingVm(domElement) {
return this.componentStructure.getUnderlyingVm(domElement) || null;
},
getUnderlyingPotencialDraggableComponent(htmElement) {
//TODO check case where you need to see component children
return htmElement.__draggable_component__;
},
emitChanges(evt) {
nextTick(() => this.$emit("change", evt));
},
alterList(onList) {
if (this.list) {
onList(this.list);
return;
}
const newList = [...this.modelValue];
onList(newList);
this.$emit("update:modelValue", newList);
},
spliceList() {
// @ts-ignore
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) {
const destination = component.getUnderlyingVm(related) || {};
return { ...destination, ...context };
}
return context;
},
getVmIndexFromDomIndex(domIndex) {
return this.componentStructure.getVmIndexFromDomIndex(
domIndex,
this.targetDomElement
);
},
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.getVmIndexFromDomIndex(evt.newIndex);
// @ts-ignore
this.spliceList(newIndex, 0, element);
const added = { element, newIndex };
this.emitChanges({ added });
},
onDragRemove(evt) {
insertNodeAt(this.$el, evt.item, evt.oldIndex);
if (evt.pullMode === "clone") {
removeNode(evt.clone);
return;
}
const { index: oldIndex, element } = this.context;
// @ts-ignore
this.spliceList(oldIndex, 1);
const removed = { element, oldIndex };
this.emitChanges({ removed });
},
onDragUpdate(evt) {
removeNode(evt.item);
insertNodeAt(evt.from, evt.item, evt.oldIndex);
const oldIndex = this.context.index;
const newIndex = this.getVmIndexFromDomIndex(evt.newIndex);
this.updatePosition(oldIndex, newIndex);
const moved = { element: this.context.element, oldIndex, newIndex };
this.emitChanges({ moved });
},
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.getVmIndexFromDomIndex(
currentDomIndex
);
const draggedInList = domChildren.indexOf(draggingElement) !== -1;
return draggedInList || !evt.willInsertAfter
? currentIndex
: currentIndex + 1;
},
onDragMove(evt, originalEvent) {
const { move, realList } = this;
if (!move || !realList) {
return true;
}
const relatedContext = this.getRelatedContextFromMoveEvent(evt);
const futureIndex = this.computeFutureIndex(relatedContext, evt);
const draggedContext = {
...this.context,
futureIndex
};
const sendEvent = {
...evt,
relatedContext,
draggedContext
};
return move(sendEvent, originalEvent);
},
onDragEnd() {
draggingElement = null;
}
}
});
export default draggableComponent;