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:
@@ -247,6 +247,32 @@ describe(".onCommit()", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("runs synchronously inside the pointer-up event", async () => {
|
||||
let calledDuringDispatch = false;
|
||||
let dispatching = false;
|
||||
const { host } = await mount(
|
||||
gesture.to
|
||||
.left()
|
||||
.navigate(() => forward(originView(Target)))
|
||||
.onCommit(() => {
|
||||
calledDuringDispatch = dispatching;
|
||||
})
|
||||
.animate(motion),
|
||||
);
|
||||
host.dispatchEvent(pointer("pointerdown", 190, 0));
|
||||
host.dispatchEvent(pointer("pointermove", 180, 500));
|
||||
await flush();
|
||||
host.dispatchEvent(pointer("pointermove", 40, 1000));
|
||||
await flush();
|
||||
|
||||
// No awaiting between the event and the handler: iOS only allows
|
||||
// haptics, audio, and clipboard writes during the user's gesture.
|
||||
dispatching = true;
|
||||
host.dispatchEvent(pointer("pointerup", 40, 1000));
|
||||
dispatching = false;
|
||||
expect(calledDuringDispatch).toBe(true);
|
||||
});
|
||||
|
||||
it("fires at release, before the settling animation finishes", async () => {
|
||||
const frames: FrameRequestCallback[] = [];
|
||||
vi.stubGlobal("requestAnimationFrame", (cb: FrameRequestCallback) =>
|
||||
|
||||
@@ -108,6 +108,26 @@ describe("stuck gestures", () => {
|
||||
expect(scene.nodes.value).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("ignores a child losing implicit touch capture to the host", async () => {
|
||||
const Root = defineComponent({
|
||||
setup: () => () =>
|
||||
h(OriginGesture, { id: "host", gesture: definition }, () =>
|
||||
h("span", { id: "child" }, "x"),
|
||||
),
|
||||
});
|
||||
const { scene, host } = await mount(Root);
|
||||
await dragLeft(host);
|
||||
expect(scene.operations.value).toHaveLength(1);
|
||||
|
||||
// Taking capture on the host makes the touched child lose its implicit
|
||||
// capture; that event bubbles to the host.
|
||||
host
|
||||
.querySelector("#child")!
|
||||
.dispatchEvent(pointer("lostpointercapture", 120, 110));
|
||||
await flush();
|
||||
expect(scene.operations.value).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("ignores lostpointercapture after a normal release", async () => {
|
||||
const { scene, host } = await mount(gestureHost(() => definition));
|
||||
await dragLeft(host);
|
||||
@@ -318,6 +338,22 @@ describe("commitVelocity", () => {
|
||||
expect(shouldCommit(operation(0.1, 0.15), {})).toBe(true);
|
||||
});
|
||||
|
||||
it("lets a fast flick's direction veto the distance", () => {
|
||||
const at = (progress: number, velocity: number) =>
|
||||
({
|
||||
progress,
|
||||
velocity,
|
||||
choreography: { effects: () => ({}) },
|
||||
}) as unknown as Parameters<typeof shouldCommit>[0];
|
||||
// Past the 0.36 threshold, but flicked back toward the start: cancels.
|
||||
expect(shouldCommit(at(0.8, -0.5), {})).toBe(false);
|
||||
// Barely started, but flicked forward: commits.
|
||||
expect(shouldCommit(at(0.05, 0.5), {})).toBe(true);
|
||||
// Slow releases fall back to distance, in either direction.
|
||||
expect(shouldCommit(at(0.8, -0.1), {})).toBe(true);
|
||||
expect(shouldCommit(at(0.2, 0.1), {})).toBe(false);
|
||||
});
|
||||
|
||||
it("still yields to an explicit decision", () => {
|
||||
expect(shouldCommit(operation(0.1, 5), { commit: false })).toBe(false);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user