Fix touch gestures cancelling, backward flicks, and late onCommit
- Only the host's own lostpointercapture cancels a gesture. Taking capture makes the touched child lose its implicit touch capture, and that bubbling event was cancelling every touch gesture immediately. - A flick at least commitVelocity fast now decides by its direction: backward cancels even past the distance threshold, forward commits. Slower releases still decide by distance. - Pointer-up finishes an already-begun operation without awaiting, so onCommit runs inside the pointer-up event, where iOS still allows haptics, audio, and clipboard writes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Faarh2Ts6StJPcz2mRiHiR
This commit is contained in:
@@ -303,6 +303,11 @@ interface PointerSession {
|
||||
captured: boolean;
|
||||
/** The scene operation, once navigation resolved and `begin()` was called. */
|
||||
operation: Promise<OriginOperationHandle | null> | null;
|
||||
/**
|
||||
* The operation's handle once `begin()` resolved, so pointer-up can finish
|
||||
* it synchronously, inside the event, without awaiting the promise.
|
||||
*/
|
||||
handle: OriginOperationHandle | null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -368,6 +373,7 @@ export class GestureRecognizer {
|
||||
),
|
||||
captured: false,
|
||||
operation: null,
|
||||
handle: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -388,14 +394,17 @@ export class GestureRecognizer {
|
||||
this.session = null;
|
||||
return;
|
||||
}
|
||||
session.operation = this.origin.begin(action).catch((error) => {
|
||||
// Another recognizer on this origin won the race; that is expected.
|
||||
if (!(
|
||||
error instanceof OriginSceneError && error.code === "origin-busy"
|
||||
))
|
||||
warn("A gesture could not begin its operation.", error);
|
||||
return null;
|
||||
});
|
||||
session.operation = this.origin.begin(action).then(
|
||||
(handle) => (session.handle = handle),
|
||||
(error) => {
|
||||
// Another recognizer on this origin won the race; that is expected.
|
||||
if (!(
|
||||
error instanceof OriginSceneError && error.code === "origin-busy"
|
||||
))
|
||||
warn("A gesture could not begin its operation.", error);
|
||||
return null;
|
||||
},
|
||||
);
|
||||
} else {
|
||||
event.preventDefault();
|
||||
session.tracker.sample(event);
|
||||
@@ -433,7 +442,11 @@ export class GestureRecognizer {
|
||||
const commit = definition.completion?.(context);
|
||||
const { progress, velocity } = tracker;
|
||||
|
||||
const handle = session.operation ? await session.operation : null;
|
||||
// Usually the operation began long ago: finish it without awaiting, so
|
||||
// commit handlers run inside this pointer-up (iOS only allows haptics,
|
||||
// audio, and clipboard writes during the user's gesture).
|
||||
const handle =
|
||||
session.handle ?? (session.operation ? await session.operation : null);
|
||||
if (!handle) return;
|
||||
handle.update(progress, velocity);
|
||||
// finish() fixes the outcome synchronously, before the spring settles.
|
||||
@@ -445,9 +458,16 @@ export class GestureRecognizer {
|
||||
await settled;
|
||||
}
|
||||
|
||||
/** Pointer capture ended without a pointer-up; cancel like pointer-cancel. */
|
||||
/**
|
||||
* The host lost pointer capture without a pointer-up; cancel like
|
||||
* pointer-cancel. The event bubbles, and taking capture makes the touched
|
||||
* child lose its implicit touch capture, so only the host's own loss counts.
|
||||
*/
|
||||
pointerCaptureLost(event: PointerEvent): Promise<void> {
|
||||
return this.sessionFor(event) ? this.abandon() : Promise.resolve();
|
||||
const session = this.sessionFor(event);
|
||||
return session && event.target === session.host
|
||||
? this.abandon()
|
||||
: Promise.resolve();
|
||||
}
|
||||
|
||||
pointerCancel(): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user