Skip to content

Commit 81c697c

Browse files
authored
Merge pull request #440 from zjffun/fix/drag-delay
fix: does not trigger `drag:start` sensor event when moved during delay
2 parents a6814ce + a85f134 commit 81c697c

File tree

12 files changed

+144
-30
lines changed

12 files changed

+144
-30
lines changed

index.d.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ declare module '@shopify/draggable' {
8686
? SnapOutEvent
8787
: AbstractEvent;
8888

89+
interface DelayOptions {
90+
mouse?: number;
91+
drag?: number;
92+
touch?: number;
93+
}
94+
8995
/**
9096
* DragEvent
9197
*/
@@ -167,7 +173,7 @@ declare module '@shopify/draggable' {
167173
draggable?: string;
168174
distance?: number;
169175
handle?: string | NodeList | HTMLElement[] | HTMLElement | ((currentElement: HTMLElement) => HTMLElement);
170-
delay?: number;
176+
delay?: number | DelayOptions;
171177
plugins?: Array<typeof AbstractPlugin>;
172178
sensors?: Sensor[];
173179
classes?: { [key in DraggableClassNames]: string };
@@ -315,7 +321,7 @@ declare module '@shopify/draggable' {
315321
export class DragPressureSensorEvent extends SensorEvent { }
316322

317323
export interface SensorOptions {
318-
delay?: number;
324+
delay?: number | DelayOptions;
319325
}
320326

321327
export class Sensor {

scripts/test/helpers/sensor.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
import {DRAG_DELAY, defaultTouchEventOptions, defaultMouseEventOptions} from './constants';
22
import {triggerEvent} from './event';
33

4-
export function waitForDragDelay(dragDelay = DRAG_DELAY) {
4+
export function waitForDragDelay({dragDelay = DRAG_DELAY, restoreDateMock = true} = {}) {
55
const next = Date.now() + dragDelay + 1;
66
const dateMock = jest.spyOn(Date, 'now').mockImplementation(() => {
77
return next;
88
});
99
jest.runTimersToTime(dragDelay + 1);
10-
dateMock.mockRestore();
10+
if (restoreDateMock) {
11+
dateMock.mockRestore();
12+
}
13+
return dateMock;
1114
}
1215

1316
export function clickMouse(element, options = {}) {

src/Draggable/Draggable.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ const defaultClasses = {
4848
export const defaultOptions = {
4949
draggable: '.draggable-source',
5050
handle: null,
51-
delay: 100,
51+
delay: {},
5252
distance: 0,
5353
placedTimeout: 800,
5454
plugins: [],

src/Draggable/README.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,19 @@ look for an element with `.draggable-source` class. Default: `.draggable-source`
9191
Specify a css selector for a handle element if you don't want to allow drag action
9292
on the entire element. Default: `null`
9393

94-
**`delay {Number}`**
94+
**`delay {Number|Object}`**
9595
If you want to delay a drag start you can specify delay in milliseconds. This can be useful
96-
for draggable elements within scrollable containers. Default: `100`
96+
for draggable elements within scrollable containers. To allow touch scrolling, we set 100ms delay for TouchSensor by default. Default:
97+
98+
```js
99+
{
100+
mouse: 0,
101+
drag: 0,
102+
touch: 100,
103+
}
104+
```
105+
106+
You can set the same delay for all sensors by setting a number, or set an object to set the delay for each sensor separately.
97107

98108
**`distance {Number}`**
99109
The distance you want the pointer to have moved before drag starts. This can be useful

src/Draggable/Sensors/DragSensor/DragSensor.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ export default class DragSensor extends Sensor {
211211
this.mouseDownTimeout = setTimeout(() => {
212212
target.draggable = true;
213213
this.draggableElement = target;
214-
}, this.options.delay);
214+
}, this.delay.drag);
215215
}
216216

217217
/**

src/Draggable/Sensors/MouseSensor/MouseSensor.js

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export default class MouseSensor extends Sensor {
8383
return;
8484
}
8585

86-
const {delay = 0} = this.options;
86+
const {delay} = this;
8787
const {pageX, pageY} = event;
8888

8989
Object.assign(this, {pageX, pageY});
@@ -97,7 +97,7 @@ export default class MouseSensor extends Sensor {
9797

9898
this.mouseDownTimeout = window.setTimeout(() => {
9999
this[onDistanceChange]({pageX: this.pageX, pageY: this.pageY});
100-
}, delay);
100+
}, delay.mouse);
101101
}
102102

103103
/**
@@ -134,8 +134,8 @@ export default class MouseSensor extends Sensor {
134134
*/
135135
[onDistanceChange](event) {
136136
const {pageX, pageY} = event;
137-
const {delay, distance} = this.options;
138-
const {startEvent} = this;
137+
const {distance} = this.options;
138+
const {startEvent, delay} = this;
139139

140140
Object.assign(this, {pageX, pageY});
141141

@@ -146,8 +146,12 @@ export default class MouseSensor extends Sensor {
146146
const timeElapsed = Date.now() - this.onMouseDownAt;
147147
const distanceTravelled = euclideanDistance(startEvent.pageX, startEvent.pageY, pageX, pageY) || 0;
148148

149-
if (timeElapsed >= delay && distanceTravelled >= distance) {
150-
window.clearTimeout(this.mouseDownTimeout);
149+
clearTimeout(this.mouseDownTimeout);
150+
151+
if (timeElapsed < delay.mouse) {
152+
// moved during delay
153+
document.removeEventListener('mousemove', this[onDistanceChange]);
154+
} else if (distanceTravelled >= distance) {
151155
document.removeEventListener('mousemove', this[onDistanceChange]);
152156
this[startDrag]();
153157
}

src/Draggable/Sensors/MouseSensor/tests/MouseSensor.test.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -251,14 +251,17 @@ describe('MouseSensor', () => {
251251
expect(dragFlow).not.toHaveTriggeredSensorEvent('drag:start');
252252
});
253253

254-
it('only triggers `drag:start` sensor event once when delay ends after distance is met', () => {
254+
it('does not trigger `drag:start` sensor event when moved during delay', () => {
255255
function dragFlow() {
256256
clickMouse(draggableElement);
257257
moveMouse(draggableElement, {pageY: 1, pageX: 0});
258+
const dateMock = waitForDragDelay({restoreDateMock: false});
259+
moveMouse(draggableElement, {pageY: 2, pageX: 0});
258260
waitForDragDelay();
259261
releaseMouse(document.body);
262+
dateMock.mockRestore();
260263
}
261-
expect(dragFlow).toHaveTriggeredSensorEvent('drag:start', 1);
264+
expect(dragFlow).not.toHaveTriggeredSensorEvent('drag:start', 1);
262265
});
263266

264267
it('only triggers `drag:start` sensor event once when distance and delay are met at the same time', () => {

src/Draggable/Sensors/Sensor/Sensor.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
const defaultDealy = {
2+
mouse: 0,
3+
drag: 0,
4+
touch: 100,
5+
};
6+
17
/**
28
* Base sensor class. Extend from this class to create a new or custom sensor
39
* @class Sensor
@@ -45,6 +51,13 @@ export default class Sensor {
4551
* @type {Event}
4652
*/
4753
this.startEvent = null;
54+
55+
/**
56+
* The delay of each sensor
57+
* @property delay
58+
* @type {Object}
59+
*/
60+
this.delay = calcDelay(options.delay);
4861
}
4962

5063
/**
@@ -96,3 +109,37 @@ export default class Sensor {
96109
return sensorEvent;
97110
}
98111
}
112+
113+
/**
114+
* Calculate the delay of each sensor through the delay in the options
115+
* @param {undefined|Number|Object} optionsDelay - the delay in the options
116+
* @return {Object}
117+
*/
118+
function calcDelay(optionsDelay) {
119+
const delay = {};
120+
121+
if (optionsDelay === undefined) {
122+
return {...defaultDealy};
123+
}
124+
125+
if (typeof optionsDelay === 'number') {
126+
for (const key in defaultDealy) {
127+
if (defaultDealy.hasOwnProperty(key)) {
128+
delay[key] = optionsDelay;
129+
}
130+
}
131+
return delay;
132+
}
133+
134+
for (const key in defaultDealy) {
135+
if (defaultDealy.hasOwnProperty(key)) {
136+
if (optionsDelay[key] === undefined) {
137+
delay[key] = defaultDealy[key];
138+
} else {
139+
delay[key] = optionsDelay[key];
140+
}
141+
}
142+
}
143+
144+
return delay;
145+
}

src/Draggable/Sensors/Sensor/tests/Sensor.test.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,38 @@ describe('Sensor', () => {
1717
expect(sensor.containers).toEqual(expectedContainers);
1818
expect(sensor.options).toEqual(expectedOptions);
1919
});
20+
21+
describe('should initialize with correct delay', () => {
22+
it('unset', () => {
23+
const sensor = new Sensor(undefined, {});
24+
25+
expect(sensor.delay).toEqual({
26+
mouse: 0,
27+
drag: 0,
28+
touch: 100,
29+
});
30+
});
31+
32+
it('number', () => {
33+
const sensor = new Sensor(undefined, {delay: 42});
34+
35+
expect(sensor.delay).toEqual({
36+
mouse: 42,
37+
drag: 42,
38+
touch: 42,
39+
});
40+
});
41+
42+
it('object', () => {
43+
const sensor = new Sensor(undefined, {delay: {mouse: 42, drag: 142}});
44+
45+
expect(sensor.delay).toEqual({
46+
mouse: 42,
47+
drag: 142,
48+
touch: 100,
49+
});
50+
});
51+
});
2052
});
2153

2254
describe('#attach', () => {

src/Draggable/Sensors/TouchSensor/TouchSensor.js

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@ export default class TouchSensor extends Sensor {
111111
if (!container) {
112112
return;
113113
}
114-
const {distance = 0, delay = 0} = this.options;
114+
const {distance = 0} = this.options;
115+
const {delay} = this;
115116
const {pageX, pageY} = touchCoords(event);
116117

117118
Object.assign(this, {pageX, pageY});
@@ -130,7 +131,7 @@ export default class TouchSensor extends Sensor {
130131

131132
this.tapTimeout = window.setTimeout(() => {
132133
this[onDistanceChange]({touches: [{pageX: this.pageX, pageY: this.pageY}]});
133-
}, delay);
134+
}, delay.touch);
134135
}
135136

136137
/**
@@ -166,16 +167,21 @@ export default class TouchSensor extends Sensor {
166167
* @param {Event} event - Touch move event
167168
*/
168169
[onDistanceChange](event) {
169-
const {delay, distance} = this.options;
170-
const {startEvent} = this;
170+
const {distance} = this.options;
171+
const {startEvent, delay} = this;
171172
const start = touchCoords(startEvent);
172173
const current = touchCoords(event);
173174
const timeElapsed = Date.now() - this.onTouchStartAt;
174175
const distanceTravelled = euclideanDistance(start.pageX, start.pageY, current.pageX, current.pageY);
175176

176177
Object.assign(this, current);
177-
if (timeElapsed >= delay && distanceTravelled >= distance) {
178-
window.clearTimeout(this.tapTimeout);
178+
179+
clearTimeout(this.tapTimeout);
180+
181+
if (timeElapsed < delay.touch) {
182+
// moved during delay
183+
document.removeEventListener('touchmove', this[onDistanceChange]);
184+
} else if (distanceTravelled >= distance) {
179185
document.removeEventListener('touchmove', this[onDistanceChange]);
180186
this[startDrag]();
181187
}

0 commit comments

Comments
 (0)