<!--
 * @description: Vue3拖拽组件，todo: 后续做成组件
 * @Date: 2021-10-31 16:57:40
 * @LastEditTime: 2021-11-05 20:06:08
 * @Author: xingheng
-->
<template>
  <div
    :draggable="true"
    :style="style"
    class="drag-item"
    ref="divRef"
    @mousedown.prevent="mousedown"
  >
    <div
      v-show="active"
      v-for="(handle, index) in handles"
      :key="index"
      :class="[classNameHandle, classNameHandle + '-' + handle]"
      @mousedown.stop.prevent="handleDown(handle, $event)"
    >
      <slot :name="handle"></slot>
    </div>
    <slot></slot>
  </div>
</template>

<script lang="ts">
import {
  computed,
  defineComponent,
  onMounted,
  onUnmounted,
  PropType,
  reactive,
  ref,
  StyleValue,
  toRefs,
  watchEffect,
} from "vue";
export default defineComponent({
  name: "VueDraggale",
  props: {
    x: {
      type: Number,
      default: 0,
    },
    y: {
      type: Number,
      default: 0,
    },
    w: {
      type: Number,
      default: 0,
    },
    h: {
      type: Number,
      default: 0,
    },
    // z-index
    z: {
      type: Number,
      default: 1,
    },
    handles: {
      type: Array as PropType<string[]>,
      default: () => ["tl", "tm", "tr", "mr", "br", "bm", "bl", "ml"],
    },
    classNameHandle: {
      type: String,
      default: "handle",
    },
    active: {
      type: Boolean,
      default: false,
    },
  },
  setup(props, ctx) {
    const data = reactive({
      x: props.x,
      y: props.y,
      w: props.w,
      h: props.h,

      offsetX: 0, // 鼠标点距离点击div的左边框距离
      offsetY: 0, // 鼠标点距离点击div的上边框距离

      active: props.active, // 是否触发
      isDrop: false, // 判断鼠标按下才能移动

      time: 0, // 节流时间
    });

    const divRef = ref();

    onMounted(() => {
      document.addEventListener("mousedown", documentMouseDown);
      document.addEventListener("mousemove", mousemove);
      document.addEventListener("mouseup", mouseup);
    });

    onUnmounted(() => {
      document.removeEventListener("mousedown", documentMouseDown);
      document.removeEventListener("mousemove", mousemove);
      document.removeEventListener("mouseup", mouseup);
    });
    /**
     * @description: 属性值更新
     */
    watchEffect(() => {
      ctx.emit("update:x", data.x);
      ctx.emit("update:y", data.y);
      ctx.emit("update:w", data.w);
      ctx.emit("update:h", data.h);
    });
    /**
     * @description: 计算div的样式
     */
    const style = computed((): StyleValue | undefined => {
      return {
        position: "absolute",
        left: `${data.x}px`,
        top: `${data.y}px`,
        // transform: `translate(${data.x}px, ${data.y}px)`,
        width: `${data.w}px`,
        height: `${data.h}px`,
        zIndex: props.z,
        border: data.active ? "1px dashed #000" : "",
      };
    });

    const mousedown = (e: MouseEvent) => {
      data.isDrop = true;
      data.offsetX = e.offsetX;
      data.offsetY = e.offsetY;
    };

    const mousemove = (e: MouseEvent) => {
      e.preventDefault();
      if (!data.isDrop) return;

      // 节流
      if (data.time && Date.now() - data.time < 15) return;
      data.time = Date.now();

      data.x = e.clientX - data.offsetX;
      data.y = e.clientY - data.offsetY;
    };

    const mouseup = () => {
      data.isDrop = false;
      console.log("mouseup");
      document.onmousemove = null;
      document.onmousemove = null;
    };

    const documentMouseDown = (e: MouseEvent) => {
      const target = e.target || e.srcElement;
      // ps: Node.contains() 返回的是一个布尔值，来表示传入的节点是否为该节点的后代节点。
      if (divRef.value?.contains(target)) {
        data.active = true;
      } else {
        data.active = false;
      }
    };
    /**
     * @description: 拖拽点时按下事件
     */
    const handleDown = (handle: string, e: MouseEvent) => {
      const diffX = e.offsetX;
      const diffY = e.offsetY;

      // 右下角
      document.onmousemove = (e: MouseEvent) => {
        // 当前移动点的位置
        const x = e.clientX - diffX;
        const y = e.clientY - diffY;

        computedPositionAndSize(handle, x, y);
      };
      // 移动结束
      document.onmouseup = () => {
        ctx.emit("resizing", data.w, data.h);
      };
    };
    /**
     * @description: 拖拽八个改变div大小的点时，计算div的位置和大小
     */
    const computedPositionAndSize = (handle: string, x: number, y: number) => {
      // 下右
      if (handle === "br") {
        data.w = Math.abs(x - data.x);
        data.h = Math.abs(y - data.y);
      }

      // 下中
      if (handle === "bm") {
        data.h = Math.abs(y - data.y);
      }

      // 下左
      if (handle === "bl") {
        data.w = data.x - x + data.w;
        data.h = y - (data.y + data.h) + data.h;
        data.x = x;
      }

      // 右中
      if (handle === "mr") {
        data.w = x - data.x;
      }

      // 左中
      if (handle === "ml") {
        data.w = data.x - x + data.w;
        data.x = x;
      }

      // 上右
      if (handle === "tr") {
        data.w = x - data.x;
        data.h = data.y - y + data.h;
        data.y = y;
      }

      // 上中
      if (handle === "tm") {
        data.h = data.y - y + data.h;
        data.y = y;
      }

      // 上左
      if (handle === "tl") {
        data.w = data.x - x + data.w;
        data.h = data.y - y + data.h;
        data.y = y;
        data.x = x;
      }
    };

    return {
      mousedown,
      mouseup,
      mousemove,
      handleDown,

      style,
      divRef,
      ...toRefs(data),
    };
  },
});
</script>

<style scoped>
.drag-item {
  display: inline-block;
  border: 5px solid #fff;
}

.vdr {
  touch-action: none;
  position: absolute;
  box-sizing: border-box;
  border: 1px dashed black;
}
.handle {
  box-sizing: border-box;
  position: absolute;
  width: 10px;
  height: 10px;
  background: #eee;
  border: 1px solid #333;
}
.handle-tl {
  top: -10px;
  left: -10px;
  cursor: nw-resize;
}
.handle-tm {
  top: -10px;
  left: 50%;
  margin-left: -5px;
  cursor: n-resize;
}
.handle-tr {
  top: -10px;
  right: -10px;
  cursor: ne-resize;
}
.handle-ml {
  top: 50%;
  margin-top: -5px;
  left: -10px;
  cursor: w-resize;
}
.handle-mr {
  top: 50%;
  margin-top: -5px;
  right: -10px;
  cursor: e-resize;
}
.handle-bl {
  bottom: -10px;
  left: -10px;
  cursor: sw-resize;
}
.handle-bm {
  bottom: -10px;
  left: 50%;
  margin-left: -5px;
  cursor: s-resize;
}
.handle-br {
  bottom: -10px;
  right: -10px;
  cursor: se-resize;
}
</style>
