minimojs 1.0.0-alpha.25 → 1.0.0-alpha.26

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.
@@ -29,6 +29,16 @@ export class InputSystem {
29
29
  this._touchPointers = new Map();
30
30
  /** @internal */
31
31
  this._primaryTouchId = null;
32
+ /**
33
+ * The primary touch after it lifted, kept until the mouse or a new touch
34
+ * takes over, so the pointer stays where the finger left the screen instead
35
+ * of jumping to the mouse's last position.
36
+ *
37
+ * @internal
38
+ */
39
+ this._releasedTouch = null;
40
+ /** @internal True on the frame the primary touch ended in a `touchcancel`. */
41
+ this._pointerCancelled = false;
32
42
  }
33
43
  get pointerX() {
34
44
  return this._pointerX;
@@ -52,16 +62,19 @@ export class InputSystem {
52
62
  this._mouseY = p.y;
53
63
  this._mouseDown = true;
54
64
  this._mousePressed = true;
65
+ this._releasedTouch = null;
55
66
  this.syncPrimaryPointer();
56
67
  });
57
68
  this.canvas.addEventListener("mouseup", () => {
58
69
  this._mouseDown = false;
70
+ this._releasedTouch = null;
59
71
  this.syncPrimaryPointer();
60
72
  });
61
73
  this.canvas.addEventListener("mousemove", (e) => {
62
74
  const p = this.getCanvasPoint(e.clientX, e.clientY);
63
75
  this._mouseX = p.x;
64
76
  this._mouseY = p.y;
77
+ this._releasedTouch = null;
65
78
  this.syncPrimaryPointer();
66
79
  });
67
80
  this.canvas.addEventListener("touchstart", (e) => {
@@ -76,27 +89,17 @@ export class InputSystem {
76
89
  this._primaryTouchId = touch.identifier;
77
90
  }
78
91
  }
92
+ this._releasedTouch = null;
93
+ this._pointerCancelled = false;
79
94
  this.syncPrimaryPointer();
80
95
  e.preventDefault();
81
96
  }, { passive: false });
82
97
  this.canvas.addEventListener("touchend", (e) => {
83
- for (const touch of Array.from(e.changedTouches)) {
84
- this._touchPointers.delete(touch.identifier);
85
- if (this._primaryTouchId === touch.identifier) {
86
- this._primaryTouchId = null;
87
- }
88
- }
89
- this.syncPrimaryPointer();
98
+ this.endTouches(e, false);
90
99
  e.preventDefault();
91
100
  }, { passive: false });
92
101
  this.canvas.addEventListener("touchcancel", (e) => {
93
- for (const touch of Array.from(e.changedTouches)) {
94
- this._touchPointers.delete(touch.identifier);
95
- if (this._primaryTouchId === touch.identifier) {
96
- this._primaryTouchId = null;
97
- }
98
- }
99
- this.syncPrimaryPointer();
102
+ this.endTouches(e, true);
100
103
  e.preventDefault();
101
104
  }, { passive: false });
102
105
  this.canvas.addEventListener("touchmove", (e) => {
@@ -131,6 +134,9 @@ export class InputSystem {
131
134
  isPointerPressed() {
132
135
  return this._pointerPressed;
133
136
  }
137
+ isPointerCancelled() {
138
+ return this._pointerCancelled;
139
+ }
134
140
  isPointerDownOverSprite(sprite) {
135
141
  if (!sprite)
136
142
  return false;
@@ -185,11 +191,38 @@ export class InputSystem {
185
191
  for (const pointer of this._touchPointers.values()) {
186
192
  pointer.pressed = false;
187
193
  }
194
+ if (this._releasedTouch)
195
+ this._releasedTouch.pressed = false;
196
+ this._pointerCancelled = false;
188
197
  this.syncPrimaryPointer();
189
198
  }
190
199
  resetPressedState() {
191
200
  this.clearFramePressedState();
192
201
  }
202
+ /**
203
+ * Removes the touches an event ended. When the primary one lifts, it is kept
204
+ * as the released touch at the point it lifted, carrying its frame-press flag
205
+ * so a tap that starts and ends between two frames still reads as pressed.
206
+ *
207
+ * @internal
208
+ */
209
+ endTouches(e, cancelled) {
210
+ for (const touch of Array.from(e.changedTouches)) {
211
+ const pointer = this._touchPointers.get(touch.identifier);
212
+ this._touchPointers.delete(touch.identifier);
213
+ if (this._primaryTouchId !== touch.identifier || !pointer)
214
+ continue;
215
+ this._primaryTouchId = null;
216
+ if (!cancelled) {
217
+ const p = this.getCanvasPoint(touch.clientX, touch.clientY);
218
+ pointer.x = p.x;
219
+ pointer.y = p.y;
220
+ }
221
+ this._releasedTouch = pointer;
222
+ this._pointerCancelled = cancelled;
223
+ }
224
+ this.syncPrimaryPointer();
225
+ }
193
226
  /** @internal */
194
227
  getCanvasPoint(clientX, clientY) {
195
228
  const rect = this.canvas.getBoundingClientRect();
@@ -257,9 +290,10 @@ export class InputSystem {
257
290
  }
258
291
  }
259
292
  this._primaryTouchId = null;
260
- this._pointerX = this._mouseX;
261
- this._pointerY = this._mouseY;
293
+ const released = this._releasedTouch;
294
+ this._pointerX = released ? released.x : this._mouseX;
295
+ this._pointerY = released ? released.y : this._mouseY;
262
296
  this._pointerDown = this._mouseDown;
263
- this._pointerPressed = this._mousePressed;
297
+ this._pointerPressed = this._mousePressed || (released?.pressed ?? false);
264
298
  }
265
299
  }
@@ -658,7 +658,10 @@ export class CardTable {
658
658
  return;
659
659
  }
660
660
  this._press = null;
661
- if (this._drag)
661
+ // A cancelled touch was interrupted, not released: return the cards, tap nothing.
662
+ if (game.isPointerCancelled())
663
+ this.cancelDrag();
664
+ else if (this._drag)
662
665
  this.finishDrag();
663
666
  else if (Math.hypot(x - press.x, y - press.y) < this.dragThreshold)
664
667
  this.onTap?.(press.card, press.pile);
package/dist/minimo.js CHANGED
@@ -1816,6 +1816,15 @@ export class Game {
1816
1816
  isPointerPressed() {
1817
1817
  return this._inputSystem.isPointerPressed();
1818
1818
  }
1819
+ /**
1820
+ * True on the frame the primary touch ended in a `touchcancel` rather than
1821
+ * lifting, so a module can drop the gesture instead of reading it as a tap.
1822
+ *
1823
+ * @internal
1824
+ */
1825
+ isPointerCancelled() {
1826
+ return this._inputSystem.isPointerCancelled();
1827
+ }
1819
1828
  /**
1820
1829
  * Returns `true` while any active pointer is held down over the target sprite.
1821
1830
  * Works with both mouse input and multiple simultaneous touches.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minimojs",
3
- "version": "1.0.0-alpha.25",
3
+ "version": "1.0.0-alpha.26",
4
4
  "description": "MinimoJS v1 — ultra-minimal, flat, deterministic 2D web game engine. Image-first sprites with emoji as a zero-asset alternative, rAF loop, TypeScript-first, LLM-friendly.",
5
5
  "type": "module",
6
6
  "main": "dist/minimo.js",