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.
Files changed (28) hide show
  1. checksums.yaml +4 -4
  2. data/lib/draggable/rails/version.rb +1 -1
  3. data/vendor/assets/javascripts/behaviour/collidable.js +99 -0
  4. data/vendor/assets/javascripts/behaviour/snappable.js +107 -0
  5. data/vendor/assets/javascripts/core/accessibility.js +58 -0
  6. data/vendor/assets/javascripts/core/mirror.js +129 -0
  7. data/vendor/assets/javascripts/draggable.js +487 -0
  8. data/vendor/assets/javascripts/droppable.js +168 -0
  9. data/vendor/assets/javascripts/events/abstract-event.js +45 -0
  10. data/vendor/assets/javascripts/events/collidable-event.js +25 -0
  11. data/vendor/assets/javascripts/events/drag-event.js +91 -0
  12. data/vendor/assets/javascripts/events/draggable-event.js +17 -0
  13. data/vendor/assets/javascripts/events/droppable-event.js +21 -0
  14. data/vendor/assets/javascripts/events/mirror-event.js +43 -0
  15. data/vendor/assets/javascripts/events/sensor-event.js +47 -0
  16. data/vendor/assets/javascripts/events/snappable-event.js +15 -0
  17. data/vendor/assets/javascripts/events/sortable-event.js +35 -0
  18. data/vendor/assets/javascripts/events/swappable-event.js +23 -0
  19. data/vendor/assets/javascripts/index.js +18 -0
  20. data/vendor/assets/javascripts/sensors/drag-sensor.js +158 -0
  21. data/vendor/assets/javascripts/sensors/force-touch-sensor.js +166 -0
  22. data/vendor/assets/javascripts/sensors/mouse-sensor.js +110 -0
  23. data/vendor/assets/javascripts/sensors/sensor.js +23 -0
  24. data/vendor/assets/javascripts/sensors/touch-sensor.js +145 -0
  25. data/vendor/assets/javascripts/sortable.js +161 -0
  26. data/vendor/assets/javascripts/swappable.js +104 -0
  27. data/vendor/assets/javascripts/utils.js +52 -0
  28. 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
+ }