<template>
  <div :class="$options.name"></div>
</template>
<script>
export default {
  name: "sgDragMove",
  data() {
    return {
      offset: { x: 0, y: 0 }, //偏移量
      style: { top: "0px", left: "0px" },
      canDragDom: null, //触发拖拽事件的物体
      moveDom: null, //可以移动的物体
    };
  },
  props: [
    "data", //可以被拖拽的元素数组(必选)
    /* data格式说明：
        [
            ...
            {
                canDragDom: elementDOM,//可以拖拽的位置元素数组or单个元素
                moveDom: elementDOM,//拖拽同步移动的元素数组or单个元素
            },
            ...
        ]
        */
    "disabled", //屏蔽
    "mousedownNearSide", //按下鼠标吸附边界
    "mousemoveNearSide", //移动鼠标吸附边界
    "mouseupNearSide", //弹起鼠标吸附边界
    "nearPadding", //距离边界多少像素自动吸附（mousedownNearSide||mousemoveNearSide||mouseupNearSide=true的时候才能生效），值可以是number或array类型，例如：是5代表[5,5,5,5],其中数组以此代表[上,右,下,左]
    "stopBoundary", //停靠边界距离，移动物体将按照这个值作为界限不再移出该范围（mousedownNearSide||mousemoveNearSide||mouseupNearSide=true的时候才能生效，值可以是number或array类型，例如：是5代表[5,5,5,5],其中数组以此代表[上,右,下,左]
    "cursor", //鼠标样式
    /*cursor格式说明：{
            grab:'default',//移入可拖拽区域的鼠标样式
            grabbing:'default',//拖拽过程中鼠标样式
        } */
  ],
  watch: {
    data: {
      handler(newValue, oldValue) {
        newValue ? this.__addDragsEvents(newValue) : this.__removeDragsEvents(oldValue);
      },
      deep: true,
      immediate: true,
    },
    disabled: {
      handler(newValue, oldValue) {
        newValue ? this.__removeAllEvents() : this.__addAllEvents();
      },
      deep: true,
      immediate: true,
    },
    style: {
      handler(newValue, oldValue) {
        if (this.moveDom && newValue && Object.keys(newValue).length) {
          let d = newValue;
          Object.keys(d).forEach((k) => (this.moveDom.style[k] = d[k]));
          this.moveDom.style.right = "revert";
          this.moveDom.style.bottom = "revert";
          this.$emit(`getStyle`, d); //用这个方式抛出被移动物体的位置数据（解决多处修改同一DOM属性导致的冲突问题）
        }
      },
      deep: true, //深度监听
      immediate: true, //立即执行
    },
  },
  destroyed() {
    this.__removeAllEvents();
  },
  mounted() {
    this.$parent.$el.style.setProperty(
      "--sgDragMove-grab",
      (this.cursor || {}).grab || "grab"
    ); //js往css传递局部参数
    this.$parent.$el.style.setProperty(
      "--sgDragMove-grabbing",
      (this.cursor || {}).grabbing || "grabbing"
    ); //js往css传递局部参数
  },

  methods: {
    __addAllEvents() {
      this.__addDragsEvents(this.data);
    },
    __removeAllEvents() {
      this.__removeWindowEvents();
      this.__removeDragsEvents(this.data);
    },
    __addWindowEvents() {
      this.__removeWindowEvents();
      addEventListener("mousemove", this.mousemove_window);
      addEventListener("mouseup", this.mouseup_window);
    },
    __removeWindowEvents() {
      removeEventListener("mousemove", this.mousemove_window);
      removeEventListener("mouseup", this.mouseup_window);
    },
    // 初始化需要拖拽的DIV
    __addDragsEvents(doms) {
      (doms || []).forEach((dom) => {
        this.__removeDraggedEvents(dom.canDragDom);
        this.__addDraggedEvents(dom.canDragDom);
      });
    },
    __removeDragsEvents(doms) {
      (doms || []).forEach((dom) => {
        this.__removeDraggedEvents(dom.canDragDom);
      });
    },
    __addDraggedEvents(dom) {
      dom.setAttribute("sgDragMove_grab", "ready");
      dom.addEventListener("dragstart", this.dragstart);
      dom.addEventListener("mousedown", this.mousedown);
    },
    __removeDraggedEvents(dom) {
      dom.removeEventListener("dragstart", this.dragstart);
      dom.removeEventListener("mousedown", this.mousedown);
    },
    dragstart(e) {
      e.stopPropagation();
      e.preventDefault();
      return false;
    },
    mousedown(e) {
      if (this.disabled) return this.mouseup_window(e);
      if (e.button === 2) return this.mouseup_window(e); //点击了鼠标右键
      this.canDragDom = e.currentTarget;
      this.moveDom = this.data.find((v) => v.canDragDom == this.canDragDom).moveDom;
      this.canDragDom.setAttribute("sgDragMove_grab", "down");
      this.moveDom.setAttribute("sgDragMove_move", "ready");
      let or = this.moveDom.getBoundingClientRect();
      this.offset = { x: e.clientX - or.x, y: e.clientY - or.y };
      (this.mousedownNearSide || this.mousedownNearSide === "") && this.nearSide();
      this.$emit("dragStart", this.getResult(e));
      this.__addWindowEvents();
    },
    setOffset(d) {
      this.offset = {
        ...this.offset,
        ...d,
      };
    },
    mousemove_window(e) {
      this.canDragDom.setAttribute("sgDragMove_grab", "down");
      this.moveDom.setAttribute("sgDragMove_move", "ing");
      let x = e.clientX - this.offset.x;
      let y = e.clientY - this.offset.y;
      this.style = { left: x + "px", top: y + "px" };
      this.style["transition-property"] = "left,top";
      this.style["transition-duration"] = "0s,0s";
      this.$nextTick(() => {
        (this.mousemoveNearSide || this.mousemoveNearSide === "") && this.nearSide();
        this.$emit("dragging", this.getResult(e));
      });
    },
    mouseup_window(e) {
      this.$emit("dragEnd", this.getResult(e));
      (this.mouseupNearSide || this.mouseupNearSide === "") && this.nearSide();
      this.offset = null;
      this.style = null;
      this.canDragDom.setAttribute("sgDragMove_grab", "ready");
      this.moveDom.setAttribute("sgDragMove_move", "end");
      setTimeout(() => {
        this.moveDom && this.moveDom.removeAttribute("sgDragMove_move");
      }, 100);
      this.canDragDom = null;
      this.moveDom = null;
      this.__removeWindowEvents();
    },
    // 自动吸附网页边界
    nearSide() {
      let arr = this.nearPadding ? JSON.parse(JSON.stringify(this.nearPadding)) : 0;
      Array.isArray(arr) || (arr = [...Array(4)].map((v) => arr));
      let [dis_top, dis_right, dis_bottom, dis_left] = arr;
      arr = this.stopBoundary ? JSON.parse(JSON.stringify(this.stopBoundary)) : 0;
      Array.isArray(arr) || (arr = [...Array(4)].map((v) => arr));
      let [
        stopBoundary_top,
        stopBoundary_right,
        stopBoundary_bottom,
        stopBoundary_left,
      ] = arr;

      let x = parseFloat(this.moveDom.style.left);
      let y = parseFloat(this.moveDom.style.top);
      let rect = this.moveDom.getBoundingClientRect();

      let min_side_x = 0,
        min_x = min_side_x + dis_left,
        min_left = min_side_x + stopBoundary_left;
      let min_side_y = 0,
        min_y = min_side_y + dis_top,
        min_top = min_side_y + stopBoundary_top;
      x < min_x && (this.moveDom.style.left = `${min_left}px`);
      y < min_y && (this.moveDom.style.top = `${min_top}px`);

      let max_side_x = innerWidth - rect.width,
        max_x = max_side_x - dis_right,
        max_right = max_side_x - stopBoundary_right;
      let max_side_y = innerHeight - rect.height,
        max_y = max_side_y - dis_bottom,
        max_bottom = max_side_y - stopBoundary_bottom;
      x > max_x && (this.moveDom.style.left = `${max_right}px`);
      y > max_y && (this.moveDom.style.top = `${max_bottom}px`);
    },
    getResult(e) {
      return {
        $event: e,
        canDragDom: this.canDragDom,
        moveDom: this.moveDom,
        canDragDomRect: this.canDragDom ? this.canDragDom.getBoundingClientRect() : null,
        moveDomRect: this.moveDom ? this.moveDom.getBoundingClientRect() : null,
      };
    },
  },
};
</script>
<style lang="scss" scoped>
[sgDragMove_grab="ready"] {
  cursor: var(--sgDragMove-grab); //css获取js传递的参数

  * {
    cursor: var(--sgDragMove-grab); //css获取js传递的参数
  }

  &:hover {
    opacity: 1;
  }

  &:active {
    opacity: 0.9;
  }
}

[sgDragMove_grab="down"] {
  cursor: var(--sgDragMove-grabbing); //css获取js传递的参数

  * {
    cursor: var(--sgDragMove-grabbing); //css获取js传递的参数
  }
}

[sgDragMove_move="ready"] {
  opacity: 1;
}

[sgDragMove_move="ing"] {
  opacity: 0.9;
}

[sgDragMove_move="end"] {
  transition: 0.1s;
}
</style>
