ember-sortable 2.2.5 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +2 -2
  2. package/.github/workflows/github_ci.yml +15 -13
  3. package/.huskyrc +0 -1
  4. package/CHANGELOG.md +234 -104
  5. package/MIGRATION_GUIDE_MODIFIERS.md +20 -13
  6. package/MIGRATION_GUIDE_V2.md +20 -8
  7. package/README.md +83 -149
  8. package/V2_MIGRATION_RFC.md +188 -222
  9. package/addon/modifiers/sortable-group.js +59 -52
  10. package/addon/modifiers/sortable-handle.js +16 -18
  11. package/addon/modifiers/sortable-item.js +124 -84
  12. package/addon/services/ember-sortable.js +3 -6
  13. package/addon/system/scroll-parent.js +1 -1
  14. package/addon/utils/coordinate.js +1 -1
  15. package/addon/utils/css-calculation.js +2 -2
  16. package/addon/utils/defaults.js +4 -3
  17. package/addon/utils/keyboard.js +3 -12
  18. package/addon-test-support/helpers/drag.js +3 -11
  19. package/addon-test-support/helpers/index.js +1 -4
  20. package/addon-test-support/helpers/reorder.js +4 -6
  21. package/addon-test-support/utils/keyboard.js +3 -12
  22. package/addon-test-support/utils/offset.js +6 -6
  23. package/config/environment.js +2 -2
  24. package/index.js +0 -20
  25. package/package.json +32 -18
  26. package/addon/components/sortable-group.js +0 -726
  27. package/addon/components/sortable-handle.js +0 -11
  28. package/addon/components/sortable-item.js +0 -753
  29. package/addon/styles/components/sortable-group.css +0 -9
  30. package/addon/templates/components/sortable-group.hbs +0 -16
  31. package/addon/templates/components/sortable-handle.hbs +0 -1
  32. package/addon/templates/components/sortable-item.hbs +0 -2
  33. package/addon/utils/transitionend.js +0 -22
  34. package/app/components/sortable-group.js +0 -3
  35. package/app/components/sortable-handle.js +0 -4
  36. package/app/components/sortable-item.js +0 -3
  37. package/jsconfig.json +0 -1
  38. package/vendor/polyfills/closest.js +0 -21
@@ -1,753 +0,0 @@
1
- import { Promise, defer } from 'rsvp';
2
- import Component from '@ember/component';
3
- import { computed, defineProperty } from '@ember/object';
4
- import { run, throttle } from '@ember/runloop';
5
- import { DEBUG } from '@glimmer/env';
6
- import scrollParent from '../system/scroll-parent';
7
- import ScrollContainer from '../system/scroll-container';
8
- import layout from '../templates/components/sortable-item';
9
- import { getBorderSpacing } from '../utils/css-calculation';
10
- import { DRAG_ACTIONS, ELEMENT_CLICK_ACTION, END_ACTIONS } from '../utils/constant';
11
- import { getX, getY } from '../utils/coordinate';
12
- import { buildWaiter } from '@ember/test-waiters';
13
- import config from 'ember-get-config';
14
- const { environment } = config;
15
- const isTesting = environment === 'test';
16
-
17
-
18
-
19
- const sortableItemWaiter = buildWaiter("sortable-item-waiter");
20
-
21
- /**
22
- * This component represents an individual model of `sortable-group`.
23
- *
24
- * Public API
25
- * @param {String} handle the attached handle, by default, it looks for a `sortable-handle`.
26
- * @param {Function} model the model that this component is associated with.
27
- * @param {Function} [onDragStart] An optional callback for when dragging starts.
28
- * @param {Function} [onDragStop] An optional callback for when dragging stops.
29
- *
30
- * @module drag-drop/-private/draggable-item
31
- */
32
- export default Component.extend({
33
- layout,
34
- tagName: 'li',
35
- classNames: ['sortable-item'],
36
- classNameBindings: ['isDragging', 'isDropping'],
37
-
38
- attributeBindings: ['data-test-selector', 'tabindex'],
39
-
40
- /**
41
- Model which the item represents.
42
- @property model
43
- @type Object
44
- @default null
45
- */
46
- model: null,
47
-
48
- /**
49
- Selector for the element to use as handle.
50
- 1. By default, we will hook it the yielded sortable-handle.
51
- 2. If you don't use the sortable-handle, the entire element will be used as the handle.
52
- 3. In very rare situations, if you want to use a handle, but not the sortable-handle,
53
- you can override this class with your own handle's selector. This behavior will be
54
- synonymous with v1
55
- @property handle
56
- @type String
57
- @default "[data-sortable-handle]"
58
- */
59
- handle: "[data-sortable-handle]",
60
-
61
- /**
62
- * Tolerance, in pixels, for when sorting should start.
63
- * If specified, sorting will not start until after mouse
64
- * is dragged beyond distance. Can be used to allow for clicks
65
- * on elements within a handle.
66
- *
67
- * @property distance
68
- * @type Integer
69
- * @default 0
70
- */
71
- distance: 0,
72
-
73
- /**
74
- * True if the item is currently being dragged.
75
- *
76
- * @property isDragging
77
- * @type Boolean
78
- * @default false
79
- * @protected
80
- */
81
- isDragging: false,
82
-
83
- /**
84
- Action that fires when the item starts being dragged.
85
- @property onDragStart
86
- @type Action
87
- @param object item model
88
- @default null
89
- */
90
- onDragStart: function() {},
91
-
92
- /**
93
- Action that fires when the item stops being dragged.
94
- @property onDragStop
95
- @type Action
96
- @param object item model
97
- @default null
98
- */
99
- onDragStop: function() {},
100
-
101
- /**
102
- True if the item is currently dropping.
103
- @property isDropping
104
- @type Boolean
105
- @default false
106
- */
107
- isDropping: false,
108
-
109
- /**
110
- True if the item was dropped during the interaction
111
- @property wasDropped
112
- @type Boolean
113
- @default false
114
- */
115
- wasDropped: false,
116
-
117
- /**
118
- @property isBusy
119
- @type Boolean
120
- */
121
- isBusy: computed.or('isDragging', 'isDropping'),
122
-
123
- /**
124
- The frequency with which the group is informed
125
- that an update is required.
126
- @property updateInterval
127
- @type Number
128
- @default 125
129
- */
130
- updateInterval: 125,
131
-
132
- /**
133
- Additional spacing between active item and the rest of the elements.
134
- @property spacing
135
- @type Number
136
- @default 0[px]
137
- */
138
- spacing: 0,
139
-
140
- /**
141
- Removes the ability for the current item to be dragged
142
- @property isDraggingDisabled
143
- @type Boolean
144
- @default false
145
- */
146
- isDraggingDisabled: false,
147
-
148
- /**
149
- @private
150
- Allows host instance to use the `group` property for something else with
151
- minimal overriding.
152
- */
153
- _direction: computed.readOnly('direction'),
154
-
155
-
156
- disableCheckScrollBounds: isTesting,
157
-
158
- init() {
159
- this._super(...arguments);
160
- this._setGetterSetters();
161
- },
162
-
163
- /**
164
- @method didInsertElement
165
- */
166
- didInsertElement() {
167
- this._super(...arguments);
168
-
169
- this.registerItem(this);
170
-
171
- // Instead of using `event.preventDefault()` in the 'primeDrag' event,
172
- // (doesn't work in Chrome 56), we set touch-action: none as a workaround.
173
- let handleElement = this.element.querySelector(this.get('handle'));
174
-
175
- if (!handleElement) {
176
- // Handle does not exist, so we set it null here.
177
- this.set('handle', null);
178
- handleElement = this.element;
179
- }
180
-
181
- handleElement.style['touch-action'] = 'none';
182
- },
183
-
184
- /**
185
- @method willDestroyElement
186
- */
187
- willDestroyElement() {
188
- this._super(...arguments);
189
- this.deregisterItem(this);
190
-
191
- // remove event listeners that may still be attached
192
- DRAG_ACTIONS.forEach(event => window.removeEventListener(event, this._prepareDragListener));
193
- END_ACTIONS.forEach(event => window.removeEventListener(event, this._cancelStartDragListener));
194
- this.element.removeEventListener(ELEMENT_CLICK_ACTION, this._preventClickHandler);
195
- this.set('isDragging', false);
196
- this.set('isDropping', false);
197
- },
198
-
199
- /**
200
- @method mouseDown
201
- */
202
- mouseDown(event) {
203
- if (event.which !== 1) { return; }
204
- if (event.ctrlKey) { return; }
205
-
206
- this._primeDrag(event);
207
- },
208
-
209
- keyDown(event) {
210
- const handleElement = this.element.querySelector(this.get('handle'));
211
-
212
- // If the event is coming from within the item, we do not want to activate keyboard reorder mode.
213
- if (event.target === handleElement || event.target === this.element) {
214
- this.setSelectedItem(this);
215
- this.activateKeyDown();
216
- } else {
217
- this.deactivateKeyDown();
218
- }
219
- },
220
-
221
- /**
222
- @method touchStart
223
- */
224
- touchStart(event) {
225
- this._primeDrag(event);
226
- },
227
-
228
- /**
229
- @method freeze
230
- */
231
- freeze() {
232
- let el = this.element;
233
- if (!el) { return; }
234
-
235
- el.style.transition = 'none';
236
- },
237
-
238
- /**
239
- @method reset
240
- */
241
- reset() {
242
- let el = this.element;
243
- if (!el) { return; }
244
-
245
- delete this._y;
246
- delete this._x;
247
-
248
- el.style.transform = '';
249
- },
250
-
251
- /**
252
- @method thaw
253
- */
254
- thaw() {
255
- let el = this.element;
256
- if (!el) { return; }
257
-
258
- el.style.transition = '';
259
- },
260
-
261
- /**
262
- * Setup event listeners for drag and drop
263
- *
264
- * @method _primeDrag
265
- * @param {Event} startEvent JS Event object
266
- * @private
267
- */
268
- _primeDrag(startEvent) {
269
- // Prevent dragging if the sortable-item is destroying or is disabled.
270
- if (this.isDestroying || this.get('isDraggingDisabled')) {
271
- return;
272
- }
273
-
274
- let handle = this.get('handle');
275
-
276
- if (handle && !startEvent.target.closest(handle)) {
277
- return;
278
- }
279
-
280
- startEvent.preventDefault();
281
- startEvent.stopPropagation();
282
-
283
- this._prepareDragListener = run.bind(this, this._prepareDrag, startEvent);
284
-
285
- DRAG_ACTIONS.forEach(event => window.addEventListener(event, this._prepareDragListener));
286
-
287
- this._cancelStartDragListener = () => {
288
- DRAG_ACTIONS.forEach(event => window.removeEventListener(event, this._prepareDragListener));
289
- };
290
-
291
- const selfCancellingCallback = () => {
292
- END_ACTIONS.forEach(event => window.removeEventListener(event, selfCancellingCallback));
293
- this._cancelStartDragListener();
294
- };
295
-
296
- END_ACTIONS.forEach(event => window.addEventListener(event, selfCancellingCallback));
297
- },
298
-
299
- /**
300
- * Prepares for the drag event
301
- *
302
- * @method _prepareDrag
303
- * @param {Event} startEvent JS Event object
304
- * @param {Event} event JS Event object
305
- * @private
306
- */
307
- _prepareDrag(startEvent, event) {
308
- let distance = this.get('distance');
309
- let dx = Math.abs(getX(startEvent) - getX(event));
310
- let dy = Math.abs(getY(startEvent) - getY(event));
311
-
312
- if (distance <= dx || distance <= dy) {
313
- DRAG_ACTIONS.forEach(event => window.removeEventListener(event, this._prepareDragListener));
314
- this._startDrag(startEvent);
315
- }
316
- },
317
-
318
- /**
319
- * Start dragging & setup more event listeners
320
- *
321
- * @method _startDrag
322
- * @param {Event} event JS Event object
323
- * @private
324
- */
325
- _startDrag(event) {
326
- if (this.isDestroying || this.get('isBusy')) { return; }
327
-
328
- let drag = this._makeDragHandler(event);
329
- let dragThrottled = ev => throttle(this, drag, ev, 16, false);
330
-
331
- let drop = () => {
332
- DRAG_ACTIONS.forEach(event => window.removeEventListener(event, dragThrottled));
333
- END_ACTIONS.forEach(event => window.removeEventListener(event, drop));
334
-
335
- run(() => {
336
- this._drop();
337
- });
338
- };
339
-
340
- DRAG_ACTIONS.forEach(event => window.addEventListener(event, dragThrottled));
341
- END_ACTIONS.forEach(event => window.addEventListener(event, drop));
342
-
343
- this.prepare();
344
- this.set('isDragging', true);
345
- this.onDragStart(this.get('model'));
346
- this._scrollOnEdges(drag);
347
- },
348
-
349
- /**
350
- The maximum scroll speed when dragging element.
351
- @property maxScrollSpeed
352
- @default 20
353
- */
354
- maxScrollSpeed: 20,
355
-
356
- _scrollOnEdges(drag) {
357
- let groupDirection = this.get('_direction');
358
- let element = this.element;
359
- let scrollContainer = new ScrollContainer(scrollParent(element));
360
- let itemContainer = {
361
- width: parseInt(getComputedStyle(element).width, 10),
362
- get height() {
363
- return parseInt(getComputedStyle(element).height, 10);
364
- },
365
- get left() {
366
- return element.getBoundingClientRect().left;
367
- },
368
- get right() {
369
- return this.left + this.width;
370
- },
371
- get top() {
372
- return element.getBoundingClientRect().top;
373
- },
374
- get bottom() {
375
- return this.top + this.height;
376
- }
377
- };
378
-
379
- let leadingEdgeKey, trailingEdgeKey, scrollKey, pageKey;
380
- if (groupDirection === 'x') {
381
- leadingEdgeKey = 'left';
382
- trailingEdgeKey = 'right';
383
- scrollKey = 'scrollLeft';
384
- pageKey = 'pageX';
385
- } else {
386
- leadingEdgeKey = 'top';
387
- trailingEdgeKey = 'bottom';
388
- scrollKey = 'scrollTop';
389
- pageKey = 'pageY';
390
- }
391
-
392
- let createFakeEvent = () => {
393
- if (this._pageX == null && this._pageY == null) { return; }
394
- return {
395
- pageX: this._pageX,
396
- pageY: this._pageY,
397
- clientX: this._pageX,
398
- clientY: this._pageY
399
- };
400
- };
401
-
402
- // Set a trigger padding that will start scrolling
403
- // the box when the item reaches within padding pixels
404
- // of the edge of the scroll container.
405
- let checkScrollBounds = () => {
406
- let leadingEdge = itemContainer[leadingEdgeKey];
407
- let trailingEdge = itemContainer[trailingEdgeKey];
408
- let scroll = scrollContainer[scrollKey]();
409
-
410
- let delta = 0;
411
- if (trailingEdge >= scrollContainer[trailingEdgeKey]) {
412
- delta = trailingEdge - scrollContainer[trailingEdgeKey];
413
- } else if (leadingEdge <= scrollContainer[leadingEdgeKey]) {
414
- delta = leadingEdge - scrollContainer[leadingEdgeKey];
415
- }
416
-
417
- if (delta !== 0) {
418
- let speed = this.get('maxScrollSpeed');
419
- delta = Math.min(Math.max(delta, -1 * speed), speed);
420
-
421
- delta = scrollContainer[scrollKey](scroll + delta) - scroll;
422
-
423
- let event = createFakeEvent();
424
- if (event) {
425
- if (scrollContainer.isWindow) {
426
- event[pageKey] += delta;
427
- }
428
- run(() => drag(event));
429
- }
430
- }
431
- if (this.get('isDragging')) {
432
- requestAnimationFrame(checkScrollBounds);
433
- }
434
- };
435
- if (!this.disableCheckScrollBounds) {
436
- requestAnimationFrame(checkScrollBounds);
437
- }
438
- },
439
-
440
- /**
441
- @method _makeDragHandler
442
- @param {Event} startEvent
443
- @return {Function}
444
- @private
445
- */
446
- _makeDragHandler(startEvent) {
447
- const groupDirection = this.get('_direction');
448
- let dragOrigin;
449
- let elementOrigin;
450
- let scrollOrigin;
451
- // if component is destroying or element does not exist or its parent does not exist, stop.
452
- if (this.isDestroying || !this.element || !this.element.parentNode) {
453
- return;
454
- }
455
- let parentElement = this.element.parentNode;
456
-
457
- if (groupDirection === 'x') {
458
- dragOrigin = getX(startEvent);
459
- elementOrigin = this.get('x');
460
- scrollOrigin = parentElement.getBoundingClientRect().left;
461
-
462
- return event => {
463
- this._pageX = getX(event);
464
- let dx = this._pageX - dragOrigin;
465
- let scrollX = parentElement.getBoundingClientRect().left;
466
- let x = elementOrigin + dx + (scrollOrigin - scrollX);
467
-
468
- this._drag(x);
469
- };
470
- }
471
-
472
- if (groupDirection === 'y') {
473
- dragOrigin = getY(startEvent);
474
- elementOrigin = this.get('y');
475
- scrollOrigin = parentElement.getBoundingClientRect().top;
476
-
477
- return event => {
478
- this._pageY = getY(event);
479
- let dy = this._pageY - dragOrigin;
480
- let scrollY = parentElement.getBoundingClientRect().top;
481
- let y = elementOrigin + dy + (scrollOrigin - scrollY);
482
-
483
- this._drag(y);
484
- };
485
- }
486
- },
487
-
488
- /**
489
- @method _scheduleApplyPosition
490
- @private
491
- */
492
- _scheduleApplyPosition() {
493
- run.scheduleOnce('render', this, '_applyPosition');
494
- },
495
-
496
- /**
497
- @method _applyPosition
498
- @private
499
- */
500
- _applyPosition() {
501
- if (!this.element || !this.element) { return; }
502
-
503
- const groupDirection = this.get('_direction');
504
-
505
- if (groupDirection === 'x') {
506
- let x = this.get('x');
507
- let dx = x - this.element.offsetLeft + parseFloat(getComputedStyle(this.element).marginLeft);
508
-
509
- this.element.style.transform = `translateX(${dx}px)`;
510
- }
511
- if (groupDirection === 'y') {
512
- let y = this.get('y');
513
- let dy = y - this.element.offsetTop;
514
-
515
- this.element.style.transform = `translateY(${dy}px)`;
516
- }
517
- },
518
-
519
- /**
520
- @method _drag
521
- @private
522
- */
523
- _drag(dimension) {
524
- if(!this.get("isDragging")) {
525
- return;
526
- }
527
- let updateInterval = this.get('updateInterval');
528
- const groupDirection = this.get('_direction');
529
-
530
- if (groupDirection === 'x') {
531
- this.set('x', dimension);
532
- }
533
- if (groupDirection === 'y') {
534
- this.set('y', dimension);
535
- }
536
-
537
- throttle(this, 'update', updateInterval);
538
- },
539
-
540
- /**
541
- @method _drop
542
- @private
543
- */
544
- _drop() {
545
- if (!this.element) { return; }
546
-
547
- let transitionPromise = this._waitForTransition();
548
-
549
- this._preventClick();
550
-
551
- this.set('isDragging', false);
552
- this.set('isDropping', true);
553
-
554
- this.update();
555
- transitionPromise.then(() => this._complete());
556
- },
557
-
558
- /**
559
- @method _preventClick
560
- @private
561
- */
562
- _preventClick() {
563
- const selfCancellingCallback = (event) => {
564
- this.element.removeEventListener(ELEMENT_CLICK_ACTION, selfCancellingCallback);
565
- this._preventClickHandler(event);
566
- };
567
-
568
- this.element.addEventListener(ELEMENT_CLICK_ACTION, selfCancellingCallback);
569
- },
570
-
571
- /**
572
- @method _preventClickHandler
573
- @private
574
- */
575
- _preventClickHandler(e) {
576
- e.stopPropagation();
577
- e.preventDefault();
578
- e.stopImmediatePropagation();
579
- },
580
-
581
- /**
582
- @method _waitForTransition
583
- @private
584
- @return Promise
585
- */
586
- _waitForTransition() {
587
- let waiterToken;
588
-
589
- if (DEBUG) {
590
- waiterToken = sortableItemWaiter.beginAsync();
591
- }
592
-
593
- let transitionPromise;
594
-
595
- if (this.get('isAnimated')) {
596
- const deferred = defer();
597
- this.element.addEventListener('transitionend', deferred.resolve);
598
- transitionPromise = deferred.promise.finally(() => {
599
- this.element.removeEventListener('transitionend', deferred.resolve);
600
- });
601
- } else {
602
- const duration = this.get('isAnimated') ? this.get('transitionDuration') : 200;
603
- transitionPromise = new Promise((resolve) => run.later(resolve, duration));
604
- }
605
-
606
- if (DEBUG) {
607
- transitionPromise = transitionPromise.finally(() => {
608
- sortableItemWaiter.endAsync(waiterToken);
609
- });
610
- }
611
-
612
- return transitionPromise;
613
- },
614
-
615
- /**
616
- @method _complete
617
- @private
618
- */
619
- _complete() {
620
- this.onDragStop(this.get('model'));
621
- this.set('isDropping', false);
622
- this.set('wasDropped', true);
623
- this.commit();
624
- },
625
-
626
- /**
627
- * Defining getters and setters to support native getter and setters until we decide to drop support for ember versions below 3.10
628
- */
629
- _setGetterSetters() {
630
- /**
631
- True if the item transitions with animation.
632
- @property isAnimated
633
- @type Boolean
634
- */
635
- defineProperty(this, 'isAnimated', {
636
- get() {
637
- if (!this.element) { return; }
638
-
639
- let el = this.element;
640
- let property = getComputedStyle(el).transitionProperty;
641
-
642
- return /all|transform/.test(property) && this.get("transitionDuration") > 0;
643
- }
644
- });
645
-
646
- /**
647
- The current transition duration in milliseconds.
648
- @property transitionDuration
649
- @type Number
650
- */
651
- defineProperty(this, 'transitionDuration', {
652
- get() {
653
- let el = this.element;
654
- let rule = getComputedStyle(el).transitionDuration;
655
- let match = rule.match(/([\d.]+)([ms]*)/);
656
-
657
- if (match) {
658
- let value = parseFloat(match[1]);
659
- let unit = match[2];
660
-
661
- if (unit === 's') {
662
- value = value * 1000;
663
- }
664
-
665
- return value;
666
- }
667
-
668
- return 0;
669
- }
670
- });
671
-
672
- /**
673
- Horizontal position of the item.
674
- @property x
675
- @type Number
676
- */
677
- defineProperty(this, 'x', {
678
- get() {
679
- if (this._x === undefined) {
680
- let marginLeft = parseFloat(getComputedStyle(this.element).marginLeft);
681
- this._x = this.element.scrollLeft + this.element.offsetLeft - marginLeft;
682
- }
683
-
684
- return this._x;
685
- },
686
- set(value) {
687
- if (value !== this._x) {
688
- this._x = value;
689
- this._scheduleApplyPosition();
690
- }
691
- }
692
- });
693
-
694
- /**
695
- Vertical position of the item relative to its offset parent.
696
- @property y
697
- @type Number
698
- */
699
- defineProperty(this, 'y', {
700
- get() {
701
- if (this._y === undefined) {
702
- this._y = this.element.offsetTop;
703
- }
704
-
705
- return this._y;
706
- },
707
- set(value) {
708
- if (value !== this._y) {
709
- this._y = value;
710
- this._scheduleApplyPosition();
711
- }
712
- }
713
- });
714
-
715
- /**
716
- Width of the item.
717
- @property height
718
- @type Number
719
- */
720
- defineProperty(this, 'width', {
721
- get() {
722
- let el = this.element;
723
- let width = el.offsetWidth;
724
- let elStyles = getComputedStyle(el);
725
-
726
- width += parseInt(elStyles.marginLeft) + parseInt(elStyles.marginRight); // equal to jQuery.outerWidth(true)
727
-
728
- width += getBorderSpacing(el).horizontal;
729
-
730
- return width;
731
- }
732
- });
733
-
734
- /**
735
- Height of the item including margins.
736
- @property height
737
- @type Number
738
- */
739
- defineProperty(this, 'height', {
740
- get() {
741
- let el = this.element;
742
- let height = el.offsetHeight;
743
-
744
- let marginBottom = parseFloat(getComputedStyle(el).marginBottom);
745
- height += marginBottom;
746
-
747
- height += getBorderSpacing(el).vertical;
748
-
749
- return height;
750
- }
751
- });
752
- },
753
- });