@havue/drag-and-drop 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # DragAndDrop
2
+
3
+ [documents](https://happypedestrian.github.io/havue/components/drag-and-drop.html)
@@ -0,0 +1,476 @@
1
+ import './style.css';
2
+ var __defProp = Object.defineProperty;
3
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
+ import { defineComponent, ref, reactive, computed, createElementBlock, openBlock, normalizeClass, renderSlot, createCommentVNode, normalizeStyle } from "vue";
6
+ import { EventBus, isMobile } from "@havue/shared";
7
+ const withInstall = (main, extra) => {
8
+ main.install = (app) => {
9
+ for (const comp of [main, ...Object.values({})]) {
10
+ app.component(comp.name, comp);
11
+ }
12
+ };
13
+ return main;
14
+ };
15
+ class DnDManager extends EventBus {
16
+ constructor() {
17
+ super();
18
+ /** 是否开始拖动 */
19
+ __publicField(this, "isDragStart", false);
20
+ /** 拖动元素类型 */
21
+ __publicField(this, "dragType");
22
+ /** Draggable传递的数据,
23
+ * 供Droppable使用
24
+ */
25
+ __publicField(this, "dragData");
26
+ __publicField(this, "isSendFirstMovePos", false);
27
+ /** 移动端长按一定时间才触发 onStart */
28
+ __publicField(this, "touchStartPressTime", 300);
29
+ /** touchstart事件触发的时的时间 */
30
+ __publicField(this, "touchStartTime", 0);
31
+ /** touchstart时间触发的位置 */
32
+ __publicField(this, "touchStartPosition", { x: 0, y: 0 });
33
+ /** onMove最后位置 */
34
+ __publicField(this, "lastMovePoint", { x: 0, y: 0 });
35
+ __publicField(this, "emitTouchStartTimer");
36
+ __publicField(this, "destroy", () => {
37
+ });
38
+ this.bindEventListener();
39
+ }
40
+ updateDargInfo(type, data) {
41
+ if (type === void 0 || type === null) {
42
+ throw new Error("请传入拖动元素 type");
43
+ }
44
+ this.isDragStart = true;
45
+ this.dragType = type;
46
+ this.dragData = data;
47
+ this.emitTouchStartTimer && clearTimeout(this.emitTouchStartTimer);
48
+ this.emitTouchStartTimer = void 0;
49
+ }
50
+ /**
51
+ * 通知 Draggable 开始点击
52
+ * @param point
53
+ */
54
+ onDown(point) {
55
+ this.emit("down", point);
56
+ }
57
+ /**
58
+ * 通知 Draggable 拖拽开始
59
+ * @param point
60
+ */
61
+ onFirstMove(point, e) {
62
+ if (this.isDragStart) {
63
+ return;
64
+ }
65
+ this.isSendFirstMovePos = true;
66
+ this.emit("first-move", point, e);
67
+ }
68
+ /**
69
+ * 通知 Draggable 拖拽开始
70
+ * @param point
71
+ */
72
+ onStart(point) {
73
+ if (this.isDragStart) {
74
+ return;
75
+ }
76
+ this.emit("start", point);
77
+ }
78
+ /**
79
+ * 通知 Draggable 移动
80
+ * @param point
81
+ * @returns
82
+ */
83
+ onMove(point) {
84
+ if (!this.isDragStart || !this.dragType) {
85
+ return;
86
+ }
87
+ this.lastMovePoint = point;
88
+ this.emit("move", {
89
+ type: this.dragType,
90
+ data: this.dragData,
91
+ point
92
+ });
93
+ }
94
+ /**
95
+ * 结束拖拽
96
+ * @returns
97
+ */
98
+ onEnd() {
99
+ this.emitTouchStartTimer && clearTimeout(this.emitTouchStartTimer);
100
+ this.emitTouchStartTimer = void 0;
101
+ this.isSendFirstMovePos = false;
102
+ if (!this.isDragStart || !this.dragType) {
103
+ return;
104
+ }
105
+ this.isDragStart = false;
106
+ this.emit("end", {
107
+ point: this.lastMovePoint,
108
+ type: this.dragType,
109
+ data: this.dragData
110
+ });
111
+ }
112
+ onMouseDown(e) {
113
+ if (e.buttons !== 1) {
114
+ this.onEnd();
115
+ return;
116
+ }
117
+ const { clientX, clientY } = e;
118
+ const position = {
119
+ x: clientX,
120
+ y: clientY
121
+ };
122
+ this.touchStartTime = Date.now();
123
+ this.touchStartPosition = position;
124
+ this.emitTouchStartTimer && clearTimeout(this.emitTouchStartTimer);
125
+ this.onDown(position);
126
+ this.emitTouchStartTimer = window.setTimeout(() => {
127
+ this.onStart(position);
128
+ }, this.touchStartPressTime);
129
+ }
130
+ onMouseMove(e) {
131
+ if (e.buttons !== 1) {
132
+ this.onEnd();
133
+ return;
134
+ }
135
+ const { clientX, clientY } = e;
136
+ if (this.isDragStart) {
137
+ e.preventDefault();
138
+ e.stopPropagation();
139
+ this.onMove({
140
+ x: clientX,
141
+ y: clientY
142
+ });
143
+ } else {
144
+ if (!this.isSendFirstMovePos) {
145
+ this.onFirstMove(
146
+ {
147
+ x: clientX,
148
+ y: clientY
149
+ },
150
+ e
151
+ );
152
+ }
153
+ const { x, y } = this.touchStartPosition;
154
+ const timeInLimit = Date.now() - this.touchStartTime < this.touchStartPressTime;
155
+ if (timeInLimit && (Math.abs(x - clientX) > 30 || Math.abs(y - clientY) > 30)) {
156
+ clearTimeout(this.emitTouchStartTimer);
157
+ this.emitTouchStartTimer = void 0;
158
+ }
159
+ }
160
+ }
161
+ onMouseUp() {
162
+ this.onEnd();
163
+ }
164
+ onTouchStart(e) {
165
+ if (e.touches.length > 1) {
166
+ return;
167
+ }
168
+ const { clientX, clientY } = e.touches[0];
169
+ const position = {
170
+ x: clientX,
171
+ y: clientY
172
+ };
173
+ this.touchStartTime = Date.now();
174
+ this.touchStartPosition = position;
175
+ this.onDown(position);
176
+ this.emitTouchStartTimer = window.setTimeout(() => {
177
+ this.onStart(position);
178
+ }, this.touchStartPressTime);
179
+ }
180
+ onTouchMove(e) {
181
+ if (e.touches.length > 1) {
182
+ return;
183
+ }
184
+ const { clientX, clientY } = e.touches[0];
185
+ if (this.isDragStart) {
186
+ e.preventDefault();
187
+ e.stopPropagation();
188
+ this.onMove({
189
+ x: clientX,
190
+ y: clientY
191
+ });
192
+ } else {
193
+ if (!this.isSendFirstMovePos) {
194
+ this.onFirstMove(
195
+ {
196
+ x: clientX,
197
+ y: clientY
198
+ },
199
+ e
200
+ );
201
+ }
202
+ const { x, y } = this.touchStartPosition;
203
+ const timeInLimit = Date.now() - this.touchStartTime < this.touchStartPressTime;
204
+ if (timeInLimit && (Math.abs(x - clientX) > 30 || Math.abs(y - clientY) > 30)) {
205
+ clearTimeout(this.emitTouchStartTimer);
206
+ }
207
+ }
208
+ }
209
+ onTouchEnd() {
210
+ this.onEnd();
211
+ }
212
+ bindEventListener() {
213
+ if (document.body) {
214
+ const onTouchStart = this.onTouchStart.bind(this);
215
+ const onTouchMove = this.onTouchMove.bind(this);
216
+ const onTouchEnd = this.onTouchEnd.bind(this);
217
+ const onMouseDown = this.onMouseDown.bind(this);
218
+ const onMouseMove = this.onMouseMove.bind(this);
219
+ const onMouseUp = this.onMouseUp.bind(this);
220
+ if (isMobile) {
221
+ document.body.addEventListener("touchstart", onTouchStart, { passive: false });
222
+ document.body.addEventListener("touchmove", onTouchMove, { passive: false });
223
+ document.body.addEventListener("touchend", onTouchEnd, { passive: false });
224
+ } else {
225
+ document.body.addEventListener("mousedown", onMouseDown);
226
+ document.body.addEventListener("mousemove", onMouseMove);
227
+ document.body.addEventListener("mouseup", onMouseUp);
228
+ }
229
+ this.destroy = () => {
230
+ document.body.removeEventListener("touchstart", onTouchStart);
231
+ document.body.removeEventListener("touchmove", onTouchMove);
232
+ document.body.removeEventListener("touchend", onTouchEnd);
233
+ document.body.removeEventListener("mousedown", onMouseDown);
234
+ document.body.removeEventListener("mousemove", onMouseMove);
235
+ document.body.removeEventListener("mouseup", onMouseUp);
236
+ };
237
+ } else {
238
+ document.addEventListener("DOMContentLoaded", this.bindEventListener);
239
+ }
240
+ }
241
+ removeEventListener() {
242
+ this.destroy();
243
+ }
244
+ destroyed() {
245
+ this.isDragStart = false;
246
+ this.dragType = void 0;
247
+ this.dragData = void 0;
248
+ this.removeEventListener();
249
+ }
250
+ }
251
+ const DnDManagerInstance = new DnDManager();
252
+ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
253
+ ...{
254
+ name: "HvDraggable"
255
+ },
256
+ __name: "Draggable",
257
+ props: {
258
+ type: {},
259
+ immediate: {},
260
+ disabled: { type: Boolean, default: false },
261
+ data: {}
262
+ },
263
+ setup(__props) {
264
+ const ImmediateEnumType = {
265
+ LEFT: "left",
266
+ RIGHT: "right",
267
+ TOP: "top",
268
+ BOTTOM: "bottom",
269
+ ALL: "all"
270
+ };
271
+ const props = __props;
272
+ const dragItemRef = ref();
273
+ const isDownThis = ref(false);
274
+ const downPosition = reactive({
275
+ x: 0,
276
+ y: 0
277
+ });
278
+ const isDragThis = ref(false);
279
+ const cloneNodePosition = reactive({
280
+ x: 0,
281
+ y: 0
282
+ });
283
+ const immediateDirections = computed(() => {
284
+ if (Array.isArray(props.immediate)) {
285
+ return props.immediate;
286
+ }
287
+ return props.immediate ? [props.immediate] : [];
288
+ });
289
+ const cloneNodeStyle = computed(() => {
290
+ return {
291
+ transform: `translate(${cloneNodePosition.x}px, ${cloneNodePosition.y}px) translate(-50%, -50%)`
292
+ };
293
+ });
294
+ DnDManagerInstance.on("down", (params) => {
295
+ const { x, y } = params;
296
+ const startEl = document.elementFromPoint(x, y);
297
+ if (!props.disabled && dragItemRef.value && dragItemRef.value.contains(startEl)) {
298
+ if (immediateDirections.value.includes(ImmediateEnumType.ALL)) {
299
+ handleStart(params);
300
+ return;
301
+ }
302
+ isDownThis.value = true;
303
+ downPosition.x = x;
304
+ downPosition.y = y;
305
+ }
306
+ });
307
+ DnDManagerInstance.on("first-move", (params, event) => {
308
+ const { x, y } = params;
309
+ const directions = immediateDirections.value.filter((item) => item !== ImmediateEnumType.ALL);
310
+ if (isDownThis.value && !props.disabled && directions.length) {
311
+ const distanceH = x - downPosition.x;
312
+ const distanceHAbs = Math.abs(distanceH);
313
+ const distanceV = y - downPosition.y;
314
+ const distanceVAbs = Math.abs(distanceV);
315
+ const max = Math.max(distanceHAbs, distanceVAbs);
316
+ const isMaxH = distanceHAbs === max;
317
+ const isMaxV = distanceVAbs === max;
318
+ let isImmediate = false;
319
+ if (isMaxH) {
320
+ isImmediate = directions.includes(ImmediateEnumType.LEFT) && distanceH < 0 || directions.includes(ImmediateEnumType.RIGHT) && distanceH > 0;
321
+ }
322
+ if (isMaxV && !isImmediate) {
323
+ isImmediate = directions.includes(ImmediateEnumType.TOP) && distanceV < 0 || directions.includes(ImmediateEnumType.BOTTOM) && distanceV > 0;
324
+ }
325
+ if (isImmediate) {
326
+ event.preventDefault();
327
+ event.stopPropagation();
328
+ handleStart(downPosition);
329
+ handleMove(params);
330
+ return;
331
+ }
332
+ }
333
+ });
334
+ DnDManagerInstance.on("start", (params) => {
335
+ const { x, y } = params;
336
+ const startEl = document.elementFromPoint(x, y);
337
+ if (!props.disabled && dragItemRef.value && dragItemRef.value.contains(startEl)) {
338
+ handleStart(params);
339
+ }
340
+ });
341
+ DnDManagerInstance.on("move", (params) => {
342
+ const { point } = params;
343
+ if (isDragThis.value) {
344
+ handleMove(point);
345
+ }
346
+ });
347
+ DnDManagerInstance.on("end", () => {
348
+ isDownThis.value = false;
349
+ isDragThis.value = false;
350
+ Object.assign(downPosition, {
351
+ x: 0,
352
+ y: 0
353
+ });
354
+ Object.assign(cloneNodePosition, {
355
+ x: 0,
356
+ y: 0
357
+ });
358
+ });
359
+ function handleStart(point) {
360
+ isDragThis.value = true;
361
+ cloneNodePosition.x = point.x;
362
+ cloneNodePosition.y = point.y;
363
+ DnDManagerInstance.updateDargInfo(props.type, props.data);
364
+ }
365
+ function handleMove(point) {
366
+ cloneNodePosition.x = point.x;
367
+ cloneNodePosition.y = point.y;
368
+ }
369
+ return (_ctx, _cache) => {
370
+ return openBlock(), createElementBlock("div", {
371
+ ref_key: "dragItemRef",
372
+ ref: dragItemRef,
373
+ class: normalizeClass(["draggable-area", _ctx.disabled ? "disabled" : ""])
374
+ }, [
375
+ renderSlot(_ctx.$slots, "default", {}, void 0, true),
376
+ isDragThis.value ? (openBlock(), createElementBlock("div", {
377
+ key: 0,
378
+ class: "draggable-clone-item",
379
+ style: normalizeStyle(cloneNodeStyle.value)
380
+ }, [
381
+ renderSlot(_ctx.$slots, "drag-item", {}, () => [
382
+ renderSlot(_ctx.$slots, "default", {}, void 0, true)
383
+ ], true)
384
+ ], 4)) : createCommentVNode("", true)
385
+ ], 2);
386
+ };
387
+ }
388
+ });
389
+ const _export_sfc = (sfc, props) => {
390
+ const target = sfc.__vccOpts || sfc;
391
+ for (const [key, val] of props) {
392
+ target[key] = val;
393
+ }
394
+ return target;
395
+ };
396
+ const Draggable = /* @__PURE__ */ _export_sfc(_sfc_main$1, [["__scopeId", "data-v-34f8fce1"]]);
397
+ const _sfc_main = /* @__PURE__ */ defineComponent({
398
+ ...{
399
+ name: "HvDroppable"
400
+ },
401
+ __name: "Droppable",
402
+ props: {
403
+ acceptDragType: {}
404
+ },
405
+ emits: ["enter", "move", "drop", "leave"],
406
+ setup(__props, { emit: __emit }) {
407
+ const emits = __emit;
408
+ const props = __props;
409
+ const dropAreaRef = ref();
410
+ const isEntered = ref(false);
411
+ const acceptDragTypeList = computed(() => {
412
+ return Array.isArray(props.acceptDragType) ? props.acceptDragType : [props.acceptDragType];
413
+ });
414
+ function getPositionInArea(point) {
415
+ if (!dropAreaRef.value) {
416
+ return {
417
+ isInArea: false,
418
+ position: {
419
+ x: 0,
420
+ y: 0
421
+ }
422
+ };
423
+ }
424
+ const { x: pointX, y: pointY } = point;
425
+ const { x, y, width, height } = dropAreaRef.value.getBoundingClientRect();
426
+ const posX = pointX - x;
427
+ const posY = pointY - y;
428
+ return {
429
+ isInArea: posX >= 0 && posX <= width && posY >= 0 && posY <= height,
430
+ position: {
431
+ x: posX,
432
+ y: posY
433
+ }
434
+ };
435
+ }
436
+ DnDManagerInstance.on("move", (params) => {
437
+ const { type, data, point } = params;
438
+ if (dropAreaRef.value && acceptDragTypeList.value.includes(type)) {
439
+ const { isInArea, position } = getPositionInArea(point);
440
+ if (isInArea) {
441
+ if (isEntered.value) {
442
+ emits("move", type, position, data);
443
+ } else {
444
+ isEntered.value = true;
445
+ emits("enter", type, position, data);
446
+ }
447
+ } else if (isEntered.value) {
448
+ isEntered.value = false;
449
+ emits("leave", type, data);
450
+ }
451
+ }
452
+ });
453
+ DnDManagerInstance.on("end", ({ type, point, data }) => {
454
+ if (dropAreaRef.value && acceptDragTypeList.value.includes(type) && isEntered.value) {
455
+ const { position } = getPositionInArea(point);
456
+ emits("drop", type, position, data);
457
+ }
458
+ });
459
+ return (_ctx, _cache) => {
460
+ return openBlock(), createElementBlock("div", {
461
+ ref_key: "dropAreaRef",
462
+ ref: dropAreaRef,
463
+ class: "droppable-area"
464
+ }, [
465
+ renderSlot(_ctx.$slots, "default", {}, void 0, true)
466
+ ], 512);
467
+ };
468
+ }
469
+ });
470
+ const Droppable = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-28d651f3"]]);
471
+ const HvDraggable = withInstall(Draggable);
472
+ const HvDroppable = withInstall(Droppable);
473
+ export {
474
+ HvDraggable,
475
+ HvDroppable
476
+ };
@@ -0,0 +1,477 @@
1
+ (function(global, factory) {
2
+ typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("vue"), require("@havue/shared")) : typeof define === "function" && define.amd ? define(["exports", "vue", "@havue/shared"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global["drag-and-drop"] = {}, global.Vue, global.shared));
3
+ })(this, function(exports2, vue, shared) {
4
+ "use strict";var __defProp = Object.defineProperty;
5
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
6
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
7
+
8
+ const withInstall = (main, extra) => {
9
+ main.install = (app) => {
10
+ for (const comp of [main, ...Object.values({})]) {
11
+ app.component(comp.name, comp);
12
+ }
13
+ };
14
+ return main;
15
+ };
16
+ class DnDManager extends shared.EventBus {
17
+ constructor() {
18
+ super();
19
+ /** 是否开始拖动 */
20
+ __publicField(this, "isDragStart", false);
21
+ /** 拖动元素类型 */
22
+ __publicField(this, "dragType");
23
+ /** Draggable传递的数据,
24
+ * 供Droppable使用
25
+ */
26
+ __publicField(this, "dragData");
27
+ __publicField(this, "isSendFirstMovePos", false);
28
+ /** 移动端长按一定时间才触发 onStart */
29
+ __publicField(this, "touchStartPressTime", 300);
30
+ /** touchstart事件触发的时的时间 */
31
+ __publicField(this, "touchStartTime", 0);
32
+ /** touchstart时间触发的位置 */
33
+ __publicField(this, "touchStartPosition", { x: 0, y: 0 });
34
+ /** onMove最后位置 */
35
+ __publicField(this, "lastMovePoint", { x: 0, y: 0 });
36
+ __publicField(this, "emitTouchStartTimer");
37
+ __publicField(this, "destroy", () => {
38
+ });
39
+ this.bindEventListener();
40
+ }
41
+ updateDargInfo(type, data) {
42
+ if (type === void 0 || type === null) {
43
+ throw new Error("请传入拖动元素 type");
44
+ }
45
+ this.isDragStart = true;
46
+ this.dragType = type;
47
+ this.dragData = data;
48
+ this.emitTouchStartTimer && clearTimeout(this.emitTouchStartTimer);
49
+ this.emitTouchStartTimer = void 0;
50
+ }
51
+ /**
52
+ * 通知 Draggable 开始点击
53
+ * @param point
54
+ */
55
+ onDown(point) {
56
+ this.emit("down", point);
57
+ }
58
+ /**
59
+ * 通知 Draggable 拖拽开始
60
+ * @param point
61
+ */
62
+ onFirstMove(point, e) {
63
+ if (this.isDragStart) {
64
+ return;
65
+ }
66
+ this.isSendFirstMovePos = true;
67
+ this.emit("first-move", point, e);
68
+ }
69
+ /**
70
+ * 通知 Draggable 拖拽开始
71
+ * @param point
72
+ */
73
+ onStart(point) {
74
+ if (this.isDragStart) {
75
+ return;
76
+ }
77
+ this.emit("start", point);
78
+ }
79
+ /**
80
+ * 通知 Draggable 移动
81
+ * @param point
82
+ * @returns
83
+ */
84
+ onMove(point) {
85
+ if (!this.isDragStart || !this.dragType) {
86
+ return;
87
+ }
88
+ this.lastMovePoint = point;
89
+ this.emit("move", {
90
+ type: this.dragType,
91
+ data: this.dragData,
92
+ point
93
+ });
94
+ }
95
+ /**
96
+ * 结束拖拽
97
+ * @returns
98
+ */
99
+ onEnd() {
100
+ this.emitTouchStartTimer && clearTimeout(this.emitTouchStartTimer);
101
+ this.emitTouchStartTimer = void 0;
102
+ this.isSendFirstMovePos = false;
103
+ if (!this.isDragStart || !this.dragType) {
104
+ return;
105
+ }
106
+ this.isDragStart = false;
107
+ this.emit("end", {
108
+ point: this.lastMovePoint,
109
+ type: this.dragType,
110
+ data: this.dragData
111
+ });
112
+ }
113
+ onMouseDown(e) {
114
+ if (e.buttons !== 1) {
115
+ this.onEnd();
116
+ return;
117
+ }
118
+ const { clientX, clientY } = e;
119
+ const position = {
120
+ x: clientX,
121
+ y: clientY
122
+ };
123
+ this.touchStartTime = Date.now();
124
+ this.touchStartPosition = position;
125
+ this.emitTouchStartTimer && clearTimeout(this.emitTouchStartTimer);
126
+ this.onDown(position);
127
+ this.emitTouchStartTimer = window.setTimeout(() => {
128
+ this.onStart(position);
129
+ }, this.touchStartPressTime);
130
+ }
131
+ onMouseMove(e) {
132
+ if (e.buttons !== 1) {
133
+ this.onEnd();
134
+ return;
135
+ }
136
+ const { clientX, clientY } = e;
137
+ if (this.isDragStart) {
138
+ e.preventDefault();
139
+ e.stopPropagation();
140
+ this.onMove({
141
+ x: clientX,
142
+ y: clientY
143
+ });
144
+ } else {
145
+ if (!this.isSendFirstMovePos) {
146
+ this.onFirstMove(
147
+ {
148
+ x: clientX,
149
+ y: clientY
150
+ },
151
+ e
152
+ );
153
+ }
154
+ const { x, y } = this.touchStartPosition;
155
+ const timeInLimit = Date.now() - this.touchStartTime < this.touchStartPressTime;
156
+ if (timeInLimit && (Math.abs(x - clientX) > 30 || Math.abs(y - clientY) > 30)) {
157
+ clearTimeout(this.emitTouchStartTimer);
158
+ this.emitTouchStartTimer = void 0;
159
+ }
160
+ }
161
+ }
162
+ onMouseUp() {
163
+ this.onEnd();
164
+ }
165
+ onTouchStart(e) {
166
+ if (e.touches.length > 1) {
167
+ return;
168
+ }
169
+ const { clientX, clientY } = e.touches[0];
170
+ const position = {
171
+ x: clientX,
172
+ y: clientY
173
+ };
174
+ this.touchStartTime = Date.now();
175
+ this.touchStartPosition = position;
176
+ this.onDown(position);
177
+ this.emitTouchStartTimer = window.setTimeout(() => {
178
+ this.onStart(position);
179
+ }, this.touchStartPressTime);
180
+ }
181
+ onTouchMove(e) {
182
+ if (e.touches.length > 1) {
183
+ return;
184
+ }
185
+ const { clientX, clientY } = e.touches[0];
186
+ if (this.isDragStart) {
187
+ e.preventDefault();
188
+ e.stopPropagation();
189
+ this.onMove({
190
+ x: clientX,
191
+ y: clientY
192
+ });
193
+ } else {
194
+ if (!this.isSendFirstMovePos) {
195
+ this.onFirstMove(
196
+ {
197
+ x: clientX,
198
+ y: clientY
199
+ },
200
+ e
201
+ );
202
+ }
203
+ const { x, y } = this.touchStartPosition;
204
+ const timeInLimit = Date.now() - this.touchStartTime < this.touchStartPressTime;
205
+ if (timeInLimit && (Math.abs(x - clientX) > 30 || Math.abs(y - clientY) > 30)) {
206
+ clearTimeout(this.emitTouchStartTimer);
207
+ }
208
+ }
209
+ }
210
+ onTouchEnd() {
211
+ this.onEnd();
212
+ }
213
+ bindEventListener() {
214
+ if (document.body) {
215
+ const onTouchStart = this.onTouchStart.bind(this);
216
+ const onTouchMove = this.onTouchMove.bind(this);
217
+ const onTouchEnd = this.onTouchEnd.bind(this);
218
+ const onMouseDown = this.onMouseDown.bind(this);
219
+ const onMouseMove = this.onMouseMove.bind(this);
220
+ const onMouseUp = this.onMouseUp.bind(this);
221
+ if (shared.isMobile) {
222
+ document.body.addEventListener("touchstart", onTouchStart, { passive: false });
223
+ document.body.addEventListener("touchmove", onTouchMove, { passive: false });
224
+ document.body.addEventListener("touchend", onTouchEnd, { passive: false });
225
+ } else {
226
+ document.body.addEventListener("mousedown", onMouseDown);
227
+ document.body.addEventListener("mousemove", onMouseMove);
228
+ document.body.addEventListener("mouseup", onMouseUp);
229
+ }
230
+ this.destroy = () => {
231
+ document.body.removeEventListener("touchstart", onTouchStart);
232
+ document.body.removeEventListener("touchmove", onTouchMove);
233
+ document.body.removeEventListener("touchend", onTouchEnd);
234
+ document.body.removeEventListener("mousedown", onMouseDown);
235
+ document.body.removeEventListener("mousemove", onMouseMove);
236
+ document.body.removeEventListener("mouseup", onMouseUp);
237
+ };
238
+ } else {
239
+ document.addEventListener("DOMContentLoaded", this.bindEventListener);
240
+ }
241
+ }
242
+ removeEventListener() {
243
+ this.destroy();
244
+ }
245
+ destroyed() {
246
+ this.isDragStart = false;
247
+ this.dragType = void 0;
248
+ this.dragData = void 0;
249
+ this.removeEventListener();
250
+ }
251
+ }
252
+ const DnDManagerInstance = new DnDManager();
253
+ const _sfc_main$1 = /* @__PURE__ */ vue.defineComponent({
254
+ ...{
255
+ name: "HvDraggable"
256
+ },
257
+ __name: "Draggable",
258
+ props: {
259
+ type: {},
260
+ immediate: {},
261
+ disabled: { type: Boolean, default: false },
262
+ data: {}
263
+ },
264
+ setup(__props) {
265
+ const ImmediateEnumType = {
266
+ LEFT: "left",
267
+ RIGHT: "right",
268
+ TOP: "top",
269
+ BOTTOM: "bottom",
270
+ ALL: "all"
271
+ };
272
+ const props = __props;
273
+ const dragItemRef = vue.ref();
274
+ const isDownThis = vue.ref(false);
275
+ const downPosition = vue.reactive({
276
+ x: 0,
277
+ y: 0
278
+ });
279
+ const isDragThis = vue.ref(false);
280
+ const cloneNodePosition = vue.reactive({
281
+ x: 0,
282
+ y: 0
283
+ });
284
+ const immediateDirections = vue.computed(() => {
285
+ if (Array.isArray(props.immediate)) {
286
+ return props.immediate;
287
+ }
288
+ return props.immediate ? [props.immediate] : [];
289
+ });
290
+ const cloneNodeStyle = vue.computed(() => {
291
+ return {
292
+ transform: `translate(${cloneNodePosition.x}px, ${cloneNodePosition.y}px) translate(-50%, -50%)`
293
+ };
294
+ });
295
+ DnDManagerInstance.on("down", (params) => {
296
+ const { x, y } = params;
297
+ const startEl = document.elementFromPoint(x, y);
298
+ if (!props.disabled && dragItemRef.value && dragItemRef.value.contains(startEl)) {
299
+ if (immediateDirections.value.includes(ImmediateEnumType.ALL)) {
300
+ handleStart(params);
301
+ return;
302
+ }
303
+ isDownThis.value = true;
304
+ downPosition.x = x;
305
+ downPosition.y = y;
306
+ }
307
+ });
308
+ DnDManagerInstance.on("first-move", (params, event) => {
309
+ const { x, y } = params;
310
+ const directions = immediateDirections.value.filter((item) => item !== ImmediateEnumType.ALL);
311
+ if (isDownThis.value && !props.disabled && directions.length) {
312
+ const distanceH = x - downPosition.x;
313
+ const distanceHAbs = Math.abs(distanceH);
314
+ const distanceV = y - downPosition.y;
315
+ const distanceVAbs = Math.abs(distanceV);
316
+ const max = Math.max(distanceHAbs, distanceVAbs);
317
+ const isMaxH = distanceHAbs === max;
318
+ const isMaxV = distanceVAbs === max;
319
+ let isImmediate = false;
320
+ if (isMaxH) {
321
+ isImmediate = directions.includes(ImmediateEnumType.LEFT) && distanceH < 0 || directions.includes(ImmediateEnumType.RIGHT) && distanceH > 0;
322
+ }
323
+ if (isMaxV && !isImmediate) {
324
+ isImmediate = directions.includes(ImmediateEnumType.TOP) && distanceV < 0 || directions.includes(ImmediateEnumType.BOTTOM) && distanceV > 0;
325
+ }
326
+ if (isImmediate) {
327
+ event.preventDefault();
328
+ event.stopPropagation();
329
+ handleStart(downPosition);
330
+ handleMove(params);
331
+ return;
332
+ }
333
+ }
334
+ });
335
+ DnDManagerInstance.on("start", (params) => {
336
+ const { x, y } = params;
337
+ const startEl = document.elementFromPoint(x, y);
338
+ if (!props.disabled && dragItemRef.value && dragItemRef.value.contains(startEl)) {
339
+ handleStart(params);
340
+ }
341
+ });
342
+ DnDManagerInstance.on("move", (params) => {
343
+ const { point } = params;
344
+ if (isDragThis.value) {
345
+ handleMove(point);
346
+ }
347
+ });
348
+ DnDManagerInstance.on("end", () => {
349
+ isDownThis.value = false;
350
+ isDragThis.value = false;
351
+ Object.assign(downPosition, {
352
+ x: 0,
353
+ y: 0
354
+ });
355
+ Object.assign(cloneNodePosition, {
356
+ x: 0,
357
+ y: 0
358
+ });
359
+ });
360
+ function handleStart(point) {
361
+ isDragThis.value = true;
362
+ cloneNodePosition.x = point.x;
363
+ cloneNodePosition.y = point.y;
364
+ DnDManagerInstance.updateDargInfo(props.type, props.data);
365
+ }
366
+ function handleMove(point) {
367
+ cloneNodePosition.x = point.x;
368
+ cloneNodePosition.y = point.y;
369
+ }
370
+ return (_ctx, _cache) => {
371
+ return vue.openBlock(), vue.createElementBlock("div", {
372
+ ref_key: "dragItemRef",
373
+ ref: dragItemRef,
374
+ class: vue.normalizeClass(["draggable-area", _ctx.disabled ? "disabled" : ""])
375
+ }, [
376
+ vue.renderSlot(_ctx.$slots, "default", {}, void 0, true),
377
+ isDragThis.value ? (vue.openBlock(), vue.createElementBlock("div", {
378
+ key: 0,
379
+ class: "draggable-clone-item",
380
+ style: vue.normalizeStyle(cloneNodeStyle.value)
381
+ }, [
382
+ vue.renderSlot(_ctx.$slots, "drag-item", {}, () => [
383
+ vue.renderSlot(_ctx.$slots, "default", {}, void 0, true)
384
+ ], true)
385
+ ], 4)) : vue.createCommentVNode("", true)
386
+ ], 2);
387
+ };
388
+ }
389
+ });
390
+ const _export_sfc = (sfc, props) => {
391
+ const target = sfc.__vccOpts || sfc;
392
+ for (const [key, val] of props) {
393
+ target[key] = val;
394
+ }
395
+ return target;
396
+ };
397
+ const Draggable = /* @__PURE__ */ _export_sfc(_sfc_main$1, [["__scopeId", "data-v-34f8fce1"]]);
398
+ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
399
+ ...{
400
+ name: "HvDroppable"
401
+ },
402
+ __name: "Droppable",
403
+ props: {
404
+ acceptDragType: {}
405
+ },
406
+ emits: ["enter", "move", "drop", "leave"],
407
+ setup(__props, { emit: __emit }) {
408
+ const emits = __emit;
409
+ const props = __props;
410
+ const dropAreaRef = vue.ref();
411
+ const isEntered = vue.ref(false);
412
+ const acceptDragTypeList = vue.computed(() => {
413
+ return Array.isArray(props.acceptDragType) ? props.acceptDragType : [props.acceptDragType];
414
+ });
415
+ function getPositionInArea(point) {
416
+ if (!dropAreaRef.value) {
417
+ return {
418
+ isInArea: false,
419
+ position: {
420
+ x: 0,
421
+ y: 0
422
+ }
423
+ };
424
+ }
425
+ const { x: pointX, y: pointY } = point;
426
+ const { x, y, width, height } = dropAreaRef.value.getBoundingClientRect();
427
+ const posX = pointX - x;
428
+ const posY = pointY - y;
429
+ return {
430
+ isInArea: posX >= 0 && posX <= width && posY >= 0 && posY <= height,
431
+ position: {
432
+ x: posX,
433
+ y: posY
434
+ }
435
+ };
436
+ }
437
+ DnDManagerInstance.on("move", (params) => {
438
+ const { type, data, point } = params;
439
+ if (dropAreaRef.value && acceptDragTypeList.value.includes(type)) {
440
+ const { isInArea, position } = getPositionInArea(point);
441
+ if (isInArea) {
442
+ if (isEntered.value) {
443
+ emits("move", type, position, data);
444
+ } else {
445
+ isEntered.value = true;
446
+ emits("enter", type, position, data);
447
+ }
448
+ } else if (isEntered.value) {
449
+ isEntered.value = false;
450
+ emits("leave", type, data);
451
+ }
452
+ }
453
+ });
454
+ DnDManagerInstance.on("end", ({ type, point, data }) => {
455
+ if (dropAreaRef.value && acceptDragTypeList.value.includes(type) && isEntered.value) {
456
+ const { position } = getPositionInArea(point);
457
+ emits("drop", type, position, data);
458
+ }
459
+ });
460
+ return (_ctx, _cache) => {
461
+ return vue.openBlock(), vue.createElementBlock("div", {
462
+ ref_key: "dropAreaRef",
463
+ ref: dropAreaRef,
464
+ class: "droppable-area"
465
+ }, [
466
+ vue.renderSlot(_ctx.$slots, "default", {}, void 0, true)
467
+ ], 512);
468
+ };
469
+ }
470
+ });
471
+ const Droppable = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-28d651f3"]]);
472
+ const HvDraggable = withInstall(Draggable);
473
+ const HvDroppable = withInstall(Droppable);
474
+ exports2.HvDraggable = HvDraggable;
475
+ exports2.HvDroppable = HvDroppable;
476
+ Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" });
477
+ });
package/dist/style.css ADDED
@@ -0,0 +1,20 @@
1
+ .draggable-area[data-v-34f8fce1] {
2
+ width: fit-content;
3
+ cursor: grab;
4
+ }
5
+ .draggable-area.disabled[data-v-34f8fce1] {
6
+ cursor: not-allowed;
7
+ }
8
+ .draggable-clone-item[data-v-34f8fce1] {
9
+ position: fixed;
10
+ top: 0;
11
+ left: 0;
12
+ z-index: 99999;
13
+ width: fit-content;
14
+ cursor: grabbing;
15
+ opacity: 0.8;
16
+ will-change: transform;
17
+ }.droppable-area[data-v-28d651f3] {
18
+ position: relative;
19
+ width: fit-content;
20
+ }
@@ -0,0 +1,47 @@
1
+ import type { DragAndDropDragType } from './manager';
2
+ declare const ImmediateEnumType: {
3
+ readonly LEFT: "left";
4
+ readonly RIGHT: "right";
5
+ readonly TOP: "top";
6
+ readonly BOTTOM: "bottom";
7
+ readonly ALL: "all";
8
+ };
9
+ type ImmediateType = (typeof ImmediateEnumType)[keyof typeof ImmediateEnumType];
10
+ type __VLS_Props = {
11
+ type: DragAndDropDragType;
12
+ immediate?: ImmediateType | Array<ImmediateType> | undefined;
13
+ disabled?: boolean;
14
+ data?: any;
15
+ };
16
+ declare const dragItemRef: import("vue").Ref<HTMLElement | undefined, HTMLElement | undefined>;
17
+ /** 是否开始拖动 */
18
+ declare const isDragThis: import("vue").Ref<boolean, boolean>;
19
+ declare const cloneNodeStyle: import("vue").ComputedRef<{
20
+ transform: string;
21
+ }>;
22
+ declare const __VLS_ctx: InstanceType<__VLS_PickNotAny<typeof __VLS_self, new () => {}>>;
23
+ declare var __VLS_1: {}, __VLS_3: {}, __VLS_5: {};
24
+ type __VLS_Slots = __VLS_PrettifyGlobal<__VLS_OmitStringIndex<typeof __VLS_ctx.$slots> & {
25
+ default?: (props: typeof __VLS_1) => any;
26
+ } & {
27
+ 'drag-item'?: (props: typeof __VLS_3) => any;
28
+ } & {
29
+ default?: (props: typeof __VLS_5) => any;
30
+ }>;
31
+ declare const __VLS_self: import("vue").DefineComponent<__VLS_Props, {
32
+ dragItemRef: typeof dragItemRef;
33
+ isDragThis: typeof isDragThis;
34
+ cloneNodeStyle: typeof cloneNodeStyle;
35
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
36
+ disabled: boolean;
37
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
38
+ declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
39
+ disabled: boolean;
40
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
41
+ declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
42
+ export default _default;
43
+ type __VLS_WithSlots<T, S> = T & {
44
+ new (): {
45
+ $slots: S;
46
+ };
47
+ };
@@ -0,0 +1,41 @@
1
+ import type { DragAndDropDragType, DragAndDropPoint } from './manager';
2
+ type __VLS_Props = {
3
+ acceptDragType: DragAndDropDragType | Array<DragAndDropDragType>;
4
+ };
5
+ declare const dropAreaRef: import("vue").Ref<HTMLElement | undefined, HTMLElement | undefined>;
6
+ declare const __VLS_ctx: InstanceType<__VLS_PickNotAny<typeof __VLS_self, new () => {}>>;
7
+ declare var __VLS_1: {};
8
+ type __VLS_Slots = __VLS_PrettifyGlobal<__VLS_OmitStringIndex<typeof __VLS_ctx.$slots> & {
9
+ default?: (props: typeof __VLS_1) => any;
10
+ }>;
11
+ declare const __VLS_self: import("vue").DefineComponent<__VLS_Props, {
12
+ dropAreaRef: typeof dropAreaRef;
13
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
14
+ drop: (type: DragAndDropDragType, point: DragAndDropPoint, data: any) => any;
15
+ move: (type: DragAndDropDragType, point: DragAndDropPoint, data: any) => any;
16
+ enter: (type: DragAndDropDragType, point: DragAndDropPoint, data: any) => any;
17
+ leave: (type: DragAndDropDragType, data: any) => any;
18
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
19
+ onDrop?: ((type: DragAndDropDragType, point: DragAndDropPoint, data: any) => any) | undefined;
20
+ onMove?: ((type: DragAndDropDragType, point: DragAndDropPoint, data: any) => any) | undefined;
21
+ onEnter?: ((type: DragAndDropDragType, point: DragAndDropPoint, data: any) => any) | undefined;
22
+ onLeave?: ((type: DragAndDropDragType, data: any) => any) | undefined;
23
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
24
+ declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
25
+ drop: (type: DragAndDropDragType, point: DragAndDropPoint, data: any) => any;
26
+ move: (type: DragAndDropDragType, point: DragAndDropPoint, data: any) => any;
27
+ enter: (type: DragAndDropDragType, point: DragAndDropPoint, data: any) => any;
28
+ leave: (type: DragAndDropDragType, data: any) => any;
29
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
30
+ onDrop?: ((type: DragAndDropDragType, point: DragAndDropPoint, data: any) => any) | undefined;
31
+ onMove?: ((type: DragAndDropDragType, point: DragAndDropPoint, data: any) => any) | undefined;
32
+ onEnter?: ((type: DragAndDropDragType, point: DragAndDropPoint, data: any) => any) | undefined;
33
+ onLeave?: ((type: DragAndDropDragType, data: any) => any) | undefined;
34
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
35
+ declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
36
+ export default _default;
37
+ type __VLS_WithSlots<T, S> = T & {
38
+ new (): {
39
+ $slots: S;
40
+ };
41
+ };
@@ -0,0 +1,6 @@
1
+ import type { SFCWithInstall } from '@havue/utils';
2
+ import Draggable from './Draggable.vue';
3
+ import Droppable from './Droppable.vue';
4
+ export declare const HvDraggable: SFCWithInstall<typeof Draggable>;
5
+ export declare const HvDroppable: SFCWithInstall<typeof Droppable>;
6
+ export type { DragAndDropPoint, DragAndDropDragType, DnDManager } from './manager';
@@ -0,0 +1,81 @@
1
+ import { EventBus } from '@havue/shared';
2
+ export type DragAndDropPoint = {
3
+ x: number;
4
+ y: number;
5
+ };
6
+ export type DragAndDropDragType = string | number | symbol;
7
+ type Events = {
8
+ down: (p: DragAndDropPoint) => void;
9
+ 'first-move': (p: DragAndDropPoint, e: MouseEvent | TouchEvent) => void;
10
+ start: (p: DragAndDropPoint) => void;
11
+ move: (params: {
12
+ type: DragAndDropDragType;
13
+ data: any;
14
+ point: DragAndDropPoint;
15
+ }) => void;
16
+ end: (params: {
17
+ type: DragAndDropDragType;
18
+ data: any;
19
+ point: DragAndDropPoint;
20
+ }) => void;
21
+ };
22
+ export declare class DnDManager extends EventBus<Events> {
23
+ /** 是否开始拖动 */
24
+ isDragStart: boolean;
25
+ /** 拖动元素类型 */
26
+ dragType: DragAndDropDragType | undefined;
27
+ /** Draggable传递的数据,
28
+ * 供Droppable使用
29
+ */
30
+ dragData: any;
31
+ private isSendFirstMovePos;
32
+ /** 移动端长按一定时间才触发 onStart */
33
+ private touchStartPressTime;
34
+ /** touchstart事件触发的时的时间 */
35
+ private touchStartTime;
36
+ /** touchstart时间触发的位置 */
37
+ private touchStartPosition;
38
+ /** onMove最后位置 */
39
+ private lastMovePoint;
40
+ private emitTouchStartTimer;
41
+ private destroy;
42
+ constructor();
43
+ updateDargInfo(type: Exclude<DragAndDropDragType, undefined>, data: any): void;
44
+ /**
45
+ * 通知 Draggable 开始点击
46
+ * @param point
47
+ */
48
+ private onDown;
49
+ /**
50
+ * 通知 Draggable 拖拽开始
51
+ * @param point
52
+ */
53
+ private onFirstMove;
54
+ /**
55
+ * 通知 Draggable 拖拽开始
56
+ * @param point
57
+ */
58
+ private onStart;
59
+ /**
60
+ * 通知 Draggable 移动
61
+ * @param point
62
+ * @returns
63
+ */
64
+ private onMove;
65
+ /**
66
+ * 结束拖拽
67
+ * @returns
68
+ */
69
+ private onEnd;
70
+ private onMouseDown;
71
+ private onMouseMove;
72
+ private onMouseUp;
73
+ private onTouchStart;
74
+ private onTouchMove;
75
+ private onTouchEnd;
76
+ private bindEventListener;
77
+ private removeEventListener;
78
+ destroyed(): void;
79
+ }
80
+ export declare const DnDManagerInstance: DnDManager;
81
+ export {};
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@havue/drag-and-drop",
3
+ "version": "1.0.0",
4
+ "description": "Drag and drop components for Vue3",
5
+ "keywords": [
6
+ "havue",
7
+ "components",
8
+ "drag",
9
+ "drop",
10
+ "vue3"
11
+ ],
12
+ "license": "MIT",
13
+ "homepage": "https://happypedestrian.github.io/havue/guide/",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/HappyPedestrian/havue.git"
17
+ },
18
+ "dependencies": {
19
+ "@havue/shared": "^1.0.0"
20
+ },
21
+ "devDependencies": {
22
+ "vue": "^3.3.0"
23
+ },
24
+ "peerDependencies": {
25
+ "vue": "^3.3.0"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public",
29
+ "registry": "https://registry.npmjs.org/"
30
+ },
31
+ "files": [
32
+ "dist"
33
+ ],
34
+ "main": "./dist/drag-and-drop.umd.js",
35
+ "module": "./dist/drag-and-drop.mjs",
36
+ "types": "./dist/types/src/index.d.ts",
37
+ "exports": {
38
+ ".": {
39
+ "require": "./dist/drag-and-drop.umd.js",
40
+ "import": "./dist/drag-and-drop.mjs",
41
+ "types": "./dist/types/src/index.d.ts"
42
+ }
43
+ },
44
+ "scripts": {
45
+ "build": "vite build --mode package"
46
+ }
47
+ }