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