0
# Sensors
1
2
Sensors detect and handle different types of input events (mouse, touch, keyboard, native drag) and convert them into unified sensor events that the draggable system can process. They provide a pluggable architecture for supporting various input methods.
3
4
## Capabilities
5
6
### Base Sensor Class
7
8
All sensors extend the base Sensor class which provides common functionality.
9
10
```typescript { .api }
11
/**
12
* Base sensor class for input detection and handling
13
*/
14
class Sensor {
15
constructor(containers: HTMLElement | HTMLElement[] | NodeList, options?: SensorOptions);
16
attach(): this;
17
detach(): this;
18
addContainer(...containers: HTMLElement[]): void;
19
removeContainer(...containers: HTMLElement[]): void;
20
trigger(element: HTMLElement, sensorEvent: SensorEvent): SensorEvent;
21
}
22
23
interface SensorOptions {
24
delay?: number | DelayOptions;
25
}
26
```
27
28
**Usage Example:**
29
30
```typescript
31
import { MouseSensor, TouchSensor } from "@shopify/draggable";
32
33
// Create custom sensor configuration
34
const containers = document.querySelectorAll('.container');
35
const mouseSensor = new MouseSensor(containers, {
36
delay: { mouse: 100 }
37
});
38
39
const touchSensor = new TouchSensor(containers, {
40
delay: { touch: 200 }
41
});
42
43
// Use with draggable
44
const draggable = new Draggable(containers, {
45
sensors: [mouseSensor, touchSensor]
46
});
47
```
48
49
### Built-in Sensors
50
51
Draggable includes several built-in sensor implementations.
52
53
```typescript { .api }
54
// Available through Draggable.Sensors static property
55
interface Sensors {
56
DragSensor: typeof DragSensor;
57
MouseSensor: typeof MouseSensor;
58
TouchSensor: typeof TouchSensor;
59
ForceTouchSensor: typeof ForceTouchSensor;
60
}
61
62
class MouseSensor extends Sensor {}
63
class TouchSensor extends Sensor {}
64
class DragSensor extends Sensor {}
65
class ForceTouchSensor extends Sensor {}
66
```
67
68
**Built-in Sensors:**
69
70
- **MouseSensor**: Handles mouse click and drag events
71
- **TouchSensor**: Handles touch events for mobile devices
72
- **DragSensor**: Handles native HTML5 drag and drop events
73
- **ForceTouchSensor**: Handles force touch/pressure-sensitive input
74
75
**Usage Example:**
76
77
```typescript
78
import { Draggable } from "@shopify/draggable";
79
80
// Access built-in sensors
81
const { MouseSensor, TouchSensor, ForceTouchSensor } = Draggable.Sensors;
82
83
const draggable = new Draggable(containers, {
84
sensors: [MouseSensor, TouchSensor, ForceTouchSensor],
85
exclude: {
86
sensors: [TouchSensor] // Exclude touch on desktop
87
}
88
});
89
```
90
91
### Sensor Events
92
93
Sensors generate unified sensor events that contain input information.
94
95
```typescript { .api }
96
class SensorEvent extends AbstractEvent {
97
readonly originalEvent: Event;
98
readonly clientX: number;
99
readonly clientY: number;
100
readonly target: HTMLElement;
101
readonly container: HTMLElement;
102
readonly pressure: number;
103
}
104
105
class DragStartSensorEvent extends SensorEvent {}
106
class DragMoveSensorEvent extends SensorEvent {}
107
class DragStopSensorEvent extends SensorEvent {}
108
class DragPressureSensorEvent extends SensorEvent {}
109
```
110
111
**Usage Example:**
112
113
```typescript
114
draggable.on('drag:move', (event) => {
115
const sensorEvent = event.sensorEvent;
116
console.log('Mouse/touch position:', sensorEvent.clientX, sensorEvent.clientY);
117
console.log('Pressure:', sensorEvent.pressure);
118
console.log('Original browser event:', sensorEvent.originalEvent);
119
});
120
```
121
122
### Sensor Management
123
124
Methods for managing sensors on draggable instances.
125
126
```typescript { .api }
127
/**
128
* Add sensors to a draggable instance
129
* @param sensors - Sensor classes to add
130
*/
131
addSensor(...sensors: (typeof Sensor)[]): this;
132
133
/**
134
* Remove sensors from a draggable instance
135
* @param sensors - Sensor classes to remove
136
*/
137
removeSensor(...sensors: (typeof Sensor)[]): this;
138
```
139
140
**Usage Example:**
141
142
```typescript
143
const draggable = new Draggable(containers);
144
145
// Add additional sensors
146
draggable.addSensor(Draggable.Sensors.ForceTouchSensor);
147
148
// Remove default sensors
149
draggable.removeSensor(Draggable.Sensors.TouchSensor);
150
151
// Add custom sensor
152
class KeyboardSensor extends Sensor {
153
attach() {
154
// Custom keyboard handling
155
document.addEventListener('keydown', this.onKeyDown);
156
return this;
157
}
158
159
detach() {
160
document.removeEventListener('keydown', this.onKeyDown);
161
return this;
162
}
163
164
onKeyDown = (event) => {
165
// Custom keyboard drag logic
166
}
167
}
168
169
draggable.addSensor(KeyboardSensor);
170
```
171
172
### Container Management
173
174
Sensors can dynamically manage which containers they monitor.
175
176
```typescript { .api }
177
/**
178
* Add containers to sensor monitoring
179
* @param containers - Container elements to add
180
*/
181
addContainer(...containers: HTMLElement[]): void;
182
183
/**
184
* Remove containers from sensor monitoring
185
* @param containers - Container elements to remove
186
*/
187
removeContainer(...containers: HTMLElement[]): void;
188
```
189
190
**Usage Example:**
191
192
```typescript
193
const mouseSensor = new MouseSensor([container1, container2]);
194
195
// Add more containers later
196
mouseSensor.addContainer(container3, container4);
197
198
// Remove a container
199
mouseSensor.removeContainer(container1);
200
```
201
202
## Custom Sensor Implementation
203
204
```typescript
205
class CustomSensor extends Sensor {
206
constructor(containers, options = {}) {
207
super(containers, options);
208
this.handleStart = this.handleStart.bind(this);
209
this.handleMove = this.handleMove.bind(this);
210
this.handleEnd = this.handleEnd.bind(this);
211
}
212
213
attach() {
214
// Add event listeners to containers
215
this.containers.forEach(container => {
216
container.addEventListener('customstart', this.handleStart);
217
container.addEventListener('custommove', this.handleMove);
218
container.addEventListener('customend', this.handleEnd);
219
});
220
return this;
221
}
222
223
detach() {
224
// Remove event listeners
225
this.containers.forEach(container => {
226
container.removeEventListener('customstart', this.handleStart);
227
container.removeEventListener('custommove', this.handleMove);
228
container.removeEventListener('customend', this.handleEnd);
229
});
230
return this;
231
}
232
233
handleStart(event) {
234
const sensorEvent = new DragStartSensorEvent({
235
originalEvent: event,
236
clientX: event.clientX || 0,
237
clientY: event.clientY || 0,
238
target: event.target,
239
container: event.currentTarget,
240
pressure: 0
241
});
242
243
this.trigger(event.target, sensorEvent);
244
}
245
246
handleMove(event) {
247
const sensorEvent = new DragMoveSensorEvent({
248
originalEvent: event,
249
clientX: event.clientX || 0,
250
clientY: event.clientY || 0,
251
target: event.target,
252
container: event.currentTarget,
253
pressure: 0
254
});
255
256
this.trigger(event.target, sensorEvent);
257
}
258
259
handleEnd(event) {
260
const sensorEvent = new DragStopSensorEvent({
261
originalEvent: event,
262
clientX: event.clientX || 0,
263
clientY: event.clientY || 0,
264
target: event.target,
265
container: event.currentTarget,
266
pressure: 0
267
});
268
269
this.trigger(event.target, sensorEvent);
270
}
271
}
272
273
// Use custom sensor
274
const draggable = new Draggable(containers, {
275
sensors: [CustomSensor]
276
});
277
```
278
279
## Sensor Configuration
280
281
```typescript
282
// Configure sensor delays
283
const draggable = new Draggable(containers, {
284
delay: {
285
mouse: 150, // 150ms delay for mouse
286
touch: 200, // 200ms delay for touch
287
drag: 0 // No delay for native drag
288
}
289
});
290
291
// Or configure per sensor
292
const mouseSensor = new MouseSensor(containers, {
293
delay: { mouse: 100 }
294
});
295
296
const touchSensor = new TouchSensor(containers, {
297
delay: { touch: 300 }
298
});
299
```
300
301
## Complete Sensor Example
302
303
```typescript
304
import { Draggable } from "@shopify/draggable";
305
306
// Custom gesture sensor for advanced touch interactions
307
class GestureSensor extends Sensor {
308
constructor(containers, options) {
309
super(containers, options);
310
this.touches = new Map();
311
this.gestureThreshold = options.gestureThreshold || 50;
312
}
313
314
attach() {
315
this.containers.forEach(container => {
316
container.addEventListener('touchstart', this.onTouchStart, { passive: false });
317
container.addEventListener('touchmove', this.onTouchMove, { passive: false });
318
container.addEventListener('touchend', this.onTouchEnd);
319
});
320
return this;
321
}
322
323
detach() {
324
this.containers.forEach(container => {
325
container.removeEventListener('touchstart', this.onTouchStart);
326
container.removeEventListener('touchmove', this.onTouchMove);
327
container.removeEventListener('touchend', this.onTouchEnd);
328
});
329
return this;
330
}
331
332
onTouchStart = (event) => {
333
// Track multiple touches for gesture detection
334
Array.from(event.touches).forEach(touch => {
335
this.touches.set(touch.identifier, {
336
startX: touch.clientX,
337
startY: touch.clientY,
338
currentX: touch.clientX,
339
currentY: touch.clientY
340
});
341
});
342
343
if (event.touches.length === 1) {
344
// Single touch - start drag
345
const touch = event.touches[0];
346
const sensorEvent = new DragStartSensorEvent({
347
originalEvent: event,
348
clientX: touch.clientX,
349
clientY: touch.clientY,
350
target: event.target,
351
container: event.currentTarget,
352
pressure: touch.force || 0
353
});
354
355
this.trigger(event.target, sensorEvent);
356
}
357
};
358
359
onTouchMove = (event) => {
360
if (event.touches.length === 1) {
361
// Single touch - continue drag
362
const touch = event.touches[0];
363
const sensorEvent = new DragMoveSensorEvent({
364
originalEvent: event,
365
clientX: touch.clientX,
366
clientY: touch.clientY,
367
target: event.target,
368
container: event.currentTarget,
369
pressure: touch.force || 0
370
});
371
372
this.trigger(event.target, sensorEvent);
373
} else if (event.touches.length === 2) {
374
// Two touches - handle pinch/zoom gesture
375
this.handlePinchGesture(event);
376
}
377
};
378
379
onTouchEnd = (event) => {
380
// Clean up touch tracking
381
Array.from(event.changedTouches).forEach(touch => {
382
this.touches.delete(touch.identifier);
383
});
384
385
if (event.touches.length === 0) {
386
// No more touches - end drag
387
const touch = event.changedTouches[0];
388
const sensorEvent = new DragStopSensorEvent({
389
originalEvent: event,
390
clientX: touch.clientX,
391
clientY: touch.clientY,
392
target: event.target,
393
container: event.currentTarget,
394
pressure: 0
395
});
396
397
this.trigger(event.target, sensorEvent);
398
}
399
};
400
401
handlePinchGesture(event) {
402
// Custom pinch gesture logic
403
const [touch1, touch2] = event.touches;
404
const distance = Math.sqrt(
405
Math.pow(touch2.clientX - touch1.clientX, 2) +
406
Math.pow(touch2.clientY - touch1.clientY, 2)
407
);
408
409
// Emit custom gesture event
410
const gestureEvent = new CustomEvent('pinch', {
411
detail: { distance, touches: [touch1, touch2] }
412
});
413
event.target.dispatchEvent(gestureEvent);
414
}
415
}
416
417
// Use the custom sensor
418
const draggable = new Draggable(containers, {
419
sensors: [GestureSensor],
420
gestureThreshold: 75
421
});
422
```