prosthetic-hand 2.0.0 → 2.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.
- package/README.md +1 -1
- package/lib/Finger.js +11 -6
- package/lib/Hand.js +87 -47
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ npm install prosthetic-hand
|
|
|
15
15
|
import Hand from 'prosthetic-hand';
|
|
16
16
|
|
|
17
17
|
const hand = new Hand();
|
|
18
|
-
const finger =
|
|
18
|
+
const finger = hand.growFinger('pointer', { pointerType: 'touch', pressure: 0.9 });
|
|
19
19
|
|
|
20
20
|
finger
|
|
21
21
|
.wait(500)
|
package/lib/Finger.js
CHANGED
|
@@ -6,7 +6,7 @@ import * as capabilities from './Capabilities.js';
|
|
|
6
6
|
// Self-incrementing identifier for touch ID and pointer ID.
|
|
7
7
|
// Fingers can either keep the same ID for their life, or request a new
|
|
8
8
|
// ID whenever they go down.
|
|
9
|
-
var fingerIdSequence =
|
|
9
|
+
var fingerIdSequence = 1000;
|
|
10
10
|
|
|
11
11
|
// 🖐️class Finger
|
|
12
12
|
// Represents a finger, capable of performing single touch/pointer/mouse synthetic
|
|
@@ -199,7 +199,7 @@ export default class Finger {
|
|
|
199
199
|
// 🖐️method moveTo(x: Number, y: Number, delay: Number, options?: {}): this
|
|
200
200
|
// Queues moving this finger to an absolute position at `(x, y)`; the
|
|
201
201
|
// movement will last for `delay` milliseconds.
|
|
202
|
-
moveTo(x, y, delay) {
|
|
202
|
+
moveTo(x, y, delay=0) {
|
|
203
203
|
return this.moveBy(x - this._finalState.x, y - this._finalState.y, delay);
|
|
204
204
|
}
|
|
205
205
|
|
|
@@ -207,7 +207,7 @@ export default class Finger {
|
|
|
207
207
|
// 🖐️method moveBy(x: Number, y: Number, delay: Number, options?: {}): this
|
|
208
208
|
// Queues a move of this finger to an position relative to its last position
|
|
209
209
|
// plus`(x, y)`; the movement will last for `delay` milliseconds.
|
|
210
|
-
moveBy(x, y, delay) {
|
|
210
|
+
moveBy(x, y, delay=0) {
|
|
211
211
|
var fromX = this._finalState.x;
|
|
212
212
|
var fromY = this._finalState.y;
|
|
213
213
|
|
|
@@ -280,7 +280,7 @@ export default class Finger {
|
|
|
280
280
|
* as fas as to `performance.now()`), then checks if the state has changed
|
|
281
281
|
* and means an event should be fired.
|
|
282
282
|
*
|
|
283
|
-
* If `justOne` is set to truthy, then this will run just one
|
|
283
|
+
* If `justOne` is set to truthy, then this will run just one movement.
|
|
284
284
|
* Otherwise, it will run as many movements as needed until `timestamp` is reached.
|
|
285
285
|
*
|
|
286
286
|
* Returns an array of objects of the form `{type: 'foo', event: MouseEvent(...), finger: Finger}`
|
|
@@ -334,7 +334,10 @@ export default class Finger {
|
|
|
334
334
|
|
|
335
335
|
if (previousState.x !== this._state.x || previousState.y !== this._state.y) {
|
|
336
336
|
evType = 'move'
|
|
337
|
-
|
|
337
|
+
|
|
338
|
+
this._currentTarget =
|
|
339
|
+
this._hand._capturedTargets.get(this._id) ??
|
|
340
|
+
document.elementFromPoint(this._state.x, this._state.y);
|
|
338
341
|
}
|
|
339
342
|
/// TODO: Detect over/out events when the event target changes.
|
|
340
343
|
|
|
@@ -345,7 +348,9 @@ export default class Finger {
|
|
|
345
348
|
// TODO: Optionally reset the finger ID and grab a fresh one
|
|
346
349
|
|
|
347
350
|
this._graphic.style.display = 'block';
|
|
348
|
-
this._touchTargetWhenDowned = this._currentTarget =
|
|
351
|
+
this._touchTargetWhenDowned = this._currentTarget =
|
|
352
|
+
this._hand._capturedTargets.get(this._id) ??
|
|
353
|
+
document.elementFromPoint(this._state.x, this._state.y);
|
|
349
354
|
evType = 'down';
|
|
350
355
|
}
|
|
351
356
|
|
package/lib/Hand.js
CHANGED
|
@@ -3,11 +3,11 @@ import Finger from './Finger.js';
|
|
|
3
3
|
import * as capabilities from './Capabilities.js';
|
|
4
4
|
|
|
5
5
|
const TimingMode = {
|
|
6
|
-
Interval: 'INTERVAL',
|
|
7
|
-
Minimal: 'MINIMAL',
|
|
8
|
-
Instant: 'INSTANT',
|
|
9
|
-
Frame: 'FRAME',
|
|
10
|
-
FastFrame: 'FAST_FRAME'
|
|
6
|
+
Interval: Symbol('INTERVAL'),
|
|
7
|
+
Minimal: Symbol('MINIMAL'),
|
|
8
|
+
Instant: Symbol('INSTANT'),
|
|
9
|
+
Frame: Symbol('FRAME'),
|
|
10
|
+
FastFrame: Symbol('FAST_FRAME')
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
// 🖐️class Hand
|
|
@@ -21,20 +21,23 @@ var h = new Hand({ timing: '20ms' });
|
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
*/
|
|
24
|
-
export default class Hand {
|
|
24
|
+
export default class Hand extends EventTarget {
|
|
25
|
+
|
|
26
|
+
#fingers = [];
|
|
27
|
+
#fingersAreIdle = true;
|
|
25
28
|
|
|
26
29
|
// 🖐️factory Hand(options?: Hand options): Hand
|
|
27
30
|
// Instantiates a new `Hand` with the given options.
|
|
28
31
|
constructor(options) {
|
|
29
|
-
|
|
32
|
+
super();
|
|
30
33
|
if (!options) {
|
|
31
34
|
options = {};
|
|
32
35
|
}
|
|
33
36
|
|
|
34
|
-
this
|
|
35
|
-
|
|
36
|
-
this._fingersAreIdle = true;
|
|
37
|
+
this.#fingers = [];
|
|
37
38
|
|
|
39
|
+
this.#fingersAreIdle = true;
|
|
40
|
+
this._capturedTargets = new Map(),
|
|
38
41
|
|
|
39
42
|
/// TODO: Timing modes: minimal, interval, frames
|
|
40
43
|
|
|
@@ -99,18 +102,20 @@ export default class Hand {
|
|
|
99
102
|
// If set to a callback function, it will be called (with the `Hand`
|
|
100
103
|
// as its only argument) whenever the movements start.
|
|
101
104
|
if (options.onStart) {
|
|
102
|
-
this.
|
|
105
|
+
this.addEventListener('start', options.onStart);
|
|
106
|
+
// this._onStart = options.onStart;
|
|
103
107
|
}
|
|
104
108
|
|
|
105
109
|
// 🖐️option onStop: Function
|
|
106
110
|
// If set to a callback function, it will be called (with the `Hand`
|
|
107
111
|
// as its only argument) whenever the movements are completed.
|
|
108
112
|
if (options.onStop) {
|
|
109
|
-
this.
|
|
113
|
+
this.addEventListener('stop', options.onStop);
|
|
114
|
+
// this._onStop = options.onStop;
|
|
110
115
|
}
|
|
111
116
|
|
|
112
117
|
|
|
113
|
-
// Cancellable reference to the next call to
|
|
118
|
+
// Cancellable reference to the next call to `#dispatchEvents`. This
|
|
114
119
|
// might be either a `setTimeout` reference or a `requestAnimationFrame`
|
|
115
120
|
// reference.
|
|
116
121
|
this._nextDispatch = null;
|
|
@@ -129,7 +134,7 @@ export default class Hand {
|
|
|
129
134
|
|
|
130
135
|
var finger = new Finger(fingerMode, options);
|
|
131
136
|
|
|
132
|
-
this.
|
|
137
|
+
this.#fingers.push(finger);
|
|
133
138
|
return finger;
|
|
134
139
|
}
|
|
135
140
|
|
|
@@ -143,19 +148,14 @@ export default class Hand {
|
|
|
143
148
|
|
|
144
149
|
/// TODO: Start up the event loop
|
|
145
150
|
|
|
146
|
-
if (this
|
|
151
|
+
if (this.#fingersAreIdle) {
|
|
147
152
|
// 🖐️section
|
|
148
|
-
// Use `
|
|
149
|
-
//
|
|
150
|
-
// 🖐️event prostheticHandStart: CustomEvent
|
|
153
|
+
// Use `hand.addEventListener('stop', fn)` to do stuff with it.
|
|
154
|
+
// 🖐️event start: CustomEvent
|
|
151
155
|
// Fired when all movements are complete.
|
|
152
|
-
|
|
156
|
+
this.dispatchEvent(new CustomEvent('start', {target: this}));
|
|
153
157
|
|
|
154
|
-
|
|
155
|
-
this._onStart(this);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
this._fingersAreIdle = false;
|
|
158
|
+
this.#fingersAreIdle = false;
|
|
159
159
|
this._scheduleNextDispatch();
|
|
160
160
|
}
|
|
161
161
|
|
|
@@ -166,9 +166,8 @@ export default class Hand {
|
|
|
166
166
|
// Used by this hand's fingers to signal that one finger has finished doing
|
|
167
167
|
// all the queued movements.
|
|
168
168
|
fingerIsIdle() {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
this._fingersAreIdle = true;
|
|
169
|
+
if (this.#fingers.every( f => f.isIdle())) {
|
|
170
|
+
this.#fingersAreIdle = true;
|
|
172
171
|
}
|
|
173
172
|
}
|
|
174
173
|
|
|
@@ -181,7 +180,7 @@ export default class Hand {
|
|
|
181
180
|
|
|
182
181
|
var endTimestamp = performance.now();
|
|
183
182
|
|
|
184
|
-
this.
|
|
183
|
+
this.#fingers.forEach( f => {
|
|
185
184
|
var movesUntil = f._movesUntil;
|
|
186
185
|
if (movesUntil) {
|
|
187
186
|
endTimestamp = Math.max(endTimestamp, movesUntil);
|
|
@@ -190,25 +189,31 @@ export default class Hand {
|
|
|
190
189
|
|
|
191
190
|
var waitUntil = endTimestamp + delay;
|
|
192
191
|
|
|
193
|
-
this.
|
|
192
|
+
this.#fingers.forEach( f => {
|
|
194
193
|
f.waitUntil(waitUntil);
|
|
195
194
|
});
|
|
196
195
|
|
|
197
196
|
}
|
|
198
197
|
|
|
199
198
|
|
|
199
|
+
// 🖐️method run(): Promise to this
|
|
200
|
+
// Returns a `Promise` that resolves when all fingers are idle.
|
|
201
|
+
async run() {
|
|
202
|
+
return new Promise((res)=>{this.addEventListener('stop', res)})
|
|
203
|
+
}
|
|
204
|
+
|
|
200
205
|
|
|
201
206
|
|
|
202
|
-
// 🖐️method
|
|
207
|
+
// 🖐️method private#dispatchEvents(): this
|
|
203
208
|
// Updates all the fingers, fetching their events/touchpoints, and dispatches
|
|
204
209
|
// all `Event`s triggered by the update.
|
|
205
210
|
// This is meant to be called on an internal timer.
|
|
206
|
-
|
|
211
|
+
#dispatchEvents(timestamp) {
|
|
207
212
|
|
|
208
|
-
// 🖐️event
|
|
213
|
+
// 🖐️event tick: CustomEvent
|
|
209
214
|
// Fired a movement is about to start, just before the mouse/touch/pointer
|
|
210
215
|
// events are fired.
|
|
211
|
-
|
|
216
|
+
this.dispatchEvent(new CustomEvent('tick', {target: this}));
|
|
212
217
|
|
|
213
218
|
|
|
214
219
|
var now = timestamp || performance.now();
|
|
@@ -225,7 +230,7 @@ export default class Hand {
|
|
|
225
230
|
this._timingMode === TimingMode.Instant ||
|
|
226
231
|
this._timingMode === TimingMode.FastFrame;
|
|
227
232
|
|
|
228
|
-
this.
|
|
233
|
+
this.#fingers.forEach(f=> {
|
|
229
234
|
|
|
230
235
|
var evs = f.getEvents(now, fast);
|
|
231
236
|
|
|
@@ -261,9 +266,20 @@ export default class Hand {
|
|
|
261
266
|
// Fire all `MouseEvent`s and `PointerEvent`s
|
|
262
267
|
events.forEach( ev => {
|
|
263
268
|
// console.log('Dispatching: ', ev.type);
|
|
264
|
-
|
|
265
|
-
|
|
269
|
+
let el;
|
|
270
|
+
if (this._capturedTargets.has(ev.pointerId)) {
|
|
271
|
+
el = this._capturedTargets.get(ev.pointerId);
|
|
272
|
+
} else {
|
|
273
|
+
el = document.elementFromPoint(ev.clientX, ev.clientY);
|
|
274
|
+
el && this._hookProstheticCapture(el);
|
|
275
|
+
}
|
|
266
276
|
|
|
277
|
+
if (el) {
|
|
278
|
+
el.dispatchEvent(ev);
|
|
279
|
+
} else {
|
|
280
|
+
console.warning('No target for prosthetic-hand event');
|
|
281
|
+
}
|
|
282
|
+
});
|
|
267
283
|
|
|
268
284
|
/// Build *ONE* `TouchEvent` with `TouchList`s built with
|
|
269
285
|
/// the fingers' touches.
|
|
@@ -445,11 +461,11 @@ export default class Hand {
|
|
|
445
461
|
|
|
446
462
|
|
|
447
463
|
_scheduleNextDispatch(){
|
|
448
|
-
if (this
|
|
449
|
-
// 🖐️event
|
|
464
|
+
if (this.#fingersAreIdle) {
|
|
465
|
+
// 🖐️event stop: CustomEvent
|
|
450
466
|
// Fired when all movements are complete.
|
|
451
467
|
|
|
452
|
-
|
|
468
|
+
this.dispatchEvent(new CustomEvent('stop', {target: this}));
|
|
453
469
|
|
|
454
470
|
|
|
455
471
|
if (this._onStop && this._onStop instanceof Function) {
|
|
@@ -461,10 +477,10 @@ export default class Hand {
|
|
|
461
477
|
// Calculate time for next movement end. Could be refactored out for
|
|
462
478
|
// some timing modes.
|
|
463
479
|
var min = Infinity;
|
|
464
|
-
this.
|
|
480
|
+
this.#fingers.forEach(f=> {
|
|
465
481
|
if (!f.isIdle()) {
|
|
466
482
|
var next = f.getNextMoveEndTime();
|
|
467
|
-
//
|
|
483
|
+
// console.log('next:', next);
|
|
468
484
|
if (next < min) {
|
|
469
485
|
min = next;
|
|
470
486
|
}
|
|
@@ -473,28 +489,52 @@ export default class Hand {
|
|
|
473
489
|
|
|
474
490
|
|
|
475
491
|
if (this._timingMode === TimingMode.Interval) {
|
|
476
|
-
this._nextDispatch = setTimeout(this.
|
|
492
|
+
this._nextDispatch = setTimeout(this.#dispatchEvents.bind(this), this._timeInterval);
|
|
477
493
|
|
|
478
494
|
} else if (this._timingMode === TimingMode.Minimal) {
|
|
479
|
-
this._nextDispatch = setTimeout(this.
|
|
495
|
+
this._nextDispatch = setTimeout(this.#dispatchEvents.bind(this), min - performance.now());
|
|
480
496
|
|
|
481
497
|
} else if (this._timingMode === TimingMode.Instant) {
|
|
482
|
-
return this
|
|
498
|
+
return this.#dispatchEvents(min);
|
|
483
499
|
|
|
484
500
|
} else if (this._timingMode === TimingMode.Frame) {
|
|
485
|
-
this._nextDispatch = requestAnimationFrame( this.
|
|
501
|
+
this._nextDispatch = requestAnimationFrame( this.#dispatchEvents.bind(this) );
|
|
486
502
|
|
|
487
503
|
} else if (this._timingMode === TimingMode.FastFrame) {
|
|
488
504
|
this._nextDispatch = requestAnimationFrame( function() {
|
|
489
|
-
this
|
|
505
|
+
this.#dispatchEvents(min);
|
|
490
506
|
}.bind(this));
|
|
491
507
|
|
|
492
508
|
}
|
|
493
509
|
}
|
|
494
510
|
}
|
|
495
511
|
|
|
496
|
-
|
|
512
|
+
|
|
513
|
+
_hookProstheticCapture(el) {
|
|
514
|
+
if (el.__prostheticPointerCapture) { return; }
|
|
515
|
+
|
|
516
|
+
const captureFn = el.setPointerCapture;
|
|
517
|
+
const releaseFn = el.releasePointerCapture;
|
|
518
|
+
el.setPointerCapture = ((id)=> {
|
|
519
|
+
if (id >= 1000) {
|
|
520
|
+
// console.log('capture', id, el)
|
|
521
|
+
this._capturedTargets.set(id, el);
|
|
522
|
+
} else
|
|
523
|
+
return captureFn.call(el, id);
|
|
524
|
+
} );
|
|
525
|
+
|
|
526
|
+
el.releasePointerCapture = ((id)=> {
|
|
527
|
+
if (this._capturedTargets.get(id) === el) {
|
|
528
|
+
// console.log('release', id, el)
|
|
529
|
+
this._capturedTargets.delete(id);
|
|
530
|
+
} else
|
|
531
|
+
return releaseFn.call(el, id);
|
|
532
|
+
} );
|
|
533
|
+
|
|
534
|
+
el.__prostheticPointerCapture = true;
|
|
535
|
+
}
|
|
497
536
|
|
|
498
537
|
|
|
538
|
+
}
|
|
499
539
|
|
|
500
540
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "prosthetic-hand",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.1.1",
|
|
5
5
|
"description": "Automate synthetic touch/pointer events",
|
|
6
6
|
"exports": "./lib/Hand.js",
|
|
7
7
|
"files": [
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
"repository": {
|
|
15
15
|
"type": "git",
|
|
16
|
-
"url": "https://github.com/
|
|
16
|
+
"url": "https://github.com/Leaflet/prosthetic-hand/.git"
|
|
17
17
|
},
|
|
18
18
|
"author": "Iván Sánchez Ortega <ivan@sanchezortega.es>",
|
|
19
19
|
"license": "Beerware",
|