diff --git a/src/__tests__/baselines/source-function-lines.json b/src/__tests__/baselines/source-function-lines.json index f9f64c814..66228b51e 100644 --- a/src/__tests__/baselines/source-function-lines.json +++ b/src/__tests__/baselines/source-function-lines.json @@ -1,7 +1,7 @@ { "$comment": "Production functions over 200 lines, keyed \"file :: name\", and their length. Maintained by src/__tests__/source-size-ratchet.test.ts.", "count": 336, - "total": 109378, + "total": 109385, "entries": { "app/achievements/page.tsx :: AchievementsPage": 206, "app/api/admin/backups/upload/route.ts :: apiHandler(arg)": 275, @@ -231,7 +231,7 @@ "components/vaccinations/custom-vaccine-sheet.tsx :: CustomVaccineSheet": 246, "components/vaccinations/vaccination-form.tsx :: VaccinationForm": 218, "components/workouts/manual-workout-form.tsx :: ManualWorkoutForm": 321, - "hooks/use-sheet-drag.ts :: createSheetDrag": 312, + "hooks/use-sheet-drag.ts :: createSheetDrag": 319, "lib/activity/calendar-math.ts :: buildActivityCalendar": 204, "lib/ai/anthropic-client.ts :: generateCompletion": 294, "lib/ai/coach/cycle-snapshot.ts :: buildCycleSnapshotBlock": 202, diff --git a/src/hooks/__tests__/use-sheet-drag.test.ts b/src/hooks/__tests__/use-sheet-drag.test.ts index 2b88e7e10..be7880307 100644 --- a/src/hooks/__tests__/use-sheet-drag.test.ts +++ b/src/hooks/__tests__/use-sheet-drag.test.ts @@ -23,6 +23,9 @@ interface FakeEl { parentElement: FakeEl | null; overflowY: string; own?: boolean; + /** Radix's `data-state` on the sheet content. */ + state?: "open" | "closed"; + getAttribute: (name: string) => string | null; closest: (selector: string) => FakeEl | null; getAnimations: () => Animation[]; setPointerCapture: (id: number) => void; @@ -39,6 +42,8 @@ function fake(partial: Partial = {}): FakeEl { parentElement: null, overflowY: "visible", closest: () => (node.own ? node : null), + getAttribute: (name) => + name === "data-state" ? (node.state ?? null) : null, getAnimations: () => [], setPointerCapture: vi.fn(), querySelector: () => null, @@ -173,6 +178,29 @@ describe("createSheetDrag", () => { expect(s.controller.swallowClick()).toBe(false); }); + it("returns to its rest when a guarded sheet stays open after the swipe", () => { + const s = setup(); + // The owner asks about unsaved input instead of closing. + s.content.state = "open"; + s.drag(s.header, 140, { stepMs: 40, restMs: 200 }); + expect(s.onClose).toHaveBeenCalledTimes(1); + expect(s.content.style.transform).toBe("translate3d(0, 140px, 0)"); + + s.flushFrames(); + expect(s.content.style.transform).toBe("translate3d(0, 0px, 0)"); + expect(s.overlay.style.opacity).toBe("1"); + s.timers.forEach((run) => run()); + expect(s.content.style.transform).toBe(""); + }); + + it("leaves a closing sheet where it was let go", () => { + const s = setup(); + s.content.state = "closed"; + s.drag(s.header, 140, { stepMs: 40, restMs: 200 }); + s.flushFrames(); + expect(s.content.style.transform).toBe("translate3d(0, 140px, 0)"); + }); + it("lets its own close through: the swallowed click comes after it", () => { const s = setup(); let swallowedDuringClose: boolean | null = null; diff --git a/src/hooks/use-sheet-drag.ts b/src/hooks/use-sheet-drag.ts index fe669b4ad..5a54f427d 100644 --- a/src/hooks/use-sheet-drag.ts +++ b/src/hooks/use-sheet-drag.ts @@ -354,6 +354,13 @@ export function createSheetDrag( // Before the swallow is armed: the close is itself a click. options.onClose(); swallow = true; + // A sheet holding unsaved input asks first and stays open meanwhile; + // put it back where it rests rather than where it was let go. + frame(() => { + if (options.content()?.getAttribute("data-state") === "open") { + settleTo(g, options.snap()); + } + }); return; } swallow = true;