@pantograph/sortable 2.0.0 → 2.1.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/src/Sortable.js DELETED
@@ -1,2099 +0,0 @@
1
- /**!
2
- * Sortable
3
- * @author RubaXa <trash@rubaxa.org>
4
- * @author owenm <owen23355@gmail.com>
5
- * @license MIT
6
- */
7
-
8
- import { version } from '../package.json';
9
-
10
- import { IE11OrLess, Edge, FireFox, Safari, IOS, ChromeForAndroid } from './BrowserInfo.js';
11
-
12
- import AnimationStateManager from './Animation.js';
13
-
14
- import PluginManager from './PluginManager.js';
15
-
16
- import dispatchEvent from './EventDispatcher.js';
17
-
18
- import {
19
- on,
20
- off,
21
- closest,
22
- toggleClass,
23
- css,
24
- matrix,
25
- find,
26
- getWindowScrollingElement,
27
- getRect,
28
- isScrolledPast,
29
- getChild,
30
- lastChild,
31
- index,
32
- getRelativeScrollOffset,
33
- extend,
34
- throttle,
35
- scrollBy,
36
- clone,
37
- expando,
38
- getChildContainingRectFromElement,
39
- getParentOrHost
40
- } from './utils.js';
41
-
42
-
43
- let pluginEvent = function(eventName, sortable, { evt: originalEvent, ...data } = {}) {
44
- PluginManager.pluginEvent.bind(Sortable)(eventName, sortable, {
45
- dragEl,
46
- originalDragEl,
47
- originalDragElDisplay,
48
- parentEl,
49
- ghostEl,
50
- rootEl,
51
- nextEl,
52
- lastDownEl,
53
- cloneEl,
54
- cloneHidden,
55
- dragStarted: moved,
56
- putSortable,
57
- activeSortable: Sortable.active,
58
- originalEvent,
59
-
60
- oldIndex,
61
- oldDraggableIndex,
62
- newIndex,
63
- newDraggableIndex,
64
-
65
- hideGhostForTarget: _hideGhostForTarget,
66
- unhideGhostForTarget: _unhideGhostForTarget,
67
-
68
-
69
- cloneNowHidden() {
70
- cloneHidden = true;
71
- },
72
- cloneNowShown() {
73
- cloneHidden = false;
74
- },
75
-
76
- dispatchSortableEvent(name) {
77
- _dispatchEvent({ sortable, name, originalEvent });
78
- },
79
-
80
- ...data
81
- });
82
- };
83
-
84
- function _dispatchEvent(info) {
85
- dispatchEvent({
86
- putSortable,
87
- cloneEl,
88
- targetEl: originalDragEl || dragEl,
89
- rootEl,
90
- oldIndex,
91
- oldDraggableIndex,
92
- newIndex,
93
- newDraggableIndex,
94
- ...info
95
- });
96
- }
97
-
98
-
99
- let dragEl,
100
- originalDragEl,
101
- originalDragElDisplay,
102
- parentEl,
103
- ghostEl,
104
- rootEl,
105
- nextEl,
106
- lastDownEl,
107
-
108
- cloneEl,
109
- cloneHidden,
110
-
111
- oldIndex,
112
- newIndex,
113
- oldDraggableIndex,
114
- newDraggableIndex,
115
-
116
- activeGroup,
117
- putSortable,
118
-
119
- awaitingDragStarted = false,
120
- ignoreNextClick = false,
121
- sortables = [],
122
-
123
- tapEvt,
124
- touchEvt,
125
- lastDx,
126
- lastDy,
127
- tapDistanceLeft,
128
- tapDistanceTop,
129
-
130
- moved,
131
-
132
- lastTarget,
133
- lastDirection,
134
- pastFirstInvertThresh = false,
135
- isCircumstantialInvert = false,
136
-
137
- targetMoveDistance,
138
-
139
- // For positioning ghost absolutely
140
- ghostRelativeParent,
141
- ghostRelativeParentInitialScroll = [], // (left, top)
142
-
143
- _silent = false,
144
- savedInputChecked = [];
145
-
146
- /** @const */
147
- const documentExists = typeof document !== 'undefined',
148
-
149
- PositionGhostAbsolutely = IOS,
150
-
151
- CSSFloatProperty = Edge || IE11OrLess ? 'cssFloat' : 'float',
152
-
153
- // This will not pass for IE9, because IE9 DnD only works on anchors
154
- supportDraggable = documentExists && !ChromeForAndroid && !IOS && ('draggable' in document.createElement('div')),
155
-
156
- supportCssPointerEvents = (function() {
157
- if (!documentExists) return;
158
- // false when <= IE11
159
- if (IE11OrLess) {
160
- return false;
161
- }
162
- let el = document.createElement('x');
163
- el.style.cssText = 'pointer-events:auto';
164
- return el.style.pointerEvents === 'auto';
165
- })(),
166
-
167
- _detectDirection = function(el, options) {
168
- let elCSS = css(el),
169
- elWidth = parseInt(elCSS.width)
170
- - parseInt(elCSS.paddingLeft)
171
- - parseInt(elCSS.paddingRight)
172
- - parseInt(elCSS.borderLeftWidth)
173
- - parseInt(elCSS.borderRightWidth),
174
- child1 = getChild(el, 0, options, false, originalDragEl),
175
- child2 = getChild(el, 1, options, false, originalDragEl),
176
- firstChildCSS = child1 && css(child1),
177
- secondChildCSS = child2 && css(child2),
178
- firstChildWidth = firstChildCSS && parseInt(firstChildCSS.marginLeft) + parseInt(firstChildCSS.marginRight) + getRect(child1).width,
179
- secondChildWidth = secondChildCSS && parseInt(secondChildCSS.marginLeft) + parseInt(secondChildCSS.marginRight) + getRect(child2).width;
180
-
181
- if (elCSS.display === 'flex') {
182
- return elCSS.flexDirection === 'column' || elCSS.flexDirection === 'column-reverse'
183
- ? 'vertical' : 'horizontal';
184
- }
185
-
186
- if (elCSS.display === 'grid') {
187
- return elCSS.gridTemplateColumns.split(' ').length <= 1 ? 'vertical' : 'horizontal';
188
- }
189
-
190
- if (child1 && firstChildCSS.float && firstChildCSS.float !== 'none') {
191
- let touchingSideChild2 = firstChildCSS.float === 'left' ? 'left' : 'right';
192
-
193
- return child2 && (secondChildCSS.clear === 'both' || secondChildCSS.clear === touchingSideChild2) ?
194
- 'vertical' : 'horizontal';
195
- }
196
-
197
- return (child1 &&
198
- (
199
- firstChildCSS.display === 'block' ||
200
- firstChildCSS.display === 'flex' ||
201
- firstChildCSS.display === 'table' ||
202
- firstChildCSS.display === 'grid' ||
203
- firstChildWidth >= elWidth &&
204
- elCSS[CSSFloatProperty] === 'none' ||
205
- child2 &&
206
- elCSS[CSSFloatProperty] === 'none' &&
207
- firstChildWidth + secondChildWidth > elWidth
208
- ) ?
209
- 'vertical' : 'horizontal'
210
- );
211
- },
212
-
213
- _dragElInRowColumn = function(dragRect, targetRect, vertical) {
214
- let dragElS1Opp = vertical ? dragRect.left : dragRect.top,
215
- dragElS2Opp = vertical ? dragRect.right : dragRect.bottom,
216
- dragElOppLength = vertical ? dragRect.width : dragRect.height,
217
- targetS1Opp = vertical ? targetRect.left : targetRect.top,
218
- targetS2Opp = vertical ? targetRect.right : targetRect.bottom,
219
- targetOppLength = vertical ? targetRect.width : targetRect.height;
220
-
221
- return (
222
- dragElS1Opp === targetS1Opp ||
223
- dragElS2Opp === targetS2Opp ||
224
- (dragElS1Opp + dragElOppLength / 2) === (targetS1Opp + targetOppLength / 2)
225
- );
226
- },
227
-
228
- /**
229
- * Detects first nearest empty sortable to X and Y position using emptyInsertThreshold.
230
- * @param {Number} x X position
231
- * @param {Number} y Y position
232
- * @return {HTMLElement} Element of the first found nearest Sortable
233
- */
234
- _detectNearestEmptySortable = function(x, y) {
235
- let ret;
236
- sortables.some((sortable) => {
237
- const threshold = sortable[expando].options.emptyInsertThreshold;
238
- if (!threshold || lastChild(sortable)) return;
239
-
240
- const rect = getRect(sortable),
241
- insideHorizontally = x >= (rect.left - threshold) && x <= (rect.right + threshold),
242
- insideVertically = y >= (rect.top - threshold) && y <= (rect.bottom + threshold);
243
-
244
- if (insideHorizontally && insideVertically) {
245
- return (ret = sortable);
246
- }
247
- });
248
- return ret;
249
- },
250
-
251
- _prepareGroup = function (options) {
252
- function toFn(value, pull) {
253
- return function(to, from, dragEl, evt) {
254
- let sameGroup = to.options.group.name &&
255
- from.options.group.name &&
256
- to.options.group.name === from.options.group.name;
257
-
258
- if (value == null && (pull || sameGroup)) {
259
- // Default pull value
260
- // Default pull and put value if same group
261
- return true;
262
- } else if (value == null || value === false) {
263
- return false;
264
- } else if (pull && value === 'clone') {
265
- return value;
266
- } else if (typeof value === 'function') {
267
- return toFn(value(to, from, dragEl, evt), pull)(to, from, dragEl, evt);
268
- } else {
269
- let otherGroup = (pull ? to : from).options.group.name;
270
-
271
- return (value === true ||
272
- (typeof value === 'string' && value === otherGroup) ||
273
- (value.join && value.indexOf(otherGroup) > -1));
274
- }
275
- };
276
- }
277
-
278
- let group = {};
279
- let originalGroup = options.group;
280
-
281
- if (!originalGroup || typeof originalGroup != 'object') {
282
- originalGroup = {name: originalGroup};
283
- }
284
-
285
- group.name = originalGroup.name;
286
- group.checkPull = toFn(originalGroup.pull, true);
287
- group.checkPut = toFn(originalGroup.put);
288
- group.revertClone = originalGroup.revertClone;
289
-
290
- options.group = group;
291
- },
292
-
293
- _hideGhostForTarget = function() {
294
- if (!supportCssPointerEvents && ghostEl) {
295
- css(ghostEl, 'display', 'none');
296
- }
297
- },
298
-
299
- _unhideGhostForTarget = function() {
300
- if (!supportCssPointerEvents && ghostEl) {
301
- css(ghostEl, 'display', '');
302
- }
303
- };
304
-
305
-
306
- // #1184 fix - Prevent click event on fallback if dragged but item not changed position
307
- if (documentExists && !ChromeForAndroid) {
308
- document.addEventListener('click', function(evt) {
309
- if (ignoreNextClick) {
310
- evt.preventDefault();
311
- evt.stopPropagation && evt.stopPropagation();
312
- evt.stopImmediatePropagation && evt.stopImmediatePropagation();
313
- ignoreNextClick = false;
314
- return false;
315
- }
316
- }, true);
317
- }
318
-
319
- let nearestEmptyInsertDetectEvent = function(evt) {
320
- if (dragEl) {
321
- evt = evt.touches ? evt.touches[0] : evt;
322
- let nearest = _detectNearestEmptySortable(evt.clientX, evt.clientY);
323
-
324
- if (nearest) {
325
- // Create imitation event
326
- let event = {};
327
- for (let i in evt) {
328
- if (evt.hasOwnProperty(i)) {
329
- event[i] = evt[i];
330
- }
331
- }
332
- event.target = event.rootEl = nearest;
333
- event.preventDefault = void 0;
334
- event.stopPropagation = void 0;
335
- nearest[expando]._onDragOver(event);
336
- }
337
- }
338
- };
339
-
340
-
341
- let _checkOutsideTargetEl = function(evt) {
342
- if (dragEl) {
343
- dragEl.parentNode[expando]._isOutsideThisEl(evt.target);
344
- }
345
- };
346
-
347
-
348
- /**
349
- * @class Sortable
350
- * @param {HTMLElement} el
351
- * @param {Object} [options]
352
- */
353
- function Sortable(el, options) {
354
- if (!(el && el.nodeType && el.nodeType === 1)) {
355
- throw `Sortable: \`el\` must be an HTMLElement, not ${ {}.toString.call(el) }`;
356
- }
357
-
358
- this.el = el; // root element
359
- this.options = options = Object.assign({}, options);
360
-
361
-
362
- // Export instance
363
- el[expando] = this;
364
-
365
- let defaults = {
366
- group: null,
367
- sort: true,
368
- disabled: false,
369
- store: null,
370
- handle: null,
371
- draggable: /^[uo]l$/i.test(el.nodeName) ? '>li' : '>*',
372
- swapThreshold: 1, // percentage; 0 <= x <= 1
373
- invertSwap: false, // invert always
374
- invertedSwapThreshold: null, // will be set to same as swapThreshold if default
375
- removeCloneOnHide: true,
376
- direction: function() {
377
- return _detectDirection(el, this.options);
378
- },
379
- ghostClass: 'sortable-ghost',
380
- chosenClass: 'sortable-chosen',
381
- dragClass: 'sortable-drag',
382
- ignore: 'a, img',
383
- filter: null,
384
- preventOnFilter: true,
385
- getGhostFallback: undefined,
386
- getPlaceholder: undefined,
387
- getPlaceholderOnMove: undefined,
388
- tree: false,
389
- treeItemLevelAttr: 'data-level',
390
- treeIndentThreshold: 10,
391
- animation: 0,
392
- easing: null,
393
- setData: function (dataTransfer, dragEl) {
394
- dataTransfer.setData('Text', dragEl.textContent);
395
- },
396
- dropBubble: false,
397
- dragoverBubble: false,
398
- dataIdAttr: 'data-id',
399
- delay: 0,
400
- delayOnTouchOnly: false,
401
- touchStartThreshold: (Number.parseInt ? Number : window).parseInt(window.devicePixelRatio, 10) || 1,
402
- forceFallback: false,
403
- fallbackClass: 'sortable-fallback',
404
- fallbackOnBody: false,
405
- fallbackTolerance: 0,
406
- fallbackOffset: {x: 0, y: 0},
407
- // Disabled on Safari: #1571; Enabled on Safari IOS: #2244
408
- supportPointer: Sortable.supportPointer !== false && ('PointerEvent' in window) && (!Safari || IOS),
409
- emptyInsertThreshold: 5
410
- };
411
-
412
- PluginManager.initializePlugins(this, el, defaults);
413
-
414
- // Set default options
415
- for (let name in defaults) {
416
- !(name in options) && (options[name] = defaults[name]);
417
- }
418
-
419
- _prepareGroup(options);
420
-
421
- // Bind all private methods
422
- for (let fn in this) {
423
- if (fn.charAt(0) === '_' && typeof this[fn] === 'function') {
424
- this[fn] = this[fn].bind(this);
425
- }
426
- }
427
-
428
- // Setup drag mode
429
- this.nativeDraggable = options.forceFallback ? false : supportDraggable;
430
-
431
- if (this.nativeDraggable) {
432
- // Touch start threshold cannot be greater than the native dragstart threshold
433
- this.options.touchStartThreshold = 1;
434
- }
435
-
436
- // Bind events
437
- if (options.supportPointer) {
438
- on(el, 'pointerdown', this._onTapStart);
439
- } else {
440
- on(el, 'mousedown', this._onTapStart);
441
- on(el, 'touchstart', this._onTapStart);
442
- }
443
-
444
- if (this.nativeDraggable) {
445
- on(el, 'dragover', this);
446
- on(el, 'dragenter', this);
447
- }
448
-
449
- sortables.push(this.el);
450
-
451
- // Restore sorting
452
- options.store && options.store.get && this.sort(options.store.get(this) || []);
453
-
454
- // Add animation state manager
455
- Object.assign(this, AnimationStateManager());
456
- }
457
-
458
- Sortable.prototype = /** @lends Sortable.prototype */ {
459
- constructor: Sortable,
460
-
461
- _isOutsideThisEl: function(target) {
462
- if (!this.el.contains(target) && target !== this.el) {
463
- lastTarget = null;
464
- }
465
- },
466
-
467
- _getDirection: function(evt, target) {
468
- return (typeof this.options.direction === 'function') ? this.options.direction.call(this, evt, target, originalDragEl || dragEl) : this.options.direction;
469
- },
470
-
471
- _onTapStart: function (/** Event|TouchEvent */evt) {
472
- if (!evt.cancelable) return;
473
- let _this = this,
474
- el = this.el,
475
- options = this.options,
476
- preventOnFilter = options.preventOnFilter,
477
- type = evt.type,
478
- touch = (evt.touches && evt.touches[0]) || (evt.pointerType && evt.pointerType === 'touch' && evt),
479
- target = (touch || evt).target,
480
- originalTarget = evt.target.shadowRoot && ((evt.path && evt.path[0]) || (evt.composedPath && evt.composedPath()[0])) || target,
481
- filter = options.filter;
482
-
483
- _saveInputCheckedState(el);
484
-
485
-
486
- // Don't trigger start event when an element is been dragged, otherwise the evt.oldindex always wrong when set option.group.
487
- if (dragEl) {
488
- return;
489
- }
490
-
491
- if (/mousedown|pointerdown/.test(type) && evt.button !== 0 || options.disabled) {
492
- return; // only left button and enabled
493
- }
494
-
495
- // cancel dnd if original target is content editable
496
- if (originalTarget.isContentEditable) {
497
- return;
498
- }
499
-
500
- // Safari ignores further event handling after mousedown
501
- if (!this.nativeDraggable && Safari && target && target.tagName.toUpperCase() === 'SELECT') {
502
- return;
503
- }
504
-
505
- target = closest(target, options.draggable, el, false, originalDragEl);
506
-
507
-
508
- if (target && target.animated) {
509
- return;
510
- }
511
-
512
- if (lastDownEl === target) {
513
- // Ignoring duplicate `down`
514
- return;
515
- }
516
-
517
- // Get the index of the dragged element within its parent
518
- oldIndex = index(target, undefined, originalDragEl);
519
- oldDraggableIndex = index(target, options.draggable, originalDragEl);
520
-
521
- // Check filter
522
- if (typeof filter === 'function') {
523
- if (filter.call(this, evt, target, this)) {
524
- _dispatchEvent({
525
- sortable: _this,
526
- rootEl: originalTarget,
527
- name: 'filter',
528
- targetEl: target,
529
- toEl: el,
530
- fromEl: el
531
- });
532
- pluginEvent('filter', _this, { evt });
533
- preventOnFilter && evt.preventDefault();
534
- return; // cancel dnd
535
- }
536
- }
537
- else if (filter) {
538
- filter = filter.split(',').some(function (criteria) {
539
- criteria = closest(originalTarget, criteria.trim(), el, false, originalDragEl);
540
-
541
- if (criteria) {
542
- _dispatchEvent({
543
- sortable: _this,
544
- rootEl: criteria,
545
- name: 'filter',
546
- targetEl: target,
547
- fromEl: el,
548
- toEl: el
549
- });
550
- pluginEvent('filter', _this, { evt });
551
- return true;
552
- }
553
- });
554
-
555
- if (filter) {
556
- preventOnFilter && evt.preventDefault();
557
- return; // cancel dnd
558
- }
559
- }
560
-
561
- if (options.handle && !closest(originalTarget, options.handle, el, false, originalDragEl)) {
562
- return;
563
- }
564
-
565
- // Prepare `dragstart`
566
- this._prepareDragStart(evt, touch, target);
567
- },
568
-
569
- _prepareDragStart: function (/** Event */evt, /** Touch */touch, /** HTMLElement */target) {
570
- let _this = this,
571
- el = _this.el,
572
- options = _this.options,
573
- ownerDocument = el.ownerDocument,
574
- dragStartFn;
575
-
576
- if (target && !dragEl && (target.parentNode === el)) {
577
- let dragRect = getRect(target);
578
- rootEl = el;
579
- dragEl = target;
580
- parentEl = dragEl.parentNode;
581
- nextEl = dragEl.nextSibling;
582
- lastDownEl = target;
583
- activeGroup = options.group;
584
-
585
- Sortable.dragged = dragEl;
586
-
587
- tapEvt = {
588
- target: dragEl,
589
- clientX: (touch || evt).clientX,
590
- clientY: (touch || evt).clientY
591
- };
592
-
593
- tapDistanceLeft = tapEvt.clientX - dragRect.left;
594
- tapDistanceTop = tapEvt.clientY - dragRect.top;
595
-
596
- this._lastX = (touch || evt).clientX;
597
- this._lastY = (touch || evt).clientY;
598
-
599
- dragEl.style['will-change'] = 'all';
600
-
601
- dragStartFn = function () {
602
- pluginEvent('delayEnded', _this, { evt });
603
- if (Sortable.eventCanceled) {
604
- _this._onDrop();
605
- return;
606
- }
607
- // Delayed drag has been triggered
608
- // we can re-enable the events: touchmove/mousemove
609
- _this._disableDelayedDragEvents();
610
-
611
- if (!FireFox && _this.nativeDraggable) {
612
- dragEl.draggable = true;
613
- }
614
-
615
- // Bind the events: dragstart/dragend
616
- _this._triggerDragStart(evt, touch);
617
-
618
- // Drag start event
619
- _dispatchEvent({
620
- sortable: _this,
621
- name: 'choose',
622
- originalEvent: evt
623
- });
624
-
625
- // Chosen item
626
- toggleClass(dragEl, options.chosenClass, true);
627
- };
628
-
629
- // Disable "draggable"
630
- options.ignore.split(',').forEach(function (criteria) {
631
- find(dragEl, criteria.trim(), _disableDraggable);
632
- });
633
-
634
- on(ownerDocument, 'dragover', nearestEmptyInsertDetectEvent);
635
- on(ownerDocument, 'mousemove', nearestEmptyInsertDetectEvent);
636
- on(ownerDocument, 'touchmove', nearestEmptyInsertDetectEvent);
637
-
638
- if (options.supportPointer) {
639
- on(ownerDocument, 'pointerup', _this._onDrop);
640
- // Native D&D triggers pointercancel
641
- !this.nativeDraggable && on(ownerDocument, 'pointercancel', _this._onDrop);
642
- } else {
643
- on(ownerDocument, 'mouseup', _this._onDrop);
644
- on(ownerDocument, 'touchend', _this._onDrop);
645
- on(ownerDocument, 'touchcancel', _this._onDrop);
646
- }
647
-
648
- // Make dragEl draggable (must be before delay for FireFox)
649
- if (FireFox && this.nativeDraggable) {
650
- this.options.touchStartThreshold = 4;
651
- dragEl.draggable = true;
652
- }
653
-
654
- pluginEvent('delayStart', this, { evt });
655
-
656
- // Delay is impossible for native DnD in Edge or IE
657
- if (options.delay && (!options.delayOnTouchOnly || touch) && (!this.nativeDraggable || !(Edge || IE11OrLess))) {
658
- if (Sortable.eventCanceled) {
659
- this._onDrop();
660
- return;
661
- }
662
- // If the user moves the pointer or let go the click or touch
663
- // before the delay has been reached:
664
- // disable the delayed drag
665
- if (options.supportPointer) {
666
- on(ownerDocument, 'pointerup', _this._disableDelayedDrag);
667
- on(ownerDocument, 'pointercancel', _this._disableDelayedDrag);
668
- } else {
669
- on(ownerDocument, 'mouseup', _this._disableDelayedDrag);
670
- on(ownerDocument, 'touchend', _this._disableDelayedDrag);
671
- on(ownerDocument, 'touchcancel', _this._disableDelayedDrag);
672
- }
673
- on(ownerDocument, 'mousemove', _this._delayedDragTouchMoveHandler);
674
- on(ownerDocument, 'touchmove', _this._delayedDragTouchMoveHandler);
675
- options.supportPointer && on(ownerDocument, 'pointermove', _this._delayedDragTouchMoveHandler);
676
-
677
- _this._dragStartTimer = setTimeout(dragStartFn, options.delay);
678
- } else {
679
- dragStartFn();
680
- }
681
- }
682
- },
683
-
684
- _delayedDragTouchMoveHandler: function (/** TouchEvent|PointerEvent **/e) {
685
- let touch = e.touches ? e.touches[0] : e;
686
- if (Math.max(Math.abs(touch.clientX - this._lastX), Math.abs(touch.clientY - this._lastY))
687
- >= Math.floor(this.options.touchStartThreshold / (this.nativeDraggable && window.devicePixelRatio || 1))
688
- ) {
689
- this._disableDelayedDrag();
690
- }
691
- },
692
-
693
- _disableDelayedDrag: function () {
694
- dragEl && _disableDraggable(dragEl);
695
- clearTimeout(this._dragStartTimer);
696
-
697
- this._disableDelayedDragEvents();
698
- },
699
-
700
- _disableDelayedDragEvents: function () {
701
- let ownerDocument = this.el.ownerDocument;
702
- off(ownerDocument, 'mouseup', this._disableDelayedDrag);
703
- off(ownerDocument, 'touchend', this._disableDelayedDrag);
704
- off(ownerDocument, 'touchcancel', this._disableDelayedDrag);
705
- off(ownerDocument, 'pointerup', this._disableDelayedDrag);
706
- off(ownerDocument, 'pointercancel', this._disableDelayedDrag);
707
- off(ownerDocument, 'mousemove', this._delayedDragTouchMoveHandler);
708
- off(ownerDocument, 'touchmove', this._delayedDragTouchMoveHandler);
709
- off(ownerDocument, 'pointermove', this._delayedDragTouchMoveHandler);
710
- },
711
-
712
- _triggerDragStart: function (/** Event */evt, /** Touch */touch) {
713
- touch = touch || (evt.pointerType == 'touch' && evt);
714
-
715
- if (!this.nativeDraggable || touch) {
716
- if (this.options.supportPointer) {
717
- on(document, 'pointermove', this._onTouchMove);
718
- } else if (touch) {
719
- on(document, 'touchmove', this._onTouchMove);
720
- } else {
721
- on(document, 'mousemove', this._onTouchMove);
722
- }
723
- } else {
724
- on(dragEl, 'dragend', this);
725
- on(rootEl, 'dragstart', this._onDragStart);
726
- }
727
-
728
- try {
729
-
730
- if (document.selection) {
731
- _nextTick(() => {
732
- document.selection.empty();
733
- });
734
- } else {
735
- window.getSelection().removeAllRanges();
736
- }
737
- } catch (err) {
738
- }
739
- },
740
-
741
- _getPlaceholder: function () {
742
- if (typeof this.options.getPlaceholder === 'function'){
743
- originalDragEl = dragEl;
744
- originalDragElDisplay = dragEl.style.display;
745
- dragEl = this.options.getPlaceholder(dragEl);
746
- if (dragEl && originalDragEl) {
747
- originalDragEl.after(dragEl);
748
- css(originalDragEl, 'display', 'none');
749
- css(originalDragEl, 'transform', '');
750
- toggleClass(originalDragEl, this.options.dragClass, false);
751
- toggleClass(originalDragEl, this.options.ghostClass, false);
752
- toggleClass(originalDragEl, this.options.chosenClass, false);
753
- }
754
- pluginEvent('getPlaceholder', this);
755
- }
756
- },
757
-
758
- _removeClonedSelectedItem: function () {
759
- if (typeof this.options.getPlaceholder === 'function' || typeof this.options.getPlaceholderOnMove === 'function') {
760
- if (dragEl && originalDragEl) {
761
- css(originalDragEl, 'display', originalDragElDisplay || '');
762
- dragEl.remove();
763
- }
764
- }
765
- },
766
-
767
- _replacePlaceholder: (newDragEl) => {
768
- const parent = dragEl.parentNode;
769
- if (parent) {
770
- parent.replaceChild(newDragEl, dragEl);
771
- } else {
772
- dragEl.remove();
773
- }
774
- dragEl = newDragEl;
775
- },
776
-
777
-
778
- _getPlaceholderOnMove(el, dragRect, target, targetRect, evt, after) {
779
- if (typeof this.options.getPlaceholderOnMove === 'function') {
780
- const newPlaceholder = this.options.getPlaceholderOnMove(getMoveEvent(rootEl, el, originalDragEl || dragEl, dragRect, target, targetRect, evt, after));
781
- if (newPlaceholder) {
782
- this._replacePlaceholder(newPlaceholder);
783
- }
784
- }
785
- },
786
-
787
- _dragStarted: function (fallback, evt) {
788
- awaitingDragStarted = false;
789
- if (rootEl && dragEl) {
790
- this._getPlaceholder();
791
- pluginEvent('dragStarted', this, { evt });
792
-
793
- if (this.nativeDraggable) {
794
- on(document, 'dragover', _checkOutsideTargetEl);
795
- }
796
- let options = this.options;
797
-
798
- // Apply effect
799
- !fallback && toggleClass(dragEl, options.dragClass, false);
800
- toggleClass(dragEl, options.ghostClass, true);
801
-
802
- Sortable.active = this;
803
-
804
- fallback && this._appendGhost();
805
-
806
- // Drag start event
807
- _dispatchEvent({
808
- sortable: this,
809
- name: 'start',
810
- originalEvent: evt
811
- });
812
- } else {
813
- this._nulling();
814
- }
815
- },
816
-
817
- _emulateDragOver: function () {
818
- if (touchEvt) {
819
- this._lastX = touchEvt.clientX;
820
- this._lastY = touchEvt.clientY;
821
-
822
- _hideGhostForTarget();
823
-
824
- let target = document.elementFromPoint(touchEvt.clientX, touchEvt.clientY);
825
- let parent = target;
826
-
827
- while (target && target.shadowRoot) {
828
- target = target.shadowRoot.elementFromPoint(touchEvt.clientX, touchEvt.clientY);
829
- if (target === parent) break;
830
- parent = target;
831
- }
832
-
833
- dragEl.parentNode[expando]._isOutsideThisEl(target);
834
-
835
- if (parent) {
836
- do {
837
- if (parent[expando]) {
838
- let inserted;
839
-
840
- inserted = parent[expando]._onDragOver({
841
- clientX: touchEvt.clientX,
842
- clientY: touchEvt.clientY,
843
- target: target,
844
- rootEl: parent
845
- });
846
-
847
- if (inserted && !this.options.dragoverBubble) {
848
- break;
849
- }
850
- }
851
-
852
- target = parent; // store last element
853
- }
854
- /* jshint boss:true */
855
- while (parent = getParentOrHost(parent));
856
- }
857
-
858
- _unhideGhostForTarget();
859
- }
860
- },
861
-
862
-
863
- _onTouchMove: function (/**TouchEvent*/evt) {
864
- if (tapEvt) {
865
- let options = this.options,
866
- fallbackTolerance = options.fallbackTolerance,
867
- fallbackOffset = options.fallbackOffset,
868
- touch = evt.touches ? evt.touches[0] : evt,
869
- ghostMatrix = ghostEl && matrix(ghostEl, true),
870
- scaleX = ghostEl && ghostMatrix && ghostMatrix.a,
871
- scaleY = ghostEl && ghostMatrix && ghostMatrix.d,
872
- relativeScrollOffset = PositionGhostAbsolutely && ghostRelativeParent && getRelativeScrollOffset(ghostRelativeParent),
873
- dx = ((touch.clientX - tapEvt.clientX)
874
- + fallbackOffset.x) / (scaleX || 1)
875
- + (relativeScrollOffset ? (relativeScrollOffset[0] - ghostRelativeParentInitialScroll[0]) : 0) / (scaleX || 1),
876
- dy = ((touch.clientY - tapEvt.clientY)
877
- + fallbackOffset.y) / (scaleY || 1)
878
- + (relativeScrollOffset ? (relativeScrollOffset[1] - ghostRelativeParentInitialScroll[1]) : 0) / (scaleY || 1);
879
-
880
- // only set the status to dragging, when we are actually dragging
881
- if (!Sortable.active && !awaitingDragStarted) {
882
- if (fallbackTolerance &&
883
- Math.max(Math.abs(touch.clientX - this._lastX), Math.abs(touch.clientY - this._lastY)) < fallbackTolerance
884
- ) {
885
- return;
886
- }
887
- this._onDragStart(evt, true);
888
- }
889
-
890
- if (ghostEl) {
891
- if (ghostMatrix) {
892
- ghostMatrix.e += dx - (lastDx || 0);
893
- ghostMatrix.f += dy - (lastDy || 0);
894
- } else {
895
- ghostMatrix = {
896
- a: 1,
897
- b: 0,
898
- c: 0,
899
- d: 1,
900
- e: dx,
901
- f: dy
902
- };
903
- }
904
-
905
- let cssMatrix = `matrix(${ghostMatrix.a},${ghostMatrix.b},${ghostMatrix.c},${ghostMatrix.d},${ghostMatrix.e},${ghostMatrix.f})`;
906
-
907
- css(ghostEl, 'webkitTransform', cssMatrix);
908
- css(ghostEl, 'mozTransform', cssMatrix);
909
- css(ghostEl, 'msTransform', cssMatrix);
910
- css(ghostEl, 'transform', cssMatrix);
911
-
912
- lastDx = dx;
913
- lastDy = dy;
914
-
915
- touchEvt = touch;
916
- }
917
-
918
- evt.cancelable && evt.preventDefault();
919
- }
920
- },
921
-
922
- _appendGhost: function () {
923
- // Bug if using scale(): https://stackoverflow.com/questions/2637058
924
- // Not being adjusted for
925
- if (!ghostEl) {
926
- let container = this.options.fallbackOnBody ? document.body : rootEl,
927
- rect = getRect(dragEl, true, PositionGhostAbsolutely, true, container),
928
- options = this.options;
929
-
930
- // Position absolutely
931
- if (PositionGhostAbsolutely) {
932
- // Get relatively positioned parent
933
- ghostRelativeParent = container;
934
-
935
- while (
936
- css(ghostRelativeParent, 'position') === 'static' &&
937
- css(ghostRelativeParent, 'transform') === 'none' &&
938
- ghostRelativeParent !== document
939
- ) {
940
- ghostRelativeParent = ghostRelativeParent.parentNode;
941
- }
942
-
943
- if (ghostRelativeParent !== document.body && ghostRelativeParent !== document.documentElement) {
944
- if (ghostRelativeParent === document) ghostRelativeParent = getWindowScrollingElement();
945
-
946
- rect.top += ghostRelativeParent.scrollTop;
947
- rect.left += ghostRelativeParent.scrollLeft;
948
- } else {
949
- ghostRelativeParent = getWindowScrollingElement();
950
- }
951
- ghostRelativeParentInitialScroll = getRelativeScrollOffset(ghostRelativeParent);
952
- }
953
-
954
-
955
- ghostEl = this.options.getGhostFallback && typeof this.options.getGhostFallback === 'function' ? this.options.getGhostFallback(dragEl) :dragEl.cloneNode(true);
956
-
957
- toggleClass(ghostEl, options.ghostClass, false);
958
- toggleClass(ghostEl, options.fallbackClass, true);
959
- toggleClass(ghostEl, options.dragClass, true);
960
-
961
- css(ghostEl, 'transition', '');
962
- css(ghostEl, 'transform', '');
963
-
964
- css(ghostEl, 'box-sizing', 'border-box');
965
- css(ghostEl, 'margin', 0);
966
- css(ghostEl, 'top', rect.top);
967
- css(ghostEl, 'left', rect.left);
968
- css(ghostEl, 'width', rect.width);
969
- css(ghostEl, 'height', rect.height);
970
- css(ghostEl, 'opacity', '0.8');
971
- css(ghostEl, 'position', (PositionGhostAbsolutely ? 'absolute' : 'fixed'));
972
- css(ghostEl, 'zIndex', '100000');
973
- css(ghostEl, 'pointerEvents', 'none');
974
-
975
-
976
- Sortable.ghost = ghostEl;
977
-
978
- container.appendChild(ghostEl);
979
-
980
- // Set transform-origin
981
- css(ghostEl, 'transform-origin', (tapDistanceLeft / parseInt(ghostEl.style.width) * 100) + '% ' + (tapDistanceTop / parseInt(ghostEl.style.height) * 100) + '%');
982
- }
983
- },
984
-
985
- _onDragStart: function (/**Event*/evt, /**boolean*/fallback) {
986
- let _this = this;
987
- let dataTransfer = evt.dataTransfer;
988
- let options = _this.options;
989
-
990
- pluginEvent('dragStart', this, { evt });
991
- if (Sortable.eventCanceled) {
992
- this._onDrop();
993
- return;
994
- }
995
-
996
- pluginEvent('setupClone', this);
997
- if (!Sortable.eventCanceled) {
998
- cloneEl = clone(dragEl);
999
- cloneEl.removeAttribute("id");
1000
- cloneEl.draggable = false;
1001
- cloneEl.style['will-change'] = '';
1002
-
1003
- this._hideClone();
1004
-
1005
- toggleClass(cloneEl, this.options.chosenClass, false);
1006
- Sortable.clone = cloneEl;
1007
- }
1008
-
1009
-
1010
- // #1143: IFrame support workaround
1011
- _this.cloneId = _nextTick(function() {
1012
- pluginEvent('clone', _this);
1013
- if (Sortable.eventCanceled) return;
1014
-
1015
- if (!_this.options.removeCloneOnHide) {
1016
- rootEl.insertBefore(cloneEl, dragEl);
1017
- }
1018
- _this._hideClone();
1019
-
1020
- _dispatchEvent({
1021
- sortable: _this,
1022
- name: 'clone'
1023
- });
1024
- });
1025
-
1026
-
1027
- !fallback && toggleClass(dragEl, options.dragClass, true);
1028
-
1029
- // Set proper drop events
1030
- if (fallback) {
1031
- ignoreNextClick = true;
1032
- _this._loopId = setInterval(_this._emulateDragOver, 50);
1033
- } else {
1034
- // Undo what was set in _prepareDragStart before drag started
1035
- off(document, 'mouseup', _this._onDrop);
1036
- off(document, 'touchend', _this._onDrop);
1037
- off(document, 'touchcancel', _this._onDrop);
1038
-
1039
- if (dataTransfer) {
1040
- dataTransfer.effectAllowed = 'move';
1041
- options.setData && options.setData.call(_this, dataTransfer, dragEl);
1042
- }
1043
-
1044
- on(document, 'drop', _this);
1045
-
1046
- // #1276 fix:
1047
- css(dragEl, 'transform', 'translateZ(0)');
1048
- }
1049
-
1050
- awaitingDragStarted = true;
1051
-
1052
- _this._dragStartId = _nextTick(_this._dragStarted.bind(_this, fallback, evt));
1053
- on(document, 'selectstart', _this);
1054
-
1055
- moved = true;
1056
-
1057
- window.getSelection().removeAllRanges();
1058
-
1059
- if (Safari) {
1060
- css(document.body, 'user-select', 'none');
1061
- }
1062
- },
1063
-
1064
- // Returns true - if no further action is needed (either inserted or another condition)
1065
- _onDragOver: function (/**Event*/evt) {
1066
- let el = this.el,
1067
- target = evt.target,
1068
- dragRect,
1069
- targetRect,
1070
- revert,
1071
- options = this.options,
1072
- group = options.group,
1073
- activeSortable = Sortable.active,
1074
- isOwner = (activeGroup === group),
1075
- canSort = options.sort,
1076
- fromSortable = (putSortable || activeSortable),
1077
- vertical,
1078
- _this = this,
1079
- completedFired = false;
1080
-
1081
- if (_silent) return;
1082
-
1083
- function dragOverEvent(name, extra) {
1084
- pluginEvent(name, _this, {
1085
- evt,
1086
- isOwner,
1087
- axis: vertical ? 'vertical' : 'horizontal',
1088
- revert,
1089
- dragRect,
1090
- targetRect,
1091
- canSort,
1092
- fromSortable,
1093
- target,
1094
- completed,
1095
- onMove(target, after) {
1096
- return onMove(rootEl, el, originalDragEl || dragEl, dragRect, target, getRect(target), evt, after);
1097
- },
1098
- changed,
1099
- ...extra
1100
- });
1101
- }
1102
-
1103
- // Capture animation state
1104
- function capture() {
1105
- dragOverEvent('dragOverAnimationCapture');
1106
-
1107
- _this.captureAnimationState();
1108
- if (_this !== fromSortable) {
1109
- fromSortable.captureAnimationState();
1110
- }
1111
- }
1112
-
1113
- // Return invocation when dragEl is inserted (or completed)
1114
- function completed(insertion) {
1115
- dragOverEvent('dragOverCompleted', { insertion });
1116
-
1117
- if (insertion) {
1118
- // Clones must be hidden before folding animation to capture dragRectAbsolute properly
1119
- if (isOwner) {
1120
- activeSortable._hideClone();
1121
- } else {
1122
- activeSortable._showClone(_this);
1123
- }
1124
-
1125
- if (_this !== fromSortable) {
1126
- // Set ghost class to new sortable's ghost class
1127
- toggleClass(dragEl, putSortable ? putSortable.options.ghostClass : activeSortable.options.ghostClass, false);
1128
- toggleClass(dragEl, options.ghostClass, true);
1129
- }
1130
-
1131
- if (putSortable !== _this && _this !== Sortable.active) {
1132
- putSortable = _this;
1133
- } else if (_this === Sortable.active && putSortable) {
1134
- putSortable = null;
1135
- }
1136
-
1137
- // Animation
1138
- if (fromSortable === _this) {
1139
- _this._ignoreWhileAnimating = target;
1140
- }
1141
- _this.animateAll(function() {
1142
- dragOverEvent('dragOverAnimationComplete');
1143
- _this._ignoreWhileAnimating = null;
1144
- });
1145
- if (_this !== fromSortable) {
1146
- fromSortable.animateAll();
1147
- fromSortable._ignoreWhileAnimating = null;
1148
- }
1149
- }
1150
-
1151
-
1152
- // Null lastTarget if it is not inside a previously swapped element
1153
- if ((target === dragEl && !dragEl.animated) || (target === el && !target.animated)) {
1154
- lastTarget = null;
1155
- }
1156
-
1157
- // no bubbling and not fallback
1158
- if (!options.dragoverBubble && !evt.rootEl && target !== document) {
1159
- dragEl.parentNode[expando]._isOutsideThisEl(evt.target);
1160
-
1161
- // Do not detect for empty insert if already inserted
1162
- !insertion && nearestEmptyInsertDetectEvent(evt);
1163
- }
1164
-
1165
- !options.dragoverBubble && evt.stopPropagation && evt.stopPropagation();
1166
-
1167
- return (completedFired = true);
1168
- }
1169
-
1170
- // Call when dragEl has been inserted
1171
- function changed() {
1172
- newIndex = index(dragEl, undefined, originalDragEl);
1173
- newDraggableIndex = index(dragEl, options.draggable, originalDragEl);
1174
- _dispatchEvent({
1175
- sortable: _this,
1176
- name: 'change',
1177
- toEl: el,
1178
- newIndex,
1179
- newDraggableIndex,
1180
- originalEvent: evt
1181
- });
1182
- }
1183
-
1184
-
1185
- if (evt.preventDefault !== void 0) {
1186
- evt.cancelable && evt.preventDefault();
1187
- }
1188
-
1189
-
1190
- target = closest(target, options.draggable, el, true, originalDragEl);
1191
-
1192
- dragOverEvent('dragOver');
1193
- if (Sortable.eventCanceled) return completedFired;
1194
-
1195
- if (
1196
- dragEl.contains(evt.target) ||
1197
- target.animated && target.animatingX && target.animatingY ||
1198
- _this._ignoreWhileAnimating === target
1199
- ) {
1200
- return completed(false);
1201
- }
1202
-
1203
- ignoreNextClick = false;
1204
-
1205
- if (activeSortable && !options.disabled &&
1206
- (isOwner
1207
- ? canSort || (revert = parentEl !== rootEl) // Reverting item into the original list
1208
- : (
1209
- putSortable === this ||
1210
- (
1211
- (this.lastPutMode = activeGroup.checkPull(this, activeSortable, originalDragEl || dragEl, evt)) &&
1212
- group.checkPut(this, activeSortable, originalDragEl || dragEl, evt)
1213
- )
1214
- )
1215
- )
1216
- ) {
1217
- vertical = this._getDirection(evt, target) === 'vertical';
1218
-
1219
- dragRect = getRect(dragEl);
1220
-
1221
- dragOverEvent('dragOverValid');
1222
- if (Sortable.eventCanceled) return completedFired;
1223
-
1224
- if (revert) {
1225
- parentEl = rootEl; // actualization
1226
- capture();
1227
-
1228
- this._hideClone();
1229
-
1230
- dragOverEvent('revert');
1231
-
1232
- if (!Sortable.eventCanceled) {
1233
- if (nextEl) {
1234
- rootEl.insertBefore(dragEl, nextEl);
1235
- } else {
1236
- rootEl.appendChild(dragEl);
1237
- }
1238
- }
1239
-
1240
- return completed(true);
1241
- }
1242
-
1243
- let elLastChild = lastChild(el, options.draggable);
1244
-
1245
- if (!elLastChild || _ghostIsLast(evt, vertical, this) && !elLastChild.animated) {
1246
- // Insert to end of list
1247
-
1248
- // If already at end of list: Do not insert
1249
- if (elLastChild === dragEl) {
1250
- return completed(false);
1251
- }
1252
-
1253
- // if there is a last element, it is the target
1254
- if (elLastChild && el === evt.target) {
1255
- target = elLastChild;
1256
- }
1257
-
1258
- if (target) {
1259
- targetRect = getRect(target);
1260
- }
1261
-
1262
- if (onMove(rootEl, el, originalDragEl || dragEl, dragRect, target, targetRect, evt, !!target) !== false) {
1263
- capture();
1264
- this._getPlaceholderOnMove(el, dragRect, target, targetRect, evt, Boolean(target));
1265
- if (elLastChild && elLastChild.nextSibling) { // the last draggable element is not the last node
1266
- el.insertBefore(dragEl, elLastChild.nextSibling);
1267
- }
1268
- else {
1269
- el.appendChild(dragEl);
1270
- }
1271
- parentEl = el; // actualization
1272
-
1273
- changed();
1274
- return completed(true);
1275
- }
1276
- }
1277
- else if (elLastChild && _ghostIsFirst(evt, vertical, this)) {
1278
- // Insert to start of list
1279
- let firstChild = getChild(el, 0, options, true, originalDragEl);
1280
- if (firstChild === dragEl) {
1281
- return completed(false);
1282
- }
1283
- target = firstChild;
1284
- targetRect = getRect(target);
1285
-
1286
- if (onMove(rootEl, el,originalDragEl || dragEl, dragRect, target, targetRect, evt, false) !== false) {
1287
- this._getPlaceholderOnMove(el, dragRect, target, targetRect, evt, false);
1288
- capture();
1289
- el.insertBefore(dragEl, firstChild);
1290
- parentEl = el; // actualization
1291
-
1292
- changed();
1293
- return completed(true);
1294
- }
1295
- }
1296
- else if (target.parentNode === el) {
1297
- targetRect = getRect(target);
1298
- let direction = 0,
1299
- targetBeforeFirstSwap,
1300
- differentLevel = dragEl.parentNode !== el,
1301
- differentRowCol = !_dragElInRowColumn(dragEl.animated && dragEl.toRect || dragRect, target.animated && target.toRect || targetRect, vertical),
1302
- side1 = vertical ? 'top' : 'left',
1303
- scrolledPastTop = isScrolledPast(target, 'top', 'top') || isScrolledPast(dragEl, 'top', 'top'),
1304
- scrollBefore = scrolledPastTop ? scrolledPastTop.scrollTop : void 0;
1305
-
1306
-
1307
- if (lastTarget !== target) {
1308
- targetBeforeFirstSwap = targetRect[side1];
1309
- pastFirstInvertThresh = false;
1310
- isCircumstantialInvert = (!differentRowCol && options.invertSwap) || differentLevel;
1311
- }
1312
-
1313
- direction = _getSwapDirection(
1314
- evt, target, targetRect, vertical,
1315
- differentRowCol ? 1 : options.swapThreshold,
1316
- options.invertedSwapThreshold == null ? options.swapThreshold : options.invertedSwapThreshold,
1317
- isCircumstantialInvert,
1318
- lastTarget === target
1319
- );
1320
-
1321
- let sibling;
1322
-
1323
- if (direction !== 0) {
1324
- // Check if target is beside dragEl in respective direction (ignoring hidden elements)
1325
- let dragIndex = index(dragEl, undefined, originalDragEl);
1326
-
1327
- do {
1328
- dragIndex -= direction;
1329
- sibling = parentEl.children[dragIndex];
1330
- } while (sibling && (css(sibling, 'display') === 'none' || sibling === ghostEl));
1331
- }
1332
- // If dragEl is already beside target: Do not insert
1333
- if (
1334
- direction === 0 ||
1335
- sibling === target
1336
- ) {
1337
- return completed(false);
1338
- }
1339
-
1340
- lastTarget = target;
1341
-
1342
- lastDirection = direction;
1343
-
1344
- let after = false;
1345
-
1346
- after = direction === 1;
1347
-
1348
- let moveVector = onMove(rootEl, el, originalDragEl || dragEl, dragRect, target, targetRect, evt, after);
1349
-
1350
- if (moveVector !== false) {
1351
- if (moveVector === 1 || moveVector === -1) {
1352
- after = (moveVector === 1);
1353
- }
1354
-
1355
- _silent = true;
1356
- setTimeout(_unsilent, 30);
1357
-
1358
- capture();
1359
-
1360
- this._getPlaceholderOnMove(el, dragRect, target, targetRect, evt, after);
1361
- let nextSibling = target.nextElementSibling;
1362
- if (after && !nextSibling) {
1363
- el.appendChild(dragEl);
1364
- } else {
1365
- target.parentNode.insertBefore(dragEl, after ? nextSibling : target);
1366
- }
1367
-
1368
- // Undo chrome's scroll adjustment (has no effect on other browsers)
1369
- if (scrolledPastTop) {
1370
- scrollBy(scrolledPastTop, 0, scrollBefore - scrolledPastTop.scrollTop);
1371
- }
1372
-
1373
- parentEl = dragEl.parentNode; // actualization
1374
-
1375
- // must be done before animation
1376
- if (targetBeforeFirstSwap !== undefined && !isCircumstantialInvert) {
1377
- targetMoveDistance = Math.abs(targetBeforeFirstSwap - getRect(target)[side1]);
1378
- }
1379
- changed();
1380
-
1381
- return completed(true);
1382
- }
1383
- }
1384
-
1385
- if (el.contains(dragEl)) {
1386
- return completed(false);
1387
- }
1388
- }
1389
-
1390
- return false;
1391
- },
1392
-
1393
- _ignoreWhileAnimating: null,
1394
-
1395
- _offMoveEvents: function() {
1396
- off(document, 'mousemove', this._onTouchMove);
1397
- off(document, 'touchmove', this._onTouchMove);
1398
- off(document, 'pointermove', this._onTouchMove);
1399
- off(document, 'dragover', nearestEmptyInsertDetectEvent);
1400
- off(document, 'mousemove', nearestEmptyInsertDetectEvent);
1401
- off(document, 'touchmove', nearestEmptyInsertDetectEvent);
1402
- },
1403
-
1404
- _offUpEvents: function () {
1405
- let ownerDocument = this.el.ownerDocument;
1406
-
1407
- off(ownerDocument, 'mouseup', this._onDrop);
1408
- off(ownerDocument, 'touchend', this._onDrop);
1409
- off(ownerDocument, 'pointerup', this._onDrop);
1410
- off(ownerDocument, 'pointercancel', this._onDrop);
1411
- off(ownerDocument, 'touchcancel', this._onDrop);
1412
- off(document, 'selectstart', this);
1413
- },
1414
-
1415
- _onDrop: function (/**Event*/evt) {
1416
- let el = this.el,
1417
- options = this.options;
1418
-
1419
- // Get the index of the dragged element within its parent
1420
- newIndex = index(dragEl, undefined, originalDragEl);
1421
- newDraggableIndex = index(dragEl, options.draggable, originalDragEl);
1422
-
1423
- pluginEvent('drop', this, {
1424
- evt
1425
- });
1426
-
1427
- parentEl = dragEl && dragEl.parentNode;
1428
-
1429
- // Get again after plugin event
1430
- newIndex = index(dragEl, undefined, originalDragEl);
1431
- newDraggableIndex = index(dragEl, options.draggable, originalDragEl);
1432
-
1433
- if (Sortable.eventCanceled) {
1434
- this._nulling();
1435
- return;
1436
- }
1437
-
1438
- awaitingDragStarted = false;
1439
- isCircumstantialInvert = false;
1440
- pastFirstInvertThresh = false;
1441
-
1442
- clearInterval(this._loopId);
1443
-
1444
- clearTimeout(this._dragStartTimer);
1445
-
1446
- _cancelNextTick(this.cloneId);
1447
- _cancelNextTick(this._dragStartId);
1448
-
1449
- // Unbind events
1450
- if (this.nativeDraggable) {
1451
- off(document, 'drop', this);
1452
- off(el, 'dragstart', this._onDragStart);
1453
- }
1454
- this._offMoveEvents();
1455
- this._offUpEvents();
1456
-
1457
-
1458
- if (Safari) {
1459
- css(document.body, 'user-select', '');
1460
- }
1461
-
1462
- css(dragEl, 'transform', '');
1463
-
1464
- if (evt) {
1465
- if (moved) {
1466
- evt.cancelable && evt.preventDefault();
1467
- !options.dropBubble && evt.stopPropagation();
1468
- }
1469
-
1470
- ghostEl && ghostEl.parentNode && ghostEl.parentNode.removeChild(ghostEl);
1471
-
1472
- if (rootEl === parentEl || (putSortable && putSortable.lastPutMode !== 'clone')) {
1473
- // Remove clone(s)
1474
- cloneEl && cloneEl.parentNode && cloneEl.parentNode.removeChild(cloneEl);
1475
- }
1476
-
1477
- if (dragEl) {
1478
- if (this.nativeDraggable) {
1479
- off(dragEl, 'dragend', this);
1480
- }
1481
-
1482
- _disableDraggable(dragEl);
1483
- dragEl.style['will-change'] = '';
1484
-
1485
- // Remove classes
1486
- // ghostClass is added in dragStarted
1487
- if (moved && !awaitingDragStarted) {
1488
- toggleClass(dragEl, putSortable ? putSortable.options.ghostClass : this.options.ghostClass, false);
1489
- }
1490
- toggleClass(dragEl, this.options.chosenClass, false);
1491
-
1492
- // Drag stop event
1493
- _dispatchEvent({
1494
- sortable: this,
1495
- name: 'unchoose',
1496
- toEl: parentEl,
1497
- newIndex: null,
1498
- newDraggableIndex: null,
1499
- originalEvent: evt
1500
- });
1501
-
1502
-
1503
- if (rootEl !== parentEl) {
1504
-
1505
- if (newIndex >= 0) {
1506
- // Add event
1507
- _dispatchEvent({
1508
- rootEl: parentEl,
1509
- name: 'add',
1510
- toEl: parentEl,
1511
- fromEl: rootEl,
1512
- originalEvent: evt
1513
- });
1514
-
1515
- // Remove event
1516
- _dispatchEvent({
1517
- sortable: this,
1518
- name: 'remove',
1519
- toEl: parentEl,
1520
- originalEvent: evt
1521
- });
1522
-
1523
- // drag from one list and drop into another
1524
- _dispatchEvent({
1525
- rootEl: parentEl,
1526
- name: 'sort',
1527
- toEl: parentEl,
1528
- fromEl: rootEl,
1529
- originalEvent: evt
1530
- });
1531
-
1532
- _dispatchEvent({
1533
- sortable: this,
1534
- name: 'sort',
1535
- toEl: parentEl,
1536
- originalEvent: evt
1537
- });
1538
- }
1539
-
1540
- putSortable && putSortable.save();
1541
- } else {
1542
- if (newIndex !== oldIndex) {
1543
- if (newIndex >= 0) {
1544
- // drag & drop within the same list
1545
- _dispatchEvent({
1546
- sortable: this,
1547
- name: 'update',
1548
- toEl: parentEl,
1549
- originalEvent: evt
1550
- });
1551
-
1552
- _dispatchEvent({
1553
- sortable: this,
1554
- name: 'sort',
1555
- toEl: parentEl,
1556
- originalEvent: evt
1557
- });
1558
- }
1559
- }
1560
- }
1561
-
1562
- if (Sortable.active) {
1563
- /* jshint eqnull:true */
1564
- if (newIndex == null || newIndex === -1) {
1565
- newIndex = oldIndex;
1566
- newDraggableIndex = oldDraggableIndex;
1567
- }
1568
-
1569
- _dispatchEvent({
1570
- sortable: this,
1571
- name: 'end',
1572
- toEl: parentEl,
1573
- originalEvent: evt
1574
- });
1575
-
1576
- // Save sorting
1577
- this.save();
1578
- }
1579
- }
1580
-
1581
- }
1582
- this._nulling();
1583
- },
1584
-
1585
- _nulling: function() {
1586
- pluginEvent('nulling', this);
1587
- this._removeClonedSelectedItem();
1588
- rootEl =
1589
- dragEl =
1590
- originalDragEl =
1591
- originalDragElDisplay =
1592
- parentEl =
1593
- ghostEl =
1594
- nextEl =
1595
- cloneEl =
1596
- lastDownEl =
1597
- cloneHidden =
1598
-
1599
- tapEvt =
1600
- touchEvt =
1601
-
1602
- moved =
1603
- newIndex =
1604
- newDraggableIndex =
1605
- oldIndex =
1606
- oldDraggableIndex =
1607
-
1608
- lastTarget =
1609
- lastDirection =
1610
-
1611
- putSortable =
1612
- activeGroup =
1613
- Sortable.dragged =
1614
- Sortable.ghost =
1615
- Sortable.clone =
1616
- Sortable.active = null;
1617
-
1618
- let el = this.el;
1619
- savedInputChecked.forEach(function (checkEl) {
1620
- if (el.contains(checkEl)) {
1621
- checkEl.checked = true;
1622
- }
1623
- });
1624
-
1625
- savedInputChecked.length =
1626
- lastDx =
1627
- lastDy = 0;
1628
- },
1629
-
1630
- handleEvent: function (/**Event*/evt) {
1631
- switch (evt.type) {
1632
- case 'drop':
1633
- case 'dragend':
1634
- this._onDrop(evt);
1635
- break;
1636
-
1637
- case 'dragenter':
1638
- case 'dragover':
1639
- if (dragEl) {
1640
- this._onDragOver(evt);
1641
- _globalDragOver(evt);
1642
- }
1643
- break;
1644
-
1645
- case 'selectstart':
1646
- evt.preventDefault();
1647
- break;
1648
- }
1649
- },
1650
-
1651
-
1652
- /**
1653
- * Serializes the item into an array of string.
1654
- * @returns {String[]}
1655
- */
1656
- toArray: function () {
1657
- let order = [],
1658
- el,
1659
- children = this.el.children,
1660
- i = 0,
1661
- n = children.length,
1662
- options = this.options;
1663
-
1664
- for (; i < n; i++) {
1665
- el = children[i];
1666
- if (closest(el, options.draggable, this.el, false, originalDragEl)) {
1667
- order.push(el.getAttribute(options.dataIdAttr) || _generateId(el));
1668
- }
1669
- }
1670
-
1671
- return order;
1672
- },
1673
-
1674
-
1675
- /**
1676
- * Sorts the elements according to the array.
1677
- * @param {String[]} order order of the items
1678
- */
1679
- sort: function (order, useAnimation) {
1680
- let items = {}, rootEl = this.el;
1681
-
1682
- this.toArray().forEach(function (id, i) {
1683
- let el = rootEl.children[i];
1684
-
1685
- if (closest(el, this.options.draggable, rootEl, false, originalDragEl)) {
1686
- items[id] = el;
1687
- }
1688
- }, this);
1689
-
1690
- useAnimation && this.captureAnimationState();
1691
- order.forEach(function (id) {
1692
- if (items[id]) {
1693
- rootEl.removeChild(items[id]);
1694
- rootEl.appendChild(items[id]);
1695
- }
1696
- });
1697
- useAnimation && this.animateAll();
1698
- },
1699
-
1700
-
1701
- /**
1702
- * Save the current sorting
1703
- */
1704
- save: function () {
1705
- let store = this.options.store;
1706
- store && store.set && store.set(this);
1707
- },
1708
-
1709
-
1710
- /**
1711
- * For each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree.
1712
- * @param {HTMLElement} el
1713
- * @param {String} [selector] default: `options.draggable`
1714
- * @returns {HTMLElement|null}
1715
- */
1716
- closest: function (el, selector) {
1717
- return closest(el, selector || this.options.draggable, this.el, false, originalDragEl);
1718
- },
1719
-
1720
-
1721
- /**
1722
- * Set/get option
1723
- * @param {string} name
1724
- * @param {*} [value]
1725
- * @returns {*}
1726
- */
1727
- option: function (name, value) {
1728
- let options = this.options;
1729
-
1730
- if (value === void 0) {
1731
- return options[name];
1732
- } else {
1733
- let modifiedValue = PluginManager.modifyOption(this, name, value);
1734
- if (typeof modifiedValue !== 'undefined') {
1735
- options[name] = modifiedValue;
1736
- } else {
1737
- options[name] = value;
1738
- }
1739
-
1740
- if (name === 'group') {
1741
- _prepareGroup(options);
1742
- }
1743
- }
1744
- },
1745
-
1746
-
1747
- /**
1748
- * Destroy
1749
- */
1750
- destroy: function () {
1751
- pluginEvent('destroy', this);
1752
- let el = this.el;
1753
-
1754
- el[expando] = null;
1755
-
1756
- off(el, 'mousedown', this._onTapStart);
1757
- off(el, 'touchstart', this._onTapStart);
1758
- off(el, 'pointerdown', this._onTapStart);
1759
-
1760
- if (this.nativeDraggable) {
1761
- off(el, 'dragover', this);
1762
- off(el, 'dragenter', this);
1763
- }
1764
- // Remove draggable attributes
1765
- Array.prototype.forEach.call(el.querySelectorAll('[draggable]'), function (el) {
1766
- el.removeAttribute('draggable');
1767
- });
1768
-
1769
- this._onDrop();
1770
-
1771
- this._disableDelayedDragEvents();
1772
-
1773
- sortables.splice(sortables.indexOf(this.el), 1);
1774
-
1775
- this.el = el = null;
1776
- },
1777
-
1778
- _hideClone: function() {
1779
- if (!cloneHidden) {
1780
- pluginEvent('hideClone', this);
1781
- if (Sortable.eventCanceled) return;
1782
-
1783
-
1784
- css(cloneEl, 'display', 'none');
1785
- if (this.options.removeCloneOnHide && cloneEl.parentNode) {
1786
- cloneEl.parentNode.removeChild(cloneEl);
1787
- }
1788
- cloneHidden = true;
1789
- }
1790
- },
1791
-
1792
- _showClone: function(putSortable) {
1793
- if (putSortable.lastPutMode !== 'clone') {
1794
- this._hideClone();
1795
- return;
1796
- }
1797
-
1798
-
1799
- if (cloneHidden) {
1800
- pluginEvent('showClone', this);
1801
- if (Sortable.eventCanceled) return;
1802
-
1803
- // show clone at dragEl or original position
1804
- if (typeof this.options.getPlaceholder !== 'function' && typeof this.options.getPlaceholderOnMove !== 'function') {
1805
- if (dragEl.parentNode == rootEl && !this.options.group.revertClone) {
1806
- rootEl.insertBefore(cloneEl, dragEl);
1807
- } else if (nextEl) {
1808
- rootEl.insertBefore(cloneEl, nextEl);
1809
- } else {
1810
- rootEl.appendChild(cloneEl);
1811
- }
1812
- }
1813
-
1814
- if (this.options.group.revertClone) {
1815
- this.animate(dragEl, cloneEl);
1816
- }
1817
-
1818
- css(cloneEl, 'display', '');
1819
- cloneHidden = false;
1820
- }
1821
- }
1822
- };
1823
-
1824
- function _globalDragOver(/**Event*/evt) {
1825
- if (evt.dataTransfer) {
1826
- evt.dataTransfer.dropEffect = 'move';
1827
- }
1828
- evt.cancelable && evt.preventDefault();
1829
- }
1830
-
1831
- const getMoveEvent = (fromEl, toEl, dragEl, dragRect, targetEl, targetRect, originalEvent, willInsertAfter) => {
1832
- let evt;
1833
- // Support for new CustomEvent feature
1834
- if (window.CustomEvent && !IE11OrLess && !Edge) {
1835
- evt = new CustomEvent('move', {
1836
- bubbles: true,
1837
- cancelable: true
1838
- });
1839
- } else {
1840
- evt = document.createEvent('Event');
1841
- evt.initEvent('move', true, true);
1842
- }
1843
-
1844
- evt.to = toEl;
1845
- evt.from = fromEl;
1846
- evt.dragged = dragEl;
1847
- evt.draggedRect = dragRect;
1848
- evt.related = targetEl || toEl;
1849
- evt.relatedRect = targetRect || getRect(toEl);
1850
- evt.willInsertAfter = willInsertAfter;
1851
-
1852
- evt.originalEvent = originalEvent;
1853
- return evt;
1854
- };
1855
-
1856
- function onMove(fromEl, toEl, dragEl, dragRect, targetEl, targetRect, originalEvent, willInsertAfter) {
1857
- let sortable = fromEl[expando],
1858
- onMoveFn = sortable.options.onMove,
1859
- retVal;
1860
-
1861
- const evt = getMoveEvent(fromEl, toEl, dragEl, dragRect, targetEl, targetRect, originalEvent, willInsertAfter);
1862
-
1863
- fromEl.dispatchEvent(evt);
1864
-
1865
- if (onMoveFn) {
1866
- retVal = onMoveFn.call(sortable, evt);
1867
- }
1868
-
1869
- return retVal;
1870
- }
1871
-
1872
- function _disableDraggable(el) {
1873
- el.draggable = false;
1874
- }
1875
-
1876
- function _unsilent() {
1877
- _silent = false;
1878
- }
1879
-
1880
- function _ghostIsFirst(evt, vertical, sortable) {
1881
- let firstElRect = getRect(getChild(sortable.el, 0, sortable.options, true, originalDragEl));
1882
- const childContainingRect = getChildContainingRectFromElement(sortable.el, sortable.options, ghostEl, originalDragEl);
1883
- const spacer = 10;
1884
-
1885
- return vertical ?
1886
- (evt.clientX < childContainingRect.left - spacer || evt.clientY < firstElRect.top && evt.clientX < firstElRect.right) :
1887
- (evt.clientY < childContainingRect.top - spacer || evt.clientY < firstElRect.bottom && evt.clientX < firstElRect.left);
1888
- }
1889
-
1890
- function _ghostIsLast(evt, vertical, sortable) {
1891
- const lastElRect = getRect(lastChild(sortable.el, sortable.options.draggable));
1892
- const childContainingRect = getChildContainingRectFromElement(sortable.el, sortable.options, ghostEl, originalDragEl);
1893
- const spacer = 10;
1894
-
1895
- return vertical ?
1896
- (evt.clientX > childContainingRect.right + spacer || evt.clientY > lastElRect.bottom && evt.clientX > lastElRect.left) :
1897
- (evt.clientY > childContainingRect.bottom + spacer || evt.clientX > lastElRect.right && evt.clientY > lastElRect.top);
1898
- }
1899
-
1900
- function _getSwapDirection(evt, target, targetRect, vertical, swapThreshold, invertedSwapThreshold, invertSwap, isLastTarget) {
1901
- let mouseOnAxis = vertical ? evt.clientY : evt.clientX,
1902
- targetLength = vertical ? targetRect.height : targetRect.width,
1903
- targetS1 = vertical ? targetRect.top : targetRect.left,
1904
- targetS2 = vertical ? targetRect.bottom : targetRect.right,
1905
- invert = false;
1906
-
1907
-
1908
- if (!invertSwap) {
1909
- // Never invert or create dragEl shadow when target movemenet causes mouse to move past the end of regular swapThreshold
1910
- if (isLastTarget && targetMoveDistance < targetLength * swapThreshold) { // multiplied only by swapThreshold because mouse will already be inside target by (1 - threshold) * targetLength / 2
1911
- // check if past first invert threshold on side opposite of lastDirection
1912
- if (!pastFirstInvertThresh &&
1913
- (lastDirection === 1 ?
1914
- (
1915
- mouseOnAxis > targetS1 + targetLength * invertedSwapThreshold / 2
1916
- ) :
1917
- (
1918
- mouseOnAxis < targetS2 - targetLength * invertedSwapThreshold / 2
1919
- )
1920
- )
1921
- )
1922
- {
1923
- // past first invert threshold, do not restrict inverted threshold to dragEl shadow
1924
- pastFirstInvertThresh = true;
1925
- }
1926
-
1927
- if (!pastFirstInvertThresh) {
1928
- // dragEl shadow (target move distance shadow)
1929
- if (
1930
- lastDirection === 1 ?
1931
- (
1932
- mouseOnAxis < targetS1 + targetMoveDistance // over dragEl shadow
1933
- ) :
1934
- (
1935
- mouseOnAxis > targetS2 - targetMoveDistance
1936
- )
1937
- )
1938
- {
1939
- return -lastDirection;
1940
- }
1941
- } else {
1942
- invert = true;
1943
- }
1944
- } else {
1945
- // Regular
1946
- if (
1947
- mouseOnAxis > targetS1 + (targetLength * (1 - swapThreshold) / 2) &&
1948
- mouseOnAxis < targetS2 - (targetLength * (1 - swapThreshold) / 2)
1949
- ) {
1950
- return _getInsertDirection(target);
1951
- }
1952
- }
1953
- }
1954
-
1955
- invert = invert || invertSwap;
1956
-
1957
- if (invert) {
1958
- // Invert of regular
1959
- if (
1960
- mouseOnAxis < targetS1 + (targetLength * invertedSwapThreshold / 2) ||
1961
- mouseOnAxis > targetS2 - (targetLength * invertedSwapThreshold / 2)
1962
- )
1963
- {
1964
- return ((mouseOnAxis > targetS1 + targetLength / 2) ? 1 : -1);
1965
- }
1966
- }
1967
-
1968
- return 0;
1969
- }
1970
-
1971
- /**
1972
- * Gets the direction dragEl must be swapped relative to target in order to make it
1973
- * seem that dragEl has been "inserted" into that element's position
1974
- * @param {HTMLElement} target The target whose position dragEl is being inserted at
1975
- * @return {Number} Direction dragEl must be swapped
1976
- */
1977
- function _getInsertDirection(target) {
1978
- if (index(dragEl, undefined, originalDragEl) < index(target, undefined, originalDragEl)) {
1979
- return 1;
1980
- } else {
1981
- return -1;
1982
- }
1983
- }
1984
-
1985
-
1986
- /**
1987
- * Generate id
1988
- * @param {HTMLElement} el
1989
- * @returns {String}
1990
- * @private
1991
- */
1992
- function _generateId(el) {
1993
- let str = el.tagName + el.className + el.src + el.href + el.textContent,
1994
- i = str.length,
1995
- sum = 0;
1996
-
1997
- while (i--) {
1998
- sum += str.charCodeAt(i);
1999
- }
2000
-
2001
- return sum.toString(36);
2002
- }
2003
-
2004
- function _saveInputCheckedState(root) {
2005
- savedInputChecked.length = 0;
2006
-
2007
- let inputs = root.getElementsByTagName('input');
2008
- let idx = inputs.length;
2009
-
2010
- while (idx--) {
2011
- let el = inputs[idx];
2012
- el.checked && savedInputChecked.push(el);
2013
- }
2014
- }
2015
-
2016
- function _nextTick(fn) {
2017
- return setTimeout(fn, 0);
2018
- }
2019
-
2020
- function _cancelNextTick(id) {
2021
- return clearTimeout(id);
2022
- }
2023
-
2024
- // Fixed #973:
2025
- if (documentExists) {
2026
- on(document, 'touchmove', function(evt) {
2027
- if ((Sortable.active || awaitingDragStarted) && evt.cancelable) {
2028
- evt.preventDefault();
2029
- }
2030
- });
2031
- }
2032
-
2033
-
2034
- // Export utils
2035
- Sortable.utils = {
2036
- on,
2037
- off,
2038
- css,
2039
- find,
2040
- is: function (el, selector) {
2041
- return !!closest(el, selector, el, false, originalDragEl);
2042
- },
2043
- extend,
2044
- throttle,
2045
- closest,
2046
- toggleClass,
2047
- clone,
2048
- index,
2049
- nextTick: _nextTick,
2050
- cancelNextTick: _cancelNextTick,
2051
- detectDirection: _detectDirection,
2052
- getChild,
2053
- expando
2054
- };
2055
-
2056
-
2057
- /**
2058
- * Get the Sortable instance of an element
2059
- * @param {HTMLElement} element The element
2060
- * @return {Sortable|undefined} The instance of Sortable
2061
- */
2062
- Sortable.get = function(element) {
2063
- return element[expando];
2064
- };
2065
-
2066
- /**
2067
- * Mount a plugin to Sortable
2068
- * @param {...SortablePlugin|SortablePlugin[]} plugins Plugins being mounted
2069
- */
2070
- Sortable.mount = function(...plugins) {
2071
- if (plugins[0].constructor === Array) plugins = plugins[0];
2072
-
2073
- plugins.forEach((plugin) => {
2074
- if (!plugin.prototype || !plugin.prototype.constructor) {
2075
- throw `Sortable: Mounted plugin must be a constructor function, not ${ {}.toString.call(plugin) }`;
2076
- }
2077
- if (plugin.utils) Sortable.utils = { ...Sortable.utils, ...plugin.utils };
2078
-
2079
- PluginManager.mount(plugin);
2080
- });
2081
- };
2082
-
2083
-
2084
-
2085
- /**
2086
- * Create sortable instance
2087
- * @param {HTMLElement} el
2088
- * @param {Object} [options]
2089
- */
2090
- Sortable.create = function (el, options) {
2091
- return new Sortable(el, options);
2092
- };
2093
-
2094
-
2095
- // Export
2096
- Sortable.version = version;
2097
-
2098
-
2099
- export default Sortable;