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 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 = h.growFinger('pointer', { pointerType: 'touch', pressure: 0.9 });
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 = 1;
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 movements.
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
- this._currentTarget = document.elementFromPoint(this._state.x, this._state.y);
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 = document.elementFromPoint(this._state.x, this._state.y);
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._fingers = [];
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._onStart = options.onStart;
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._onStop = options.onStop;
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 `_dispatchEvents`. This
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._fingers.push(finger);
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._fingersAreIdle) {
151
+ if (this.#fingersAreIdle) {
147
152
  // 🖐️section
148
- // Use `document.addEventListener('prostheticHandStop', fn)` to
149
- // do stuff with it.
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
- document.dispatchEvent(new CustomEvent('prostheticHandStart', {target: this}));
156
+ this.dispatchEvent(new CustomEvent('start', {target: this}));
153
157
 
154
- if (this._onStart && this._onStart instanceof Function) {
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
- if (this._fingers.every( f => f.isIdle())) {
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._fingers.forEach( f => {
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._fingers.forEach( f => {
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 private_dispatchEvents(): this
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
- _dispatchEvents(timestamp) {
211
+ #dispatchEvents(timestamp) {
207
212
 
208
- // 🖐️event prostheticHandTick: CustomEvent
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
- document.dispatchEvent(new CustomEvent('prostheticHandStart', {target: this}));
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._fingers.forEach(f=> {
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
- document.elementFromPoint(ev.clientX, ev.clientY).dispatchEvent(ev);
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._fingersAreIdle) {
449
- // 🖐️event prostheticHandStop: CustomEvent
464
+ if (this.#fingersAreIdle) {
465
+ // 🖐️event stop: CustomEvent
450
466
  // Fired when all movements are complete.
451
467
 
452
- document.dispatchEvent(new CustomEvent('prostheticHandStop', {target: this}));
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._fingers.forEach(f=> {
480
+ this.#fingers.forEach(f=> {
465
481
  if (!f.isIdle()) {
466
482
  var next = f.getNextMoveEndTime();
467
- // console.log('next:', next);
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._dispatchEvents.bind(this), this._timeInterval);
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._dispatchEvents.bind(this), min - performance.now());
495
+ this._nextDispatch = setTimeout(this.#dispatchEvents.bind(this), min - performance.now());
480
496
 
481
497
  } else if (this._timingMode === TimingMode.Instant) {
482
- return this._dispatchEvents(min);
498
+ return this.#dispatchEvents(min);
483
499
 
484
500
  } else if (this._timingMode === TimingMode.Frame) {
485
- this._nextDispatch = requestAnimationFrame( this._dispatchEvents.bind(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._dispatchEvents(min);
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.0.0",
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/IvanSanchez/prosthetic-hand.git"
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",