partforge 0.133.2 → 0.133.3
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/package.json
CHANGED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// src/framework/stale-touch-guard.js
|
|
2
|
+
// Keeps the camera controls from holding onto a finger that has already left
|
|
3
|
+
// the screen.
|
|
4
|
+
//
|
|
5
|
+
// OrbitControls tracks every pointer that goes down on the canvas and forgets
|
|
6
|
+
// one only on its pointerup or pointercancel. When the page is embedded in a
|
|
7
|
+
// host that runs its own gestures (a chat app's WKWebView, which scrolls the
|
|
8
|
+
// conversation on a vertical drag), the host can take a drag over and the page
|
|
9
|
+
// never hears that finger lift. The controls then keep it as down forever: the
|
|
10
|
+
// next one-finger drag reads as a two-finger pinch (it zooms), and with two
|
|
11
|
+
// stuck, no drag moves the camera at all.
|
|
12
|
+
//
|
|
13
|
+
// Two measures, both on the canvas's document and both passive:
|
|
14
|
+
//
|
|
15
|
+
// 1. Do-nothing touchstart/touchend/touchcancel listeners. WebKit on iOS hands
|
|
16
|
+
// a touch to the page only where the page listens for TOUCH events, and the
|
|
17
|
+
// controls listen for pointer events alone, so without these the host's
|
|
18
|
+
// gesture recognizers could claim a drag the page itself was handling.
|
|
19
|
+
//
|
|
20
|
+
// 2. Stale fingers are cancelled on arrival. A touch pointerdown marked
|
|
21
|
+
// isPrimary is the first finger of a brand-new gesture, so no other touch
|
|
22
|
+
// can really be down; every touch id still recorded as down on the canvas is
|
|
23
|
+
// sent the pointercancel it missed, from a capture listener on the document
|
|
24
|
+
// (which runs before the controls' own listener on the canvas). A second
|
|
25
|
+
// finger of the same gesture is never primary, so pinch and two-finger pan
|
|
26
|
+
// are untouched.
|
|
27
|
+
//
|
|
28
|
+
// The controls release pointer capture for the id they remove, and that call
|
|
29
|
+
// throws for an id the browser has already forgotten, aborting their cleanup
|
|
30
|
+
// half way (the move listeners and the gesture state would stay live). So the
|
|
31
|
+
// canvas's releasePointerCapture is wrapped to ignore that one failure.
|
|
32
|
+
const TOUCH_TYPES = ["touchstart", "touchend", "touchcancel"];
|
|
33
|
+
|
|
34
|
+
export function guardStaleTouches(element) {
|
|
35
|
+
const doc = element.ownerDocument;
|
|
36
|
+
if (!doc) return () => {};
|
|
37
|
+
const noop = () => {};
|
|
38
|
+
const touchesDown = new Set();
|
|
39
|
+
|
|
40
|
+
const release = element.releasePointerCapture;
|
|
41
|
+
if (typeof release === "function") {
|
|
42
|
+
element.releasePointerCapture = function releaseIfHeld(pointerId) {
|
|
43
|
+
try {
|
|
44
|
+
release.call(this, pointerId);
|
|
45
|
+
} catch {
|
|
46
|
+
// Already released: the finger is gone, which is all the caller wanted.
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const onDown = (event) => {
|
|
52
|
+
if (event.pointerType !== "touch") return;
|
|
53
|
+
if (event.isPrimary && touchesDown.size) {
|
|
54
|
+
for (const pointerId of [...touchesDown]) {
|
|
55
|
+
if (pointerId === event.pointerId) continue;
|
|
56
|
+
touchesDown.delete(pointerId);
|
|
57
|
+
element.dispatchEvent(new PointerEvent("pointercancel", { pointerId, pointerType: "touch", bubbles: true }));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (event.target === element) touchesDown.add(event.pointerId);
|
|
61
|
+
};
|
|
62
|
+
const onEnd = (event) => { touchesDown.delete(event.pointerId); };
|
|
63
|
+
|
|
64
|
+
for (const type of TOUCH_TYPES) doc.addEventListener(type, noop, { capture: true, passive: true });
|
|
65
|
+
doc.addEventListener("pointerdown", onDown, { capture: true, passive: true });
|
|
66
|
+
doc.addEventListener("pointerup", onEnd, { capture: true, passive: true });
|
|
67
|
+
doc.addEventListener("pointercancel", onEnd, { capture: true, passive: true });
|
|
68
|
+
|
|
69
|
+
return function dispose() {
|
|
70
|
+
for (const type of TOUCH_TYPES) doc.removeEventListener(type, noop, { capture: true });
|
|
71
|
+
doc.removeEventListener("pointerdown", onDown, { capture: true });
|
|
72
|
+
doc.removeEventListener("pointerup", onEnd, { capture: true });
|
|
73
|
+
doc.removeEventListener("pointercancel", onEnd, { capture: true });
|
|
74
|
+
if (typeof release === "function") element.releasePointerCapture = release;
|
|
75
|
+
touchesDown.clear();
|
|
76
|
+
};
|
|
77
|
+
}
|
package/src/framework/viewer.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as THREE from "three";
|
|
2
2
|
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
|
|
3
|
+
import { guardStaleTouches } from "./stale-touch-guard.js";
|
|
3
4
|
import { toCreasedNormals } from "three/addons/utils/BufferGeometryUtils.js";
|
|
4
5
|
import { LineSegments2 } from "three/addons/lines/LineSegments2.js";
|
|
5
6
|
import { LineSegmentsGeometry } from "three/addons/lines/LineSegmentsGeometry.js";
|
|
@@ -231,6 +232,9 @@ export function createViewer(container, part) {
|
|
|
231
232
|
|
|
232
233
|
const controls = new OrbitControls(activeCamera, renderer.domElement);
|
|
233
234
|
controls.enableDamping = true;
|
|
235
|
+
// A finger a host gesture swallowed would otherwise stay "down" in the
|
|
236
|
+
// controls forever (every later drag zooms) — see stale-touch-guard.js.
|
|
237
|
+
const disposeTouchGuard = guardStaleTouches(renderer.domElement);
|
|
234
238
|
|
|
235
239
|
// --- render on demand -------------------------------------------------------
|
|
236
240
|
// The loop ticks every animation frame (controls, tweens, frame listeners —
|
|
@@ -2608,6 +2612,7 @@ export function createViewer(container, part) {
|
|
|
2608
2612
|
canonicalCaptureHidden.clear();
|
|
2609
2613
|
camTween.cancel();
|
|
2610
2614
|
controls.dispose();
|
|
2615
|
+
disposeTouchGuard();
|
|
2611
2616
|
for (const t of flashTimers) clearTimeout(t);
|
|
2612
2617
|
flashTimers.clear();
|
|
2613
2618
|
// A dot whose timer was just cancelled — or one held indefinitely — still
|