draggable-rails 0.1.0 → 0.1.1
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.
- checksums.yaml +4 -4
- data/lib/draggable/rails/version.rb +1 -1
- data/vendor/assets/javascripts/behaviour/collidable.js +99 -0
- data/vendor/assets/javascripts/behaviour/snappable.js +107 -0
- data/vendor/assets/javascripts/core/accessibility.js +58 -0
- data/vendor/assets/javascripts/core/mirror.js +129 -0
- data/vendor/assets/javascripts/draggable.js +487 -0
- data/vendor/assets/javascripts/droppable.js +168 -0
- data/vendor/assets/javascripts/events/abstract-event.js +45 -0
- data/vendor/assets/javascripts/events/collidable-event.js +25 -0
- data/vendor/assets/javascripts/events/drag-event.js +91 -0
- data/vendor/assets/javascripts/events/draggable-event.js +17 -0
- data/vendor/assets/javascripts/events/droppable-event.js +21 -0
- data/vendor/assets/javascripts/events/mirror-event.js +43 -0
- data/vendor/assets/javascripts/events/sensor-event.js +47 -0
- data/vendor/assets/javascripts/events/snappable-event.js +15 -0
- data/vendor/assets/javascripts/events/sortable-event.js +35 -0
- data/vendor/assets/javascripts/events/swappable-event.js +23 -0
- data/vendor/assets/javascripts/index.js +18 -0
- data/vendor/assets/javascripts/sensors/drag-sensor.js +158 -0
- data/vendor/assets/javascripts/sensors/force-touch-sensor.js +166 -0
- data/vendor/assets/javascripts/sensors/mouse-sensor.js +110 -0
- data/vendor/assets/javascripts/sensors/sensor.js +23 -0
- data/vendor/assets/javascripts/sensors/touch-sensor.js +145 -0
- data/vendor/assets/javascripts/sortable.js +161 -0
- data/vendor/assets/javascripts/swappable.js +104 -0
- data/vendor/assets/javascripts/utils.js +52 -0
- metadata +26 -1
@@ -0,0 +1,23 @@
|
|
1
|
+
export default class Sensor {
|
2
|
+
constructor(containers = [], options = {}) {
|
3
|
+
this.containers = containers;
|
4
|
+
this.options = options;
|
5
|
+
}
|
6
|
+
|
7
|
+
attach() {
|
8
|
+
return this;
|
9
|
+
}
|
10
|
+
|
11
|
+
detach() {
|
12
|
+
return this;
|
13
|
+
}
|
14
|
+
|
15
|
+
trigger(element, sensorEvent) {
|
16
|
+
const event = document.createEvent('Event');
|
17
|
+
event.detail = sensorEvent;
|
18
|
+
event.initEvent(sensorEvent.type, true, true);
|
19
|
+
element.dispatchEvent(event);
|
20
|
+
this.lastEvent = sensorEvent;
|
21
|
+
return sensorEvent;
|
22
|
+
}
|
23
|
+
}
|
@@ -0,0 +1,145 @@
|
|
1
|
+
import Sensor from './sensor';
|
2
|
+
import {closest} from './../utils';
|
3
|
+
|
4
|
+
import {
|
5
|
+
DragStartSensorEvent,
|
6
|
+
DragMoveSensorEvent,
|
7
|
+
DragStopSensorEvent,
|
8
|
+
} from './../events/sensor-event';
|
9
|
+
|
10
|
+
export default class TouchSensor extends Sensor {
|
11
|
+
constructor(containers = [], options = {}) {
|
12
|
+
super(containers, options);
|
13
|
+
|
14
|
+
this.dragging = false;
|
15
|
+
this.currentContainer = null;
|
16
|
+
this.currentScrollableParent = null;
|
17
|
+
|
18
|
+
this._onTouchStart = this._onTouchStart.bind(this);
|
19
|
+
this._onTouchHold = this._onTouchHold.bind(this);
|
20
|
+
this._onTouchEnd = this._onTouchEnd.bind(this);
|
21
|
+
this._onTouchMove = this._onTouchMove.bind(this);
|
22
|
+
this._onScroll = this._onScroll.bind(this);
|
23
|
+
}
|
24
|
+
|
25
|
+
attach() {
|
26
|
+
for (const container of this.containers) {
|
27
|
+
container.addEventListener('touchstart', this._onTouchStart, false);
|
28
|
+
}
|
29
|
+
|
30
|
+
document.addEventListener('touchend', this._onTouchEnd, false);
|
31
|
+
document.addEventListener('touchcancel', this._onTouchEnd, false);
|
32
|
+
document.addEventListener('touchmove', this._onTouchMove, false);
|
33
|
+
}
|
34
|
+
|
35
|
+
detach() {
|
36
|
+
for (const container of this.containers) {
|
37
|
+
container.removeEventListener('touchstart', this._onTouchStart, false);
|
38
|
+
}
|
39
|
+
|
40
|
+
document.removeEventListener('touchend', this._onTouchEnd, false);
|
41
|
+
document.removeEventListener('touchcancel', this._onTouchEnd, false);
|
42
|
+
document.removeEventListener('touchmove', this._onTouchMove, false);
|
43
|
+
}
|
44
|
+
|
45
|
+
_onScroll() {
|
46
|
+
// Cancel potential drag and allow scroll on iOS or other touch devices
|
47
|
+
clearTimeout(this.tapTimeout);
|
48
|
+
}
|
49
|
+
|
50
|
+
_onTouchStart(event) {
|
51
|
+
event.preventDefault();
|
52
|
+
const container = event.currentTarget;
|
53
|
+
|
54
|
+
// detect if body is scrolling on iOS
|
55
|
+
document.addEventListener('scroll', this._onScroll);
|
56
|
+
container.addEventListener('contextmenu', _onContextMenu);
|
57
|
+
|
58
|
+
this.currentScrollableParent = closest(container, (element) => element.offsetHeight < element.scrollHeight);
|
59
|
+
|
60
|
+
if (this.currentScrollableParent) {
|
61
|
+
this.currentScrollableParent.addEventListener('scroll', this._onScroll);
|
62
|
+
}
|
63
|
+
|
64
|
+
this.tapTimeout = setTimeout(this._onTouchHold(event, container), this.options.delay);
|
65
|
+
}
|
66
|
+
|
67
|
+
_onTouchHold(event, container) {
|
68
|
+
return () => {
|
69
|
+
const touch = event.touches[0] || event.changedTouches[0];
|
70
|
+
const target = event.target;
|
71
|
+
|
72
|
+
const dragStartEvent = new DragStartSensorEvent({
|
73
|
+
clientX: touch.pageX,
|
74
|
+
clientY: touch.pageY,
|
75
|
+
target,
|
76
|
+
container,
|
77
|
+
originalEvent: event,
|
78
|
+
});
|
79
|
+
|
80
|
+
this.trigger(container, dragStartEvent);
|
81
|
+
|
82
|
+
this.currentContainer = container;
|
83
|
+
this.dragging = !dragStartEvent.canceled();
|
84
|
+
};
|
85
|
+
}
|
86
|
+
|
87
|
+
_onTouchMove(event) {
|
88
|
+
if (!this.dragging) {
|
89
|
+
return;
|
90
|
+
}
|
91
|
+
|
92
|
+
event.stopPropagation();
|
93
|
+
|
94
|
+
const touch = event.touches[0] || event.changedTouches[0];
|
95
|
+
const target = document.elementFromPoint(touch.pageX - window.scrollX, touch.pageY - window.scrollY);
|
96
|
+
|
97
|
+
const dragMoveEvent = new DragMoveSensorEvent({
|
98
|
+
clientX: touch.pageX,
|
99
|
+
clientY: touch.pageY,
|
100
|
+
target,
|
101
|
+
container: this.currentContainer,
|
102
|
+
originalEvent: event,
|
103
|
+
});
|
104
|
+
|
105
|
+
this.trigger(this.currentContainer, dragMoveEvent);
|
106
|
+
}
|
107
|
+
|
108
|
+
_onTouchEnd(event) {
|
109
|
+
const container = event.currentTarget;
|
110
|
+
|
111
|
+
document.removeEventListener('scroll', this._onScroll);
|
112
|
+
container.removeEventListener('contextmenu', _onContextMenu);
|
113
|
+
|
114
|
+
if (this.currentScrollableParent) {
|
115
|
+
this.currentScrollableParent.removeEventListener('scroll', this._onScroll);
|
116
|
+
}
|
117
|
+
|
118
|
+
clearTimeout(this.tapTimeout);
|
119
|
+
|
120
|
+
if (!this.dragging) {
|
121
|
+
return;
|
122
|
+
}
|
123
|
+
|
124
|
+
const touch = event.touches[0] || event.changedTouches[0];
|
125
|
+
|
126
|
+
event.preventDefault();
|
127
|
+
|
128
|
+
const dragStopEvent = new DragStopSensorEvent({
|
129
|
+
clientX: touch.pageX,
|
130
|
+
clientY: touch.pageY,
|
131
|
+
target: null,
|
132
|
+
container: this.currentContainer,
|
133
|
+
originalEvent: event,
|
134
|
+
});
|
135
|
+
|
136
|
+
this.trigger(this.currentContainer, dragStopEvent);
|
137
|
+
|
138
|
+
this.currentContainer = null;
|
139
|
+
this.dragging = false;
|
140
|
+
}
|
141
|
+
}
|
142
|
+
|
143
|
+
function _onContextMenu(event) {
|
144
|
+
event.preventDefault();
|
145
|
+
}
|
@@ -0,0 +1,161 @@
|
|
1
|
+
import Draggable from './draggable';
|
2
|
+
|
3
|
+
import {
|
4
|
+
SortableStartEvent,
|
5
|
+
SortableSortedEvent,
|
6
|
+
SortableStopEvent,
|
7
|
+
} from './events/sortable-event';
|
8
|
+
|
9
|
+
export default class Sortable {
|
10
|
+
constructor(containers = [], options = {}) {
|
11
|
+
this.draggable = new Draggable(containers, options);
|
12
|
+
|
13
|
+
this._onDragStart = this._onDragStart.bind(this);
|
14
|
+
this._onDragOverContainer = this._onDragOverContainer.bind(this);
|
15
|
+
this._onDragOver = this._onDragOver.bind(this);
|
16
|
+
this._onDragStop = this._onDragStop.bind(this);
|
17
|
+
|
18
|
+
this.draggable
|
19
|
+
.on('drag:start', this._onDragStart)
|
20
|
+
.on('drag:over:container', this._onDragOverContainer)
|
21
|
+
.on('drag:over', this._onDragOver)
|
22
|
+
.on('drag:stop', this._onDragStop);
|
23
|
+
}
|
24
|
+
|
25
|
+
destroy() {
|
26
|
+
this.draggable
|
27
|
+
.off('drag:start', this._onDragStart)
|
28
|
+
.off('drag:over:container', this._onDragOverContainer)
|
29
|
+
.off('drag:over', this._onDragOver)
|
30
|
+
.off('drag:stop', this._onDragStop)
|
31
|
+
.destroy();
|
32
|
+
}
|
33
|
+
|
34
|
+
on(type, callback) {
|
35
|
+
this.draggable.on(type, callback);
|
36
|
+
return this;
|
37
|
+
}
|
38
|
+
|
39
|
+
off(type, callback) {
|
40
|
+
this.draggable.off(type, callback);
|
41
|
+
return this;
|
42
|
+
}
|
43
|
+
|
44
|
+
_onDragStart(event) {
|
45
|
+
this.startIndex = index(event.source);
|
46
|
+
|
47
|
+
const sortableStartEvent = new SortableStartEvent({
|
48
|
+
dragEvent: event,
|
49
|
+
startIndex: this.startIndex,
|
50
|
+
});
|
51
|
+
|
52
|
+
this.draggable.trigger(sortableStartEvent);
|
53
|
+
|
54
|
+
if (sortableStartEvent.canceled()) {
|
55
|
+
event.cancel();
|
56
|
+
}
|
57
|
+
}
|
58
|
+
|
59
|
+
_onDragOverContainer(event) {
|
60
|
+
if (event.canceled()) {
|
61
|
+
return;
|
62
|
+
}
|
63
|
+
|
64
|
+
const moves = move(event.source, event.over, event.overContainer);
|
65
|
+
|
66
|
+
if (!moves) {
|
67
|
+
return;
|
68
|
+
}
|
69
|
+
|
70
|
+
const sortableSortedEvent = new SortableSortedEvent({
|
71
|
+
dragEvent: event,
|
72
|
+
moves,
|
73
|
+
});
|
74
|
+
|
75
|
+
this.draggable.triggerEvent(sortableSortedEvent);
|
76
|
+
}
|
77
|
+
|
78
|
+
_onDragOver(event) {
|
79
|
+
if (event.over === event.source) {
|
80
|
+
return;
|
81
|
+
}
|
82
|
+
|
83
|
+
const moves = move(event.source, event.over, event.overContainer);
|
84
|
+
|
85
|
+
if (!moves) {
|
86
|
+
return;
|
87
|
+
}
|
88
|
+
|
89
|
+
const sortableSortedEvent = new SortableSortedEvent({
|
90
|
+
dragEvent: event,
|
91
|
+
moves,
|
92
|
+
});
|
93
|
+
|
94
|
+
this.draggable.triggerEvent(sortableSortedEvent);
|
95
|
+
}
|
96
|
+
|
97
|
+
_onDragStop(event) {
|
98
|
+
const sortableStopEvent = new SortableStopEvent({
|
99
|
+
dragEvent: event,
|
100
|
+
oldIndex: this.startIndex,
|
101
|
+
newIndex: index(event.source),
|
102
|
+
});
|
103
|
+
|
104
|
+
this.draggable.triggerEvent(sortableStopEvent);
|
105
|
+
|
106
|
+
this.startIndex = null;
|
107
|
+
this.offset = null;
|
108
|
+
}
|
109
|
+
}
|
110
|
+
|
111
|
+
function index(element) {
|
112
|
+
return Array.prototype.indexOf.call(element.parentNode.children, element);
|
113
|
+
}
|
114
|
+
|
115
|
+
function move(source, over, overContainer) {
|
116
|
+
const emptyOverContainer = !overContainer.children.length;
|
117
|
+
const differentContainer = over && (source.parentNode !== over.parentNode);
|
118
|
+
const sameContainer = over && (source.parentNode === over.parentNode);
|
119
|
+
|
120
|
+
if (emptyOverContainer) {
|
121
|
+
return moveInsideEmptyContainer(source, overContainer);
|
122
|
+
} else if (sameContainer) {
|
123
|
+
return moveWithinContainer(source, over);
|
124
|
+
} else if (differentContainer) {
|
125
|
+
return moveOutsideContainer(source, over);
|
126
|
+
} else {
|
127
|
+
return null;
|
128
|
+
}
|
129
|
+
}
|
130
|
+
|
131
|
+
function moveInsideEmptyContainer(source, overContainer) {
|
132
|
+
const oldContainer = source.parentNode;
|
133
|
+
const oldIndex = index(source);
|
134
|
+
|
135
|
+
overContainer.appendChild(source);
|
136
|
+
|
137
|
+
return {oldIndex, newIndex: index(source), oldContainer, newContainer: overContainer};
|
138
|
+
}
|
139
|
+
|
140
|
+
function moveWithinContainer(source, over) {
|
141
|
+
const oldIndex = index(source);
|
142
|
+
const newIndex = index(over);
|
143
|
+
|
144
|
+
if (oldIndex < newIndex) {
|
145
|
+
source.parentNode.insertBefore(source, over.nextElementSibling);
|
146
|
+
} else {
|
147
|
+
source.parentNode.insertBefore(source, over);
|
148
|
+
}
|
149
|
+
|
150
|
+
return {oldIndex, newIndex, oldContainer: source.parentNode, newContainer: source.parentNode};
|
151
|
+
}
|
152
|
+
|
153
|
+
function moveOutsideContainer(source, over) {
|
154
|
+
const oldContainer = source.parentNode;
|
155
|
+
const oldIndex = index(source);
|
156
|
+
const newIndex = index(over);
|
157
|
+
|
158
|
+
over.parentNode.insertBefore(source, over);
|
159
|
+
|
160
|
+
return {oldIndex, newIndex, oldContainer, newContainer: source.parentNode};
|
161
|
+
}
|
@@ -0,0 +1,104 @@
|
|
1
|
+
import Draggable from './draggable';
|
2
|
+
|
3
|
+
import {
|
4
|
+
SwappableStartEvent,
|
5
|
+
SwappableSwappedEvent,
|
6
|
+
SwappableStopEvent,
|
7
|
+
} from './events/swappable-event';
|
8
|
+
|
9
|
+
export default class Swappable {
|
10
|
+
constructor(containers = [], options = {}) {
|
11
|
+
this.draggable = new Draggable(containers, options);
|
12
|
+
|
13
|
+
this._onDragStart = this._onDragStart.bind(this);
|
14
|
+
this._onDragOver = this._onDragOver.bind(this);
|
15
|
+
this._onDragStop = this._onDragStop.bind(this);
|
16
|
+
|
17
|
+
this.draggable
|
18
|
+
.on('drag:start', this._onDragStart)
|
19
|
+
.on('drag:over', this._onDragOver)
|
20
|
+
.on('drag:stop', this._onDragStop);
|
21
|
+
}
|
22
|
+
|
23
|
+
destroy() {
|
24
|
+
this.draggable
|
25
|
+
.off('drag:start', this._onDragStart)
|
26
|
+
.off('drag:over', this._onDragOver)
|
27
|
+
.off('drag:stop', this._onDragStop)
|
28
|
+
.destroy();
|
29
|
+
}
|
30
|
+
|
31
|
+
on(type, callback) {
|
32
|
+
this.draggable.on(type, callback);
|
33
|
+
return this;
|
34
|
+
}
|
35
|
+
|
36
|
+
off(type, callback) {
|
37
|
+
this.draggable.off(type, callback);
|
38
|
+
return this;
|
39
|
+
}
|
40
|
+
|
41
|
+
_onDragStart(event) {
|
42
|
+
const swappableStartEvent = new SwappableStartEvent({
|
43
|
+
dragEvent: event,
|
44
|
+
});
|
45
|
+
|
46
|
+
this.draggable.triggerEvent(swappableStartEvent);
|
47
|
+
|
48
|
+
if (swappableStartEvent.canceled()) {
|
49
|
+
event.cancel();
|
50
|
+
}
|
51
|
+
}
|
52
|
+
|
53
|
+
_onDragOver(event) {
|
54
|
+
if (event.over === event.source) {
|
55
|
+
return;
|
56
|
+
}
|
57
|
+
|
58
|
+
if (event.canceled()) {
|
59
|
+
return;
|
60
|
+
}
|
61
|
+
|
62
|
+
if (this.lastOver && this.lastOver !== event.over) {
|
63
|
+
swap(this.lastOver, event.source);
|
64
|
+
}
|
65
|
+
|
66
|
+
this.lastOver = event.over;
|
67
|
+
|
68
|
+
swap(event.source, event.over);
|
69
|
+
|
70
|
+
// Let this cancel the actual swap
|
71
|
+
const swappableSwappedEvent = new SwappableSwappedEvent({
|
72
|
+
dragEvent: event,
|
73
|
+
swappedElement: event.over,
|
74
|
+
});
|
75
|
+
|
76
|
+
this.draggable.triggerEvent(swappableSwappedEvent);
|
77
|
+
}
|
78
|
+
|
79
|
+
_onDragStop(event) {
|
80
|
+
const swappableStopEvent = new SwappableStopEvent({
|
81
|
+
dragEvent: event,
|
82
|
+
});
|
83
|
+
|
84
|
+
this.draggable.triggerEvent(swappableStopEvent);
|
85
|
+
this.lastOver = null;
|
86
|
+
}
|
87
|
+
}
|
88
|
+
|
89
|
+
function withTempElement(callback) {
|
90
|
+
const tmpElement = document.createElement('div');
|
91
|
+
callback(tmpElement);
|
92
|
+
tmpElement.parentNode.removeChild(tmpElement);
|
93
|
+
}
|
94
|
+
|
95
|
+
function swap(source, over) {
|
96
|
+
const overParent = over.parentNode;
|
97
|
+
const sourceParent = source.parentNode;
|
98
|
+
|
99
|
+
withTempElement((tmpElement) => {
|
100
|
+
sourceParent.insertBefore(tmpElement, source);
|
101
|
+
overParent.insertBefore(source, over);
|
102
|
+
sourceParent.insertBefore(over, tmpElement);
|
103
|
+
});
|
104
|
+
}
|
@@ -0,0 +1,52 @@
|
|
1
|
+
/** @module utils */
|
2
|
+
|
3
|
+
export function closest(element, selector) {
|
4
|
+
if (!element) {
|
5
|
+
return null;
|
6
|
+
}
|
7
|
+
|
8
|
+
function conditionFn(currentElement) {
|
9
|
+
if (!currentElement) {
|
10
|
+
return currentElement;
|
11
|
+
} else if (typeof selector === 'string') {
|
12
|
+
const matchFunction = Element.prototype.matches ||
|
13
|
+
Element.prototype.webkitMatchesSelector ||
|
14
|
+
Element.prototype.mozMatchesSelector ||
|
15
|
+
Element.prototype.msMatchesSelector;
|
16
|
+
return matchFunction.call(currentElement, selector);
|
17
|
+
} else {
|
18
|
+
return selector(currentElement);
|
19
|
+
}
|
20
|
+
}
|
21
|
+
|
22
|
+
let current = element;
|
23
|
+
|
24
|
+
do {
|
25
|
+
current = current.correspondingUseElement || current.correspondingElement || current;
|
26
|
+
if (conditionFn(current)) {
|
27
|
+
return current;
|
28
|
+
}
|
29
|
+
current = current.parentNode;
|
30
|
+
} while (current !== document.body && current !== document);
|
31
|
+
|
32
|
+
return null;
|
33
|
+
}
|
34
|
+
|
35
|
+
let scrollAnimationFrame;
|
36
|
+
|
37
|
+
export function scroll(element, {clientX, clientY, speed, sensitivity}) {
|
38
|
+
if (scrollAnimationFrame) {
|
39
|
+
cancelAnimationFrame(scrollAnimationFrame);
|
40
|
+
}
|
41
|
+
|
42
|
+
function scrollFn() {
|
43
|
+
const rect = element.getBoundingClientRect();
|
44
|
+
const offsetY = (Math.abs(rect.bottom - clientY) <= sensitivity) - (Math.abs(rect.top - clientY) <= sensitivity);
|
45
|
+
const offsetX = (Math.abs(rect.right - clientX) <= sensitivity) - (Math.abs(rect.left - clientX) <= sensitivity);
|
46
|
+
element.scrollTop += offsetY * speed;
|
47
|
+
element.scrollLeft += offsetX * speed;
|
48
|
+
scrollAnimationFrame = requestAnimationFrame(scrollFn);
|
49
|
+
}
|
50
|
+
|
51
|
+
scrollAnimationFrame = requestAnimationFrame(scrollFn);
|
52
|
+
}
|