From 11fa0365f967c34f76ea5eb10ad95d57dd6f4f34 Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Sat, 31 Oct 2026 13:29:19 +0100 Subject: [PATCH] fix: clear the new event slot marker when the editor closes or the tab is left --- .../calendar/TimeGridScrollView.tsx | 6 ++++++ .../__tests__/slot-draft-reset.test.ts | 21 +++++++++++++++++++ src/screens/CalendarScreen.tsx | 11 ++++++++++ 3 files changed, 38 insertions(+) create mode 100644 src/components/calendar/__tests__/slot-draft-reset.test.ts diff --git a/src/components/calendar/TimeGridScrollView.tsx b/src/components/calendar/TimeGridScrollView.tsx index 65f7503e..be6da07a 100644 --- a/src/components/calendar/TimeGridScrollView.tsx +++ b/src/components/calendar/TimeGridScrollView.tsx @@ -89,6 +89,8 @@ interface TimeGridScrollViewProps { onSelectDate?: (date: Date) => void; onSelectEvent?: (event: CalendarEvent) => void; onCreateAtTime?: (date: Date) => void; + /** A change takes the "New event" block away (the editor closed, the tab was left). */ + draftResetKey?: number; } /** @@ -119,6 +121,7 @@ function TimeGridScrollViewInner({ onSelectDate, onSelectEvent, onCreateAtTime, + draftResetKey = 0, }: TimeGridScrollViewProps) { const c = useColors(); // "Light event colors" (webmail calendarTintedEvents). @@ -314,6 +317,9 @@ function TimeGridScrollViewInner({ // A tap on an empty slot marks it with a "New event" block; tapping that // block creates the event. Two taps, so a stray tap never opens the editor. const [draft, setDraft] = React.useState<{ key: string; hour: number } | null>(null); + React.useEffect(() => { + setDraft(null); + }, [draftResetKey]); const handleTapAt = React.useCallback((day: Date, hour: number) => { const key = dayKey(day); setDraft((cur) => (cur && cur.key === key && cur.hour === hour ? null : { key, hour })); diff --git a/src/components/calendar/__tests__/slot-draft-reset.test.ts b/src/components/calendar/__tests__/slot-draft-reset.test.ts new file mode 100644 index 00000000..542e774d --- /dev/null +++ b/src/components/calendar/__tests__/slot-draft-reset.test.ts @@ -0,0 +1,21 @@ +import { describe, expect, it } from 'vitest'; +import fs from 'node:fs'; +import path from 'node:path'; + +// Checked on the source: the suite has no React Native renderer. +const grid = fs.readFileSync(path.resolve(__dirname, '../TimeGridScrollView.tsx'), 'utf8'); +const screen = fs.readFileSync(path.resolve(__dirname, '../../../screens/CalendarScreen.tsx'), 'utf8'); + +describe('the "New event" block a tap on an empty slot leaves', () => { + it('goes when the grid is told to drop it', () => { + expect(grid).toMatch(/React\.useEffect\(\(\) => \{\s*setDraft\(null\);\s*\}, \[draftResetKey\]\);/); + }); + + it('is dropped when the editor opens or closes and when the user leaves the tab', () => { + const grids = screen.slice(screen.indexOf('', screen.indexOf(' \{\s*setSlotDraftReset\(\(n\) => n \+ 1\);\s*\}, \[modalVisible\]\);/); + // The focus effect's cleanup runs on blur. + expect(screen).toMatch(/useFocusEffect\(\s*React\.useCallback\(\(\) => \(\) => setSlotDraftReset\(\(n\) => n \+ 1\), \[\]\),?\s*\)/); + }); +}); diff --git a/src/screens/CalendarScreen.tsx b/src/screens/CalendarScreen.tsx index 38eb0432..3c36e138 100644 --- a/src/screens/CalendarScreen.tsx +++ b/src/screens/CalendarScreen.tsx @@ -11,6 +11,7 @@ import { useWindowDimensions, } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; +import { useFocusEffect } from '@react-navigation/native'; import { TASK_CHIP_ID_PREFIX, multiDayCount } from '../lib/calendar-time-grid'; import { Calendar as CalendarIcon, @@ -239,6 +240,15 @@ export default function CalendarScreen() { const [modalEvent, setModalEvent] = React.useState(null); const [modalDate, setModalDate] = React.useState(undefined); const [modalVisible, setModalVisible] = React.useState(false); + // The "New event" block a tap on an empty slot leaves: gone once the + // editor opens or closes, or the user leaves the tab. + const [slotDraftReset, setSlotDraftReset] = React.useState(0); + React.useEffect(() => { + setSlotDraftReset((n) => n + 1); + }, [modalVisible]); + useFocusEffect( + React.useCallback(() => () => setSlotDraftReset((n) => n + 1), []), + ); const [pendingAction, setPendingAction] = React.useState(null); const [sidebarVisible, setSidebarVisible] = React.useState(false); const [tasksVisible, setTasksVisible] = React.useState(false); @@ -1177,6 +1187,7 @@ export default function CalendarScreen() { onSelectDate={viewMode === 'day' ? handleSelectDate : openDay} onSelectEvent={handleSelectEvent} onCreateAtTime={openCreate} + draftResetKey={slotDraftReset} /> )} {viewMode === 'week' && !freeScroll && (