draggable-rails 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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
+ }