estreui 1.2.2 → 1.2.4

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.
@@ -0,0 +1,1374 @@
1
+ /*
2
+ EstreUI rimwork — Swipe / Draggable handlers
3
+ Part of the split from estreUi.js (roadmap #002 phase 2).
4
+
5
+ This file is loaded as a plain <script> tag and shares the global scope
6
+ with the other estreUi-*.js files. Load order matters: see index.html.
7
+ */
8
+
9
+ // MODULE: Interaction -- EstreSwipeHandler, EstreDraggableHandler
10
+ // ======================================================================
11
+
12
+ /**
13
+ * Attachable swipe handler
14
+ */
15
+ class EstreSwipeHandler {
16
+
17
+ // constants
18
+ static mouseTrigger = "mousedown";
19
+ static pointerTrigger = "pointerdown";
20
+ static touchTrigger = "touchstart";
21
+
22
+ static mouseUpTriggerSet = ["mouseup"];
23
+ static pointerUpTriggerSet = ["pointercancel", "pointerup"];
24
+ static touchUpTriggerSet = ["touchcancel", "touchend"];
25
+
26
+ static mouseHandleSet = ["mouseup", "mousemove"];
27
+ static pointerHandleSet = ["pointercancel", "pointerup", "pointermove"];
28
+ static touchHandleSet = ["touchcancel", "touchend", "touchmove"];
29
+
30
+ defaultThreshold = 80;//80px //20;//20px
31
+
32
+ // statics
33
+ static handlers = [];
34
+ static register(instance) { return this.handlers.push(instance) - 1; }
35
+
36
+ // open property
37
+ stopPropagation = false;
38
+ preventDefault = false;
39
+
40
+ preventDown = false;
41
+ preventCancel = false;
42
+ preventUp = false;
43
+ preventMove = false;
44
+
45
+ thresholdX = -1;
46
+ thresholdY = -1;
47
+
48
+ dropStrayed = false;
49
+
50
+ onDown = null;
51
+ onMove = null;
52
+ onCancel = null;
53
+ onUp = null;
54
+
55
+ isDebug = false;
56
+ debugDisplay = null;
57
+
58
+ cancelDelay = 200;
59
+
60
+
61
+ // enclosed property
62
+ #handleIndex = null;
63
+ #handleId = null;
64
+
65
+ #bound = null;
66
+ #$bound = null;
67
+ #data = null;
68
+ #$responseBound = null;
69
+ #$outerBound = null;
70
+
71
+ #triggerEventAllowed = new Set();
72
+ #triggerUpEventAllowed = new Set();
73
+ #handleEventAllowed = new Set();
74
+
75
+ #isMoving = false;
76
+ #eventOrigin = null;
77
+ #directed = null;
78
+
79
+ #startX = null;
80
+ #startY = null;
81
+
82
+ #shiftX = null;
83
+ #shiftY = null;
84
+
85
+ #lastX = null;
86
+ #lastY = null;
87
+
88
+ #grabX = null;
89
+ #grabY = null;
90
+
91
+ #pointerType = null;
92
+ #eventType = null;
93
+
94
+ #grabMarker = null;
95
+
96
+
97
+ // getter and setter
98
+ #$wind = $(window);
99
+ get $wind() { return this.#$wind }
100
+
101
+ get allowedDirection() { return this.allowedDirectionX ? (this.allowedDirectionY ? "both": "horizontal") : (this.allowedDirectionY ? "vertical" : "neither"); }
102
+ get allowedDirectionX() { return this.thresholdX > -1; }
103
+ get allowedDirectionY() { return this.thresholdY > -1; }
104
+ get directionFix() { return this.directionFixX ? (this.directionFixY ? "both" : "horizontal") : (this.directionFixY ? "vertical" : "neither"); }
105
+ get directionFixX() { return this.thresholdX > 0; }
106
+ get directionFixY() { return this.thresholdY > 0; }
107
+ get directtion() { return this.grabX > this.grabY ? this.directionX : this.directionY; }
108
+ get directionX() { return this.grabX < 0 ? "left" : (this.grabX > 0 ? "right" : null); }
109
+ get directionY() { return this.grabY < 0 ? "up" : (this.grabY > 0 ? "down" : null); }
110
+ get handledDirection() {
111
+ if (this.isMoving) switch (this.directionFix) {
112
+ case "both":
113
+ if (this.directed != null) {
114
+ const moveX = this.moveX;
115
+ const moveY = this.moveY;
116
+ if (moveX > moveY) {
117
+ if (this.exceedX && moveX - moveY > this.thresholdX) return this.directionX;
118
+ } else if (moveX < moveY) {
119
+ if (this.exceedY && moveY - moveX > this.thresholdY) return this.directionY;
120
+ }
121
+ }
122
+
123
+ case "horizontal":
124
+ if (this.directed == "horizontal" && this.exceedX) return this.directionX;
125
+
126
+ case "vertical":
127
+ if (this.directed == "vertical" && this.exceedY) return this.directionY;
128
+
129
+ case "neither":
130
+ if (this.allowedDirectionX && this.grabX != null) return this.directionX;
131
+ if (this.allowedDirectionY && this.grabY != null) return this.directionY;
132
+ }
133
+ return null;
134
+ }
135
+ get handled() {
136
+ if (!this.isMoving) return false;
137
+ else {
138
+ switch (this.directionFix) {
139
+ case "both":
140
+ return this.directed != null && (this.exceedX || this.exceedY);
141
+
142
+ case "horizontal":
143
+ return this.directed == "horizontal" && this.exceedX;
144
+
145
+ case "vertical":
146
+ return this.directed == "vertical" && this.exceedY;
147
+
148
+ case "neither":
149
+ return (this.allowedDirectionX && this.grabX != null) || (this.allowedDirectionY && this.grabY != null);
150
+ }
151
+ }
152
+ }
153
+ get moveX() { return Math.abs(this.lastX - this.startX); }
154
+ get moveY() { return Math.abs(this.lastY - this.startY); }
155
+ get exceedX() { return this.directionFixX && this.moveX > this.thresholdX; }
156
+ get exceedY() { return this.directionFixY && this.moveY > this.thresholdY; }
157
+ get strayedX() { return this.directionFixX && this.moveY > this.thresholdX * 2; }
158
+ get strayedY() { return this.directionFixY && this.moveX > this.thresholdY * 2; }
159
+
160
+ get #triggers() { return Array.from(this.#triggerEventAllowed).join(" "); }
161
+ get #upTriggers() { return Array.from(this.#triggerUpEventAllowed).join(" "); }
162
+ get #upDownTriggers() { return [...Array.from(this.#triggerEventAllowed), ...Array.from(this.#triggerUpEventAllowed)].join(" "); }
163
+ get #handles() { return Array.from(this.#handleEventAllowed).join(" "); }
164
+ get #events() { return [...Array.from(this.#triggerEventAllowed), ...Array.from(this.#triggerUpEventAllowed), ...Array.from(this.#handleEventAllowed)].join(" "); }
165
+
166
+ get isMoving() { return this.#isMoving; }
167
+ get eventOrigin() { return this.#eventOrigin; }
168
+ get directed() { return this.#directed; }
169
+ get startX() { return this.#startX; }
170
+ get startY() { return this.#startY; }
171
+ get shiftX() { return this.#shiftX; }
172
+ get shiftY() { return this.#shiftY; }
173
+ get lastX() { return this.#lastX; }
174
+ get lastY() { return this.#lastY; }
175
+ get grabX() { return this.#grabX; }
176
+ get grabY() { return this.#grabY; }
177
+ get pointerType() { return this.#pointerType; }
178
+ get eventType() { return this.#eventType; }
179
+
180
+
181
+ /**
182
+ * Set swipe handler for element
183
+ *
184
+ * if need stopPropagation or preventDefault to be set each property.
185
+ * and custom event callbacks to be set methods.
186
+ *
187
+ * @param {Element} element is target element
188
+ * @param {boolean} [onMouse=true] allow mouse handle (must be allowed one in three options)
189
+ * @param {boolean} [onPointer=true] allow pointer handle (must be allowed one in three options)
190
+ * @param {boolean} [onTouch=true] allow touch handle (must be allowed one in three options)
191
+ * @param {number} [thresholdX=this.defaultThreshold] fix direction threshold px - 0 = unuse direction fix, -1 = unallowed horizontal swipe
192
+ * @param {number} [thresholdY=this.defaultThreshold] fix direction threshold px - 0 = unuse direction fix, -1 = unallowed vertical swipe
193
+ * @param {boolean} [debug=false] show event triggers and values when true
194
+ */
195
+ constructor (element, onMouse = true, onPointer = true, onTouch = true, thresholdX = this.defaultThreshold, thresholdY = this.defaultThreshold, debug = false) {
196
+ this.#setHandleId();
197
+
198
+ this.isDebug = debug;
199
+
200
+ this.setEventMouse(onMouse, false);
201
+ this.setEventPointer(onPointer, false);
202
+ this.setEventTouch(onTouch, false);
203
+
204
+ this.setThresholdX(thresholdX);
205
+ this.setThresholdY(thresholdY);
206
+
207
+ this.setDropStrayed();
208
+
209
+ this.setElement(element);
210
+ }
211
+
212
+ #setHandleId() {
213
+ this.#handleIndex = EstreSwipeHandler.register(this);
214
+ this.#handleId = this.constructor.name + "@" + Date.now() + "#" + this.#handleIndex;
215
+ }
216
+
217
+ #dropHandle() {
218
+ setTimeout(_ => this.$wind.attr(eds.onSwipe, null), 0);
219
+ //this.#$outerBound.off(this.#upTriggers, null, this.#onClick);
220
+ this.#$bound.off(this.#handles, null, this.#onEvent);
221
+ this.$wind.off(this.#handles, null, this.#onEvent);
222
+ this.#isMoving = false;
223
+ this.#directed = null;
224
+ this.#startX = null;
225
+ this.#startY = null;
226
+ this.#shiftX = null;
227
+ this.#shiftY = null;
228
+ this.#lastX = null;
229
+ this.#lastY = null;
230
+ this.#grabX = null;
231
+ this.#grabY = null;
232
+ this.#pointerType = null;
233
+ this.#eventType = null;
234
+ }
235
+
236
+ #clearBound() {
237
+ this.#$responseBound.css("--grab-x", "0px");
238
+ this.#$responseBound.css("--grab-y", "0px");
239
+ this.#$responseBound.attr(eds.onGrab, "");
240
+ }
241
+
242
+ release() {
243
+ this.#dropHandle();
244
+ this.#clearBound();
245
+ this.#$responseBound = null;
246
+ //this.#$outerBound.off(this.#triggers, null, this.#onClick);
247
+ this.#$outerBound = null;
248
+ const $blockTarget = this.#$bound.find(uis.blockSwipe);
249
+ $blockTarget.off(this.#events, this.#onBlock);
250
+ this.#$bound.css("user-select", "");
251
+ this.#$bound.off("click", null, this.#onClick);
252
+ //this.#$bound.off(this.#handles, null, this.#onHandle);
253
+ this.#$bound = null;
254
+ if (this.#bound.swipeHandler == this) delete this.#bound.swipeHandler;
255
+ this.#bound = null;
256
+ this.#data = null;
257
+ delete EstreSwipeHandler.handlers[this.#handleIndex];
258
+
259
+ return this;
260
+ }
261
+
262
+
263
+ setEventMouse(enable = true, byUser = true) {
264
+ if (enable) {
265
+ this.#triggerEventAllowed.add(EstreSwipeHandler.mouseTrigger);
266
+ EstreSwipeHandler.mouseUpTriggerSet.forEach(item => this.#triggerUpEventAllowed.add(item));
267
+ EstreSwipeHandler.mouseHandleSet.forEach(item => this.#handleEventAllowed.add(item));
268
+ } else {
269
+ this.#triggerEventAllowed.delete(EstreSwipeHandler.mouseTrigger);
270
+ EstreSwipeHandler.mouseUpTriggerSet.forEach(item => this.#handleEventAllowed.delete(item));
271
+ EstreSwipeHandler.mouseHandleSet.forEach(item => this.#handleEventAllowed.delete(item));
272
+ }
273
+
274
+ if (byUser) this.setElement();
275
+
276
+ return this;
277
+ }
278
+
279
+ setEventPointer(enable = true, byUser = true) {
280
+ if (enable) {
281
+ this.#triggerEventAllowed.add(EstreSwipeHandler.pointerTrigger);
282
+ EstreSwipeHandler.pointerUpTriggerSet.forEach(item => this.#triggerUpEventAllowed.add(item));
283
+ EstreSwipeHandler.pointerHandleSet.forEach(item => this.#handleEventAllowed.add(item));
284
+ } else {
285
+ this.#triggerEventAllowed.delete(EstreSwipeHandler.pointerTrigger);
286
+ EstreSwipeHandler.pointerUpTriggerSet.forEach(item => this.#triggerUpEventAllowed.delete(item));
287
+ EstreSwipeHandler.pointerHandleSet.forEach(item => this.#handleEventAllowed.delete(item));
288
+ }
289
+
290
+ if (byUser) this.setElement();
291
+
292
+ return this;
293
+ }
294
+
295
+ setEventTouch(enable = true, byUser = true) {
296
+ if (enable) {
297
+ this.#triggerEventAllowed.add(EstreSwipeHandler.touchTrigger);
298
+ EstreSwipeHandler.touchUpTriggerSet.forEach(item => this.#triggerUpEventAllowed.add(item));
299
+ EstreSwipeHandler.touchHandleSet.forEach(item => this.#handleEventAllowed.add(item));
300
+ } else {
301
+ this.#triggerEventAllowed.delete(EstreSwipeHandler.touchTrigger);
302
+ EstreSwipeHandler.touchUpTriggerSet.forEach(item => this.#triggerUpEventAllowed.delete(item));
303
+ EstreSwipeHandler.touchHandleSet.forEach(item => this.#handleEventAllowed.delete(item));
304
+ }
305
+
306
+ if (byUser) this.setElement();
307
+
308
+ return this;
309
+ }
310
+
311
+
312
+ setThresholdX(threshold = this.thresholdX) {
313
+ this.thresholdX = threshold;
314
+
315
+ return this;
316
+ }
317
+
318
+ setThresholdY(threshold = this.thresholdY) {
319
+ this.thresholdY = threshold;
320
+
321
+ return this;
322
+ }
323
+
324
+ unuseDirectionFixX() {
325
+ this.thresholdX = 0;
326
+
327
+ return this;
328
+ }
329
+
330
+ unuseDriectionFixY() {
331
+ this.thresholdY = 0;
332
+
333
+ return this;
334
+ }
335
+
336
+ unuseX() {
337
+ this.thresholdX = -1;
338
+
339
+ return this;
340
+ }
341
+
342
+ unuseY() {
343
+ this.thresholdY = -1;
344
+
345
+ return this;
346
+ }
347
+
348
+
349
+ setDropStrayed(enable = true) {
350
+ this.dropStrayed = enable;
351
+
352
+ return this;
353
+ }
354
+
355
+
356
+ //custom setters
357
+ setResponseBound(bound = this.#$bound) {
358
+ this.#$responseBound = bound instanceof jQuery ? bound : $(bound);
359
+ return this;
360
+ }
361
+
362
+ setOuterBound(bound = this.#$bound.parent()) {
363
+ //if (this.#$outerBound != null) this.#$outerBound.off(this.#triggers, null, this.#onClick);
364
+ this.#$outerBound = bound instanceof jQuery ? bound : $(bound);
365
+ //bound.on(this.#triggers, null, this.#onClick);
366
+ return this;
367
+ }
368
+
369
+ setStopPropagation(enable = true) {
370
+ this.stopPropagation = enable;
371
+ return this;
372
+ }
373
+
374
+ setPreventDefault(enable = true) {
375
+ this.preventDefault = enable;
376
+ return this;
377
+ }
378
+
379
+ setPreventDown(enable = true) {
380
+ this.preventDown = enable;
381
+ return this;
382
+ }
383
+
384
+ setPreventCancel(enable = true) {
385
+ this.preventCancel = enable;
386
+ return this;
387
+ }
388
+
389
+ setPreventUp(enable = true) {
390
+ this.preventUp = enable;
391
+ return this;
392
+ }
393
+
394
+ setPreventMove(enable = true) {
395
+ this.preventMove = enable;
396
+ return this;
397
+ }
398
+
399
+ setPreventAll(enable = true) {
400
+ return this.setPreventDown(enable).setPreventUp(enable).setPreventCancel(enable).setPreventMove(enable);
401
+ }
402
+
403
+ setDebug(enable = true) {
404
+ this.isDebug = enable;
405
+ return this;
406
+ }
407
+
408
+ setDebugDisplay(element = null) {
409
+ this.debugDisplay = element;
410
+ return this;
411
+ }
412
+
413
+ setCancelDelay(delay = 200) {
414
+ this.cancelDelay = delay;
415
+ return this;
416
+ }
417
+
418
+
419
+ setOnDown(callback) {
420
+ this.onDown = callback;
421
+
422
+ return this;
423
+ }
424
+
425
+ setOnMove(callback) {
426
+ this.onMove = callback;
427
+
428
+ return this;
429
+ }
430
+
431
+ setOnCancel(callback) {
432
+ this.onCancel = callback;
433
+
434
+ return this;
435
+ }
436
+
437
+ setOnUp(callback) {
438
+ this.onUp = callback;
439
+
440
+ return this;
441
+ }
442
+
443
+ //---
444
+
445
+
446
+ #onEvent = (e) => {
447
+ const isSelf = e.target == e.delegateTarget;
448
+ var isBlocked = false;
449
+ var curElem = e.target;
450
+ if (!isSelf) do {
451
+ const $curElem = $(curElem);
452
+ if ($curElem.is(uis.allowSwipe)) break;
453
+ else if ($curElem.is(uis.blockSwipe)) {
454
+ isBlocked = true;
455
+ break;
456
+ }
457
+ curElem = curElem.parentElement;
458
+ } while (curElem != document.body && curElem != e.delegateTarget);
459
+ if (isBlocked) return;
460
+
461
+ const isTouch = e.type.indexOf("touch") > -1;
462
+ const isMouse = e.type.indexOf("mouse") > -1;
463
+ const isPointer = e.type.indexOf("pointer") > -1;
464
+ const screenX = isTouch ? (e.touches.length > 0 ? e.touches[0].screenX : null) : e.screenX;
465
+ const screenY = isTouch ? (e.touches.length > 0 ? e.touches[0].screenY : null) : e.screenY;
466
+ const pointerType = isTouch ? "touch" : (isMouse ? "mouse" : (isPointer ? "pointer" : "extra"));
467
+
468
+ var canceled = false;
469
+ switch(e.type) {
470
+ case "pointerdown":
471
+ break;
472
+ case "mousedown":
473
+ if (isMouse && e.button !== 0) break;
474
+ case "touchstart":
475
+ if (this.isMoving) {
476
+ if (this.preventDown) {
477
+ if (this.preventDefault) e.preventDefault();
478
+ if (this.stopPropagation) e.stopPropagation();
479
+ if (this.preventDefault) return false;
480
+ }
481
+ }
482
+ this.#eventType = "down";
483
+ this.#isMoving = true;
484
+ this.#eventOrigin = e.target;
485
+ this.#pointerType = pointerType;
486
+ this.#lastX = screenX;
487
+ this.#lastY = screenY;
488
+ if (this.shiftX == null) this.#shiftX = 0;
489
+ if (this.shiftY == null) this.#shiftY = 0;
490
+
491
+ if (this.startX != null) {
492
+ this.#shiftX += screenX - this.startX;
493
+ if (this.grabX != null) this.#grabX = this.shiftX;
494
+ } else this.#startX = screenX;
495
+ if (this.startY != null) {
496
+ this.#shiftY += screenY - this.startY;
497
+ if (this.grabY != null) this.grabY = this.shiftY;
498
+ } else this.#startY = screenY;
499
+
500
+ if (this.isDebug) {
501
+ var log = "start: " + f4f(this.startX) + ", " + f4f(this.startY) + " / shift: " + f4f(this.shiftX) + ", " + f4f(this.shiftY) + " / last: " + f4f(this.lastX) + ", " + f4f(this.lastY) + " / grab: " + f4f(grabX) + ", " + f4f(grabY);
502
+ console.log(e.type + " - " + log);
503
+ if (this.debugDisplay != null) this.debugDisplay.prepend(e.type + " - " + log + "<br />");
504
+ }
505
+
506
+ this.#$responseBound.css("--grab-x", this.shiftX + "px");
507
+ this.#$responseBound.css("--grab-y", this.shiftY + "px");
508
+ if (this.onDown != null) this.onDown(this.startX, this.startY);
509
+ this.$wind.on(this.#handles, null, this.#onEvent);
510
+ this.#$bound.on(this.#handles, null, this.#onEvent);
511
+ //this.#$outerBound.on(this.#upTriggers, null, this.#onClick);
512
+ //$(this.eventOrigin).on("click", null, this.#onClick);
513
+ if (this.preventDown) {
514
+ if (this.preventDefault) e.preventDefault();
515
+ if (this.stopPropagation) e.stopPropagation();
516
+ if (this.preventDefault) return false;
517
+ }
518
+ break;
519
+
520
+ case "pointercancel":
521
+ case "touchcancel":
522
+ if (!this.isMoving) break;
523
+ if (this.#pointerType != pointerType || this.eventType == "cancel") {
524
+ if (this.handled) {
525
+ if (this.preventCancel) {
526
+ if (this.preventDefault) e.preventDefault();
527
+ if (this.stopPropagation) e.stopPropagation();
528
+ if (this.preventDefault) return false;
529
+ }
530
+ }
531
+ break;
532
+ }
533
+ canceled = true;
534
+ this.#eventType = "cancel";
535
+ if (this.isDebug) console.log("canceled");
536
+ if (this.onCancel != null) this.onCancel();
537
+ case "pointerup":
538
+ case "mouseup":
539
+ case "touchend":
540
+ if (!this.isMoving) break;
541
+ if (this.eventType == "up") {
542
+ if (this.handled) {
543
+ if (this.preventUp) {
544
+ if (this.preventDefault) e.preventDefault();
545
+ if (this.stopPropagation) e.stopPropagation();
546
+ if (this.preventDefault) return false;
547
+ }
548
+ }
549
+ break;
550
+ }
551
+ if (!canceled) {
552
+ if (this.#pointerType != pointerType) {
553
+ if (this.handled) {
554
+ if (this.preventUp) {
555
+ if (this.preventDefault) e.preventDefault();
556
+ if (this.stopPropagation) e.stopPropagation();
557
+ if (this.preventDefault) return false;
558
+ }
559
+ }
560
+ break;
561
+ }
562
+ this.#eventType = "up";
563
+ }
564
+ if (this.isDebug) {
565
+ var log = "directed: " + this.directed + ", start: " + f4f(this.startX) + ", " + f4f(this.startY) + " / shift: " + f4f(this.shiftX) + ", " + f4f(this.shiftY) + " / last: " + f4f(this.lastX) + ", " + f4f(this.lastY) + " / grab: " + f4f(this.grabX) + ", " + f4f(this.grabY);
566
+ console.log(e.type + " - " + log);
567
+ if (this.debugDisplay != null) this.debugDisplay.prepend(e.type + " - " + log + "<br />");
568
+ }
569
+ const clear = () => {
570
+ const grabX = this.#lastX - this.startX + this.#shiftX;
571
+ const grabY = this.#lastY - this.startY + this.#shiftY;
572
+ if (this.isDebug) {
573
+ var log = "directed: " + this.directed + ", start: " + f4f(this.startX) + ", " + f4f(this.startY) + " / shift: " + f4f(this.shiftX) + ", " + f4f(this.shiftY) + " / last: " + f4f(this.lastX) + ", " + f4f(this.lastY) + " / grab: " + f4f(grabX) + ", " + f4f(grabY);
574
+ console.log(e.type + " delayed - " + log);
575
+ if (this.debugDisplay != null) this.debugDisplay.prepend(e.type + " delayed - " + log + "<br />");
576
+ }
577
+ const handled = this.handled;
578
+ var onClearBound = null;
579
+ if (this.onUp != null) onClearBound = this.onUp(grabX, grabY, handled, canceled, this.directed);
580
+ this.#dropHandle();
581
+ if (onClearBound == null) this.#clearBound();
582
+ else if (onClearBound.delay == null) {
583
+ this.#clearBound();
584
+ if (onClearBound.callback != null) onClearBound.callback();
585
+ } else setTimeout(_ => {
586
+ this.#clearBound();
587
+ if (onClearBound.callback != null) onClearBound.callback();
588
+ }, onClearBound.delay);
589
+ if (this.isDebug) {
590
+ console.log("cleared" + (handled ? " with handled" : ""));
591
+ if (this.debugDisplay != null) this.debugDisplay.prepend("cleared<br/>");
592
+ }
593
+ //$(this.eventOrigin).off("click", null, this.#onClick);
594
+ //if (!handled && this.eventOrigin != null) this.eventOrigin.click();
595
+ this.#eventOrigin = null;
596
+ return handled;
597
+ };
598
+ var handled = true;
599
+ if (canceled) setTimeout(_ => { clear(); }, this.cancelDelay);
600
+ else handled = clear();
601
+ if (handled) {
602
+ if (this.preventUp) {
603
+ if (this.preventDefault) e.preventDefault();
604
+ if (this.stopPropagation) e.stopPropagation();
605
+ //if (!canceled && !handled && this.eventOrigin != null) this.eventOrigin.click();
606
+ if (this.preventDefault) return false;
607
+ }
608
+ }
609
+ break;
610
+
611
+ case "pointermove":
612
+ case "mousemove":
613
+ case "touchmove":
614
+ if (this.#bound != null) {
615
+ if (this.preventMove && this.stopPropagation) e.stopPropagation();
616
+ if (this.pointerType != pointerType) {
617
+ if (this.preventMove && this.preventDefault) {
618
+ e.preventDefault();
619
+ return false;
620
+ }
621
+ break;
622
+ }
623
+ this.#eventType = "move";
624
+ this.#lastX = screenX;
625
+ this.#lastY = screenY;
626
+ const allowedX = this.allowedDirectionX;
627
+ const allowedY = this.allowedDirectionY;
628
+ var grabX = 0;
629
+ var grabY = 0;
630
+ if (allowedX) grabX = screenX - this.startX + this.shiftX;
631
+ if (allowedY) grabY = screenY - this.startY + this.shiftY;
632
+ const moveX = this.moveX;
633
+ const moveY = this.moveY;
634
+ const exceedX = moveX > this.thresholdX;
635
+ const exceedY = moveY > this.thresholdY;
636
+ const strayedX = moveY > this.thresholdX * 2;
637
+ const strayedY = moveX > this.thresholdY * 2;
638
+ var handled = false;
639
+ var applyX = false;
640
+ var applyY = false;
641
+ var fixX = false;
642
+ var fixY = false;
643
+ var dropped = false;
644
+ switch (this.directionFix) {
645
+ case "both":
646
+ if (this.directed != null) {
647
+ handled = true;
648
+ switch (this.directed) {
649
+ case "horizontal":
650
+ applyX = true;
651
+ break;
652
+
653
+ case "vertical":
654
+ applyY = true;
655
+ break;
656
+ }
657
+ } else if (exceedX || exceedY) {
658
+ handled = true;
659
+ if (exceedX) {
660
+ applyX = true;
661
+ fixX = true;
662
+ } else if (exceedY) {
663
+ applyY = true;
664
+ fixY = true;
665
+ }
666
+ }
667
+ break;
668
+
669
+ case "horizontal":
670
+ if (this.directed == "horizontal") {
671
+ handled = true;
672
+ applyX = true;
673
+ } else if (this.dropStrayed && strayedX) {
674
+ dropped = true;
675
+ } else if (exceedX) {
676
+ handled = true;
677
+ applyX = true;
678
+ fixX = true;
679
+ }
680
+ break;
681
+
682
+ case "vertical":
683
+ if (this.directed == "vertical") {
684
+ handled = true;
685
+ applyY = true;
686
+ } else if (this.dropStrayed && strayedY) {
687
+ dropped = true;
688
+ } else if (exceedY) {
689
+ handled = true;
690
+ applyY = true;
691
+ fixY = true;
692
+ }
693
+ break;
694
+
695
+ case "neither":
696
+ if (allowedX || allowedY) {
697
+ handled = true;
698
+ if (allowedX) applyX = true;
699
+ if (allowedY) applyY = true;
700
+ } else if (!allowedX && !allowedY) {
701
+ dropped = true;
702
+ }
703
+ break;
704
+ }
705
+
706
+ const onSwipe = handled ? t1 : "";
707
+ if (this.$wind.attr(eds.onSwipe) != onSwipe) this.$wind.attr(eds.onSwipe, onSwipe);
708
+ if (handled) {
709
+ if (applyX) {
710
+ this.#grabX = grabX;
711
+ this.#$responseBound.css("--grab-x", grabX + "px");
712
+ }
713
+ if (applyY) {
714
+ this.#grabY = grabY;
715
+ this.#$responseBound.css("--grab-y", grabY + "px");
716
+ }
717
+ if (fixX) this.#directed = "horizontal";
718
+ if (fixY) this.#directed = "vertical";
719
+ }
720
+ if (this.isDebug) {
721
+ var log = "directed: " + this.directed + ", start: " + f4f(this.startX) + ", " + f4f(this.startY) + " / shift: " + f4f(this.shiftX) + ", " + f4f(this.shiftY) + " / last: " + f4f(this.lastX) + ", " + f4f(this.lastY) + " / grab: " + f4f(grabX) + ", " + f4f(grabY);
722
+ console.log(e.type + " - " + log);
723
+ if (this.debugDisplay != null) this.debugDisplay.prepend(e.type + " - " + log + "<br />");
724
+ }
725
+ if (this.onMove != null) this.onMove(grabX, grabY, handled, dropped, this.directed);
726
+ if (dropped) {
727
+ if (this.isDebug) console.log("dropped");
728
+ this.#dropHandle();
729
+ this.#clearBound();
730
+ //$(this.eventOrigin).off("click", null, this.#onClick);
731
+ //if (!handled && this.eventOrigin != null) this.eventOrigin.click();
732
+ this.#eventOrigin = null;
733
+ } else if (handled) {
734
+ if (this.isDebug) console.log("handled");
735
+ if (this.#grabMarker == null) this.#grabMarker = setTimeout(_ => {
736
+ if (this.handled && this.#$responseBound.attr(eds.onGrab) != t1) {
737
+ this.#$responseBound.attr(eds.onGrab, t1);
738
+ }
739
+ this.#grabMarker = null;
740
+ }, 0);
741
+ if (this.preventMove) {
742
+ if (this.preventDefault) e.preventDefault();
743
+ if (this.stopPropagation) e.stopPropagation();
744
+ if (this.preventDefault) return false;
745
+ }
746
+ } else {
747
+ if (this.isDebug) console.log("ignored");
748
+ this.#clearBound();
749
+ }
750
+ }
751
+ break;
752
+
753
+ }
754
+ }
755
+
756
+ #onHandle = (e) => {
757
+ if (this.handled) {
758
+ e.preventDefault();
759
+ e.stopPropagation();
760
+ return false;
761
+ }
762
+ }
763
+
764
+ #onClick = (e) => {
765
+ //e.preventDefault();
766
+ e.stopPropagation();
767
+ //return false;
768
+ }
769
+
770
+ #onBlock = (e) => {
771
+ e.preventDefault();
772
+ e.stopPropagation();
773
+ return false;
774
+ }
775
+
776
+ setElement(element = this.element) {
777
+ const $responseBound = this.#$responseBound;
778
+ const $outerBound = this.#$outerBound;
779
+ if (this.#bound != null) this.release();
780
+ if (element instanceof jQuery) {
781
+ this.#$bound = element;
782
+ this.#bound = element[0];
783
+ } else {
784
+ this.#bound = element;
785
+ this.#$bound = $(element);
786
+ }
787
+ this.#data = this.#bound.dataset;
788
+ this.#bound.swipeHandler = this;
789
+
790
+ this.#$bound.on(this.#triggers, null, this.#onEvent);
791
+ //this.#$bound.on(this.#handles, null, this.#onHandle);
792
+ this.#$bound.on("click", null, this.#onClick);
793
+ this.#$bound.css("user-select", "none");
794
+
795
+ this.#$responseBound = $responseBound != null ? $responseBound : this.#$bound;
796
+ if ($outerBound != null) this.setOuterBound($outerBound);
797
+
798
+ const $blockTarget = this.#$bound.find(uis.blockSwipe);
799
+ $blockTarget.on(this.#events, this.#onBlock);
800
+
801
+ return this;
802
+ }
803
+
804
+ }
805
+
806
+
807
+
808
+ class EstreDraggableHandler {
809
+
810
+ // enclosed property
811
+ #isEnabledTouch = f;
812
+
813
+ // open property
814
+ $bound;
815
+ bound;
816
+
817
+ draggableAxis = "vertical"; // "both", "horizontal", "vertical"
818
+ // currently supports only vertical
819
+
820
+ useTouchSupport = n; // null is auto
821
+
822
+
823
+ // getter and setter
824
+ get isTouchSupported() { return "ontouchstart" in window ||
825
+ navigator.maxTouchPoints > 0 ||
826
+ navigator.msMaxTouchPoints > 0 ||
827
+ window.DocumentTouch && document instanceof DocumentTouch; }
828
+
829
+
830
+ // instant methods
831
+ startTouch = _ => {};
832
+ getDragDistance = _ => {};
833
+ shouldMoveDraggingItem = _ => {};
834
+ performDragMove = _ => {};
835
+ startDragging = _ => {};
836
+ endDragging = _ => {};
837
+ clearGhost = _ => {};
838
+
839
+
840
+ constructor($bound, axis = "vertical", useTouchSupport = n) {
841
+ this.useTouchSupport = useTouchSupport;
842
+ if (useTouchSupport || (useTouchSupport == n && this.isTouchSupported && !isAndroid)) this.#isEnabledTouch = t;
843
+ this.draggableAxis = axis;
844
+ this.$bound = $bound;
845
+ this.bound = $bound[0];
846
+ for (const bound of $bound) bound.draggableHandler = this;
847
+ this.init();
848
+ }
849
+
850
+ release() {
851
+ $(document).off('touchstart.dragHandler');
852
+ this.endDragging();
853
+ for (const bound of this.$bound) delete bound.draggableHandler;
854
+
855
+ if (this.$blockingBound != null && this.eventBlocker != null) {
856
+ this.$blockingBound.off("click touchstart touchmove touchend touchcancel", this.eventBlocker);
857
+ this.$blockingBound = null;
858
+ this.eventBlocker = null;
859
+ }
860
+ }
861
+
862
+ init() {
863
+ const handler = this;
864
+
865
+ const $draggables = this.$bound.find(aiv("draggable", "true"));
866
+ const $containers = this.$bound.find(aiv("droppable", "true"));
867
+
868
+ let $topScrollerPad = this.$bound.find(aiv("scroller-pad", "top"));
869
+ let $bottomScrollerPad = this.$bound.find(aiv("scroller-pad", "bottom"));
870
+ if (this.draggableAxis == "both" || this.draggableAxis == "vertical") {
871
+ if ($topScrollerPad.length < 1) {
872
+ const topPad = doc.ce(div, n, n, n, { "scroller-pad": "top" });
873
+ this.$bound.append(topPad);
874
+ $topScrollerPad = this.$bound.find(aiv("scroller-pad", "top"));
875
+ }
876
+ if ($bottomScrollerPad.length < 1) {
877
+ const bottomPad = doc.ce(div, n, n, n, { "scroller-pad": "bottom" });
878
+ this.$bound.append(bottomPad);
879
+ $bottomScrollerPad = this.$bound.find(aiv("scroller-pad", "bottom"));
880
+ }
881
+ }
882
+
883
+ // Remove existing events
884
+ $draggables.off("dragstart dragend touchstart touchmove touchend touchcancel");
885
+ $containers.off("dragover dragleave drop touchmove touchend touchcancel");
886
+ $topScrollerPad.off("dragover dragleave drop touchmove touchend touchcancel");
887
+ $bottomScrollerPad.off("dragover dragleave drop touchmove touchend touchcancel");
888
+
889
+ const scrollDistance = 10;
890
+ const scrollTerminal = 16; // ~60fps
891
+ let topScrollInterval = n;
892
+ let bottomScrollInterval = n;
893
+
894
+ let draggingItem = n;
895
+ let isDragging = false;
896
+ let touchData = {
897
+ startX: 0,
898
+ startY: 0,
899
+ currentX: 0,
900
+ currentY: 0,
901
+ startTime: 0,
902
+ moved: false,
903
+ dragThreshold: 10 // Drag start threshold in pixels
904
+ };
905
+ let ghostElement = null;
906
+ let dragStartTimeout = null;
907
+ let lastDragPosition = { container: null, afterElement: null, timestamp: 0 };
908
+ let dragMoveThrottle = null;
909
+
910
+ // Helper function for touch start
911
+ this.startTouch = (element, touch) => {
912
+ touchData.startX = touch.clientX;
913
+ touchData.startY = touch.clientY;
914
+ touchData.currentX = touch.clientX;
915
+ touchData.currentY = touch.clientY;
916
+ touchData.startTime = Date.now();
917
+ touchData.moved = false;
918
+ draggingItem = element;
919
+ this.clearGhost();
920
+ };
921
+
922
+ // Calculate drag distance
923
+ this.getDragDistance = () => {
924
+ const deltaX = touchData.currentX - touchData.startX;
925
+ const deltaY = touchData.currentY - touchData.startY;
926
+ return Math.sqrt(deltaX * deltaX + deltaY * deltaY);
927
+ };
928
+
929
+ // Check if element position needs to be changed
930
+ this.shouldMoveDraggingItem = (targetContainer, afterElement) => {
931
+ const currentParent = draggingItem.parentNode;
932
+ const currentNext = draggingItem.nextSibling;
933
+
934
+ // Different container - always move
935
+ if (currentParent !== targetContainer) {
936
+ return true;
937
+ }
938
+
939
+ // Same container - check if position actually changes
940
+ if (afterElement === null) {
941
+ // Moving to end - only move if not already at end
942
+ return currentNext !== null;
943
+ } else {
944
+ // Moving before specific element - only move if not already before it
945
+ return currentNext !== afterElement;
946
+ }
947
+ };
948
+
949
+ // Perform DOM move with throttling to prevent excessive operations
950
+ this.performDragMove = (targetContainer, afterElement) => {
951
+ const now = Date.now();
952
+
953
+ // Check if this is the same position as last move (within 50ms)
954
+ if (lastDragPosition.container === targetContainer &&
955
+ lastDragPosition.afterElement === afterElement &&
956
+ (now - lastDragPosition.timestamp) < 50) {
957
+ return;
958
+ }
959
+
960
+ // Check if actual move is needed
961
+ if (!this.shouldMoveDraggingItem(targetContainer, afterElement)) {
962
+ return;
963
+ }
964
+
965
+ // Clear any pending throttled move
966
+ if (dragMoveThrottle) {
967
+ clearTimeout(dragMoveThrottle);
968
+ dragMoveThrottle = null;
969
+ }
970
+
971
+ // Throttle the move operation
972
+ dragMoveThrottle = setTimeout(() => {
973
+ if (draggingItem && targetContainer) {
974
+ try {
975
+ if (afterElement === null) {
976
+ targetContainer.appendChild(draggingItem);
977
+ } else {
978
+ targetContainer.insertBefore(draggingItem, afterElement);
979
+ }
980
+
981
+ // Update last position
982
+ lastDragPosition = {
983
+ container: targetContainer,
984
+ afterElement: afterElement,
985
+ timestamp: Date.now()
986
+ };
987
+ } catch (error) {
988
+ console.warn('Drag move operation failed:', error);
989
+ }
990
+ }
991
+ dragMoveThrottle = null;
992
+ }, 16); // ~60fps throttling
993
+ };
994
+
995
+ // Handle drag start
996
+ this.startDragging = (element) => {
997
+ if (isDragging) return;
998
+
999
+ isDragging = true;
1000
+ this.$bound.attr("data-dragging", t1);
1001
+ element.dataset.dragging = t1;
1002
+
1003
+ // Create ghost element for visual feedback
1004
+ ghostElement = element.cloneNode(true);
1005
+ ghostElement.classList.add('ghost-element');
1006
+ ghostElement.style.cssText = `
1007
+ position: fixed !important;
1008
+ z-index: 9999 !important;
1009
+ left: ${touchData.currentX - 200}px !important;
1010
+ top: ${touchData.currentY - 25}px !important;
1011
+ border-radius: 8px !important;
1012
+ box-shadow: 0 8px 16px var(--color-boundary-o20) !important;
1013
+ opacity: 0.8 !important;
1014
+ pointer-events: none !important;
1015
+ transform: rotate(5deg) scale(1.05) !important;
1016
+ transition-duration: 0s;
1017
+ `;
1018
+ document.body.appendChild(ghostElement);
1019
+ };
1020
+
1021
+ // Handle drag end
1022
+ this.endDragging = () => {
1023
+ // Clear drag timeout
1024
+ if (dragStartTimeout) {
1025
+ clearTimeout(dragStartTimeout);
1026
+ dragStartTimeout = null;
1027
+ }
1028
+
1029
+ // Clear drag move timeout
1030
+ if (dragMoveThrottle) {
1031
+ clearTimeout(dragMoveThrottle);
1032
+ dragMoveThrottle = null;
1033
+ }
1034
+
1035
+ // Clear scroll intervals
1036
+ if (topScrollInterval != n) {
1037
+ clearInterval(topScrollInterval);
1038
+ topScrollInterval = n;
1039
+ }
1040
+ if (bottomScrollInterval != n) {
1041
+ clearInterval(bottomScrollInterval);
1042
+ bottomScrollInterval = n;
1043
+ }
1044
+
1045
+ // Remove ghost element
1046
+ this.clearGhost();
1047
+
1048
+ // Remove all highlights
1049
+ setTimeout(_ => {
1050
+ $containers.removeAttr("data-highlight");
1051
+ }, 200);
1052
+
1053
+ if (!isDragging) return;
1054
+
1055
+ isDragging = false;
1056
+ this.$bound.removeAttr("data-dragging");
1057
+
1058
+ // Remove drag state
1059
+ if (draggingItem) {
1060
+ draggingItem.dataset.dragging = n;
1061
+ draggingItem = n;
1062
+ }
1063
+
1064
+ // Reset position tracking
1065
+ lastDragPosition = { container: null, afterElement: null, timestamp: 0 };
1066
+ };
1067
+
1068
+ this.clearGhost = () => {
1069
+ if (ghostElement && ghostElement.parentNode) {
1070
+ ghostElement.parentNode.removeChild(ghostElement);
1071
+ ghostElement = null;
1072
+ }
1073
+ $(doc.b).find(c.c + cls + "ghost-element").remove();
1074
+ };
1075
+
1076
+ // Touch start event
1077
+ if (this.#isEnabledTouch) $draggables.on({
1078
+ "touchstart": function (e) {
1079
+ const touch = e.originalEvent.touches[0];
1080
+ handler.startTouch(this, touch);
1081
+
1082
+ // Set delayed timeout for drag start
1083
+ dragStartTimeout = setTimeout(() => {
1084
+ if (draggingItem === this && !isDragging && !touchData.moved) {
1085
+ handler.startDragging(this);
1086
+ }
1087
+ }, 150); // Start drag mode after 150ms hold
1088
+ },
1089
+
1090
+ // Touch move event
1091
+ "touchmove": function (e) {
1092
+ const touch = e.originalEvent.touches[0];
1093
+ touchData.currentX = touch.clientX;
1094
+ touchData.currentY = touch.clientY;
1095
+
1096
+ // Detect movement
1097
+ if (!touchData.moved) {
1098
+ const distance = handler.getDragDistance();
1099
+ if (distance > touchData.dragThreshold) {
1100
+ if (isDragging) {
1101
+ touchData.moved = true;
1102
+ } else {
1103
+ handler.endDragging();
1104
+ }
1105
+ }
1106
+ }
1107
+
1108
+ // Do not process if not dragging
1109
+ if (!isDragging) return;
1110
+
1111
+ e.preventDefault();
1112
+ e.stopPropagation();
1113
+
1114
+ // Update ghost element position
1115
+ if (ghostElement) {
1116
+ ghostElement.style.left = (touchData.currentX - 200) + 'px';
1117
+ ghostElement.style.top = (touchData.currentY - 25) + 'px';
1118
+ }
1119
+
1120
+ // Find drop target from touch position
1121
+ const elementBelow = document.elementFromPoint(touchData.currentX, touchData.currentY);
1122
+ const container = elementBelow?.closest(aiv("droppable", "true"));
1123
+ const topScrollPad = elementBelow?.closest(aiv("scroller-pad", "top"));
1124
+ const bottomScrollPad = elementBelow?.closest(aiv("scroller-pad", "bottom"));
1125
+
1126
+ // Handle auto-scrolling for touch drag
1127
+ if (topScrollPad) {
1128
+ if (topScrollInterval == n) {
1129
+ topScrollInterval = setInterval(() => {
1130
+ handler.bound.scrollTop = handler.bound.scrollTop - scrollDistance;
1131
+ }, scrollTerminal);
1132
+ }
1133
+ } else if (bottomScrollPad) {
1134
+ if (bottomScrollInterval == n) {
1135
+ bottomScrollInterval = setInterval(() => {
1136
+ handler.bound.scrollTop = handler.bound.scrollTop + scrollDistance;
1137
+ }, scrollTerminal);
1138
+ }
1139
+ } else {
1140
+ // Stop scrolling when not on scroll pads
1141
+ if (topScrollInterval != n) {
1142
+ clearInterval(topScrollInterval);
1143
+ topScrollInterval = n;
1144
+ }
1145
+ if (bottomScrollInterval != n) {
1146
+ clearInterval(bottomScrollInterval);
1147
+ bottomScrollInterval = n;
1148
+ }
1149
+ }
1150
+
1151
+ if (container) {
1152
+ // Remove highlights from all containers
1153
+ $containers.removeAttr("data-highlight");
1154
+
1155
+ // Apply highlight to current container
1156
+ container.dataset.highlight = t1;
1157
+
1158
+ // Use improved drag move logic
1159
+ const afterElement = handler.getDragAfterElement(container, touchData.currentY);
1160
+ handler.performDragMove(container, afterElement);
1161
+ } else {
1162
+ // Remove highlight when outside containers
1163
+ $containers.removeAttr("data-highlight");
1164
+ }
1165
+ },
1166
+
1167
+ // Touch end event
1168
+ "touchend": function (e) {
1169
+ // Clear scroll intervals on touch end
1170
+ if (topScrollInterval != n) {
1171
+ clearInterval(topScrollInterval);
1172
+ topScrollInterval = n;
1173
+ }
1174
+ if (bottomScrollInterval != n) {
1175
+ clearInterval(bottomScrollInterval);
1176
+ bottomScrollInterval = n;
1177
+ }
1178
+
1179
+ // Treat as click if touch is short and movement is minimal
1180
+ const touchDuration = Date.now() - touchData.startTime;
1181
+ const dragDistance = handler.getDragDistance();
1182
+
1183
+ if (!isDragging && touchDuration < 200 && dragDistance < touchData.dragThreshold) {
1184
+ // Handle as regular click - allow default behavior
1185
+ handler.endDragging();
1186
+ return;
1187
+ }
1188
+
1189
+ e.preventDefault();
1190
+ e.stopPropagation();
1191
+
1192
+ handler.endDragging();
1193
+ },
1194
+
1195
+ // Touch cancel event
1196
+ "touchcancel": function (e) {
1197
+ // Clear scroll intervals on touch cancel
1198
+ if (topScrollInterval != n) {
1199
+ clearInterval(topScrollInterval);
1200
+ topScrollInterval = n;
1201
+ }
1202
+ if (bottomScrollInterval != n) {
1203
+ clearInterval(bottomScrollInterval);
1204
+ bottomScrollInterval = n;
1205
+ }
1206
+
1207
+ handler.endDragging();
1208
+ },
1209
+ });
1210
+
1211
+ // Desktop drag events
1212
+ $draggables.on({
1213
+ "dragstart": function (e) {
1214
+ draggingItem = this;
1215
+ isDragging = true;
1216
+ handler.$bound.attr("data-dragging", t1);
1217
+ postQueue(_ => { this.dataset.dragging = t1; });
1218
+ const event = e.originalEvent;
1219
+ event.dataTransfer.effectAllowed = "move";
1220
+ event.dataTransfer.setDragImage(this, e.offsetX, e.offsetY);
1221
+ },
1222
+
1223
+ "dragend": function (e) {
1224
+ draggingItem = n;
1225
+ isDragging = false;
1226
+ handler.$bound.removeAttr("data-dragging");
1227
+ this.dataset.dragging = n;
1228
+ $containers.removeAttr("data-highlight");
1229
+ },
1230
+ });
1231
+
1232
+ // Desktop container events
1233
+ $containers.on({
1234
+ "dragover": function (e) {
1235
+ e.preventDefault();
1236
+
1237
+ $containers.removeAttr("data-highlight");
1238
+ this.dataset.highlight = t1;
1239
+ const event = e.originalEvent;
1240
+ event.dataTransfer.dropEffect = "move";
1241
+
1242
+ // Use improved drag move logic
1243
+ const afterElement = handler.getDragAfterElement(this, e.clientY);
1244
+ if (handler.#isEnabledTouch) handler.performDragMove(this, afterElement);
1245
+ else if (afterElement === null) this.appendChild(draggingItem);
1246
+ else this.insertBefore(draggingItem, afterElement);
1247
+
1248
+ return false;
1249
+ },
1250
+
1251
+ "dragleave": function (e) {
1252
+ // Prevent dragleave from being triggered incorrectly by child elements
1253
+ const rect = this.getBoundingClientRect();
1254
+ const isOutside = e.clientX < rect.left || e.clientX > rect.right ||
1255
+ e.clientY < rect.top || e.clientY > rect.bottom;
1256
+
1257
+ if (isOutside) {
1258
+ delete this.dataset.highlight;
1259
+ }
1260
+ },
1261
+
1262
+ "drop": function (e) {
1263
+ e.preventDefault();
1264
+
1265
+ delete this.dataset.highlight;
1266
+
1267
+ return false;
1268
+ },
1269
+ });
1270
+
1271
+
1272
+ $topScrollerPad.on({
1273
+ "dragover": function (e) {
1274
+ e.preventDefault();
1275
+
1276
+ if (topScrollInterval == n) {
1277
+ topScrollInterval = setInterval(() => {
1278
+ handler.bound.scrollTop = handler.bound.scrollTop - scrollDistance;
1279
+ }, scrollTerminal);
1280
+ }
1281
+
1282
+ return false;
1283
+ },
1284
+
1285
+ "dragleave": function (e) {
1286
+ if (topScrollInterval != n) {
1287
+ clearInterval(topScrollInterval);
1288
+ topScrollInterval = n;
1289
+ }
1290
+ },
1291
+
1292
+ "drop": function (e) {
1293
+ e.preventDefault();
1294
+
1295
+ if (topScrollInterval != n) {
1296
+ clearInterval(topScrollInterval);
1297
+ topScrollInterval = n;
1298
+ }
1299
+
1300
+ return false;
1301
+ },
1302
+ });
1303
+ $bottomScrollerPad.on({
1304
+ "dragover": function (e) {
1305
+ e.preventDefault();
1306
+
1307
+ if (bottomScrollInterval == n) {
1308
+ bottomScrollInterval = setInterval(() => {
1309
+ handler.bound.scrollTop = handler.bound.scrollTop + scrollDistance;
1310
+ }, scrollTerminal);
1311
+ }
1312
+
1313
+ return false;
1314
+ },
1315
+
1316
+ "dragleave": function (e) {
1317
+ if (bottomScrollInterval != n) {
1318
+ clearInterval(bottomScrollInterval);
1319
+ bottomScrollInterval = n;
1320
+ }
1321
+ },
1322
+
1323
+ "drop": function (e) {
1324
+ e.preventDefault();
1325
+
1326
+ if (bottomScrollInterval != n) {
1327
+ clearInterval(bottomScrollInterval);
1328
+ bottomScrollInterval = n;
1329
+ }
1330
+
1331
+ return false;
1332
+ },
1333
+ });
1334
+
1335
+ // Global touch event to handle drag end during scroll
1336
+ $(document).on('touchstart.dragHandler', function(e) {
1337
+ if (isDragging && !$(e.target).closest(aiv("draggable", "true")).length) {
1338
+ handler.endDragging();
1339
+ }
1340
+ });
1341
+ }
1342
+
1343
+ getDragAfterElement(container, y) {
1344
+ const draggableElements = [...container.querySelectorAll(li + aiv("draggable", "true") + naiv("data-dragging", t1))];
1345
+ return draggableElements.reduce((closest, child) => {
1346
+ const box = child.getBoundingClientRect();
1347
+ const offset = y - box.top - box.height / 2;
1348
+ if (offset < 0 && offset > closest.offset) return { offset: offset, element: child };
1349
+ else return closest;
1350
+ }, { offset: Number.NEGATIVE_INFINITY }).element;
1351
+ }
1352
+
1353
+ blockEventLeaks($closestBound = this.$bound) {
1354
+ if (this.$blockingBound != null && this.eventBlocker != null) {
1355
+ this.$blockingBound.off("click touchstart touchmove touchend touchcancel", this.eventBlocker);
1356
+ }
1357
+
1358
+ this.eventBlocker = function (e) {
1359
+ // e.preventDefault();
1360
+ e.stopPropagation();
1361
+
1362
+ // return false;
1363
+ }
1364
+
1365
+ this.$blockingBound = $closestBound;
1366
+
1367
+ $closestBound.on("click touchstart touchmove touchend touchcancel", this.eventBlocker);
1368
+
1369
+ return this;
1370
+ }
1371
+ }
1372
+
1373
+
1374
+ // ======================================================================