diff --git a/src/screens/EmailListScreen.tsx b/src/screens/EmailListScreen.tsx index 49d2488d..5ac72ac6 100644 --- a/src/screens/EmailListScreen.tsx +++ b/src/screens/EmailListScreen.tsx @@ -1122,6 +1122,18 @@ export default function EmailListScreen({ const id = setTimeout(() => setSearchQuery(searchInput), 600); return () => clearTimeout(id); }, [searchInput, storeSearchQuery, setSearchQuery]); + // The X in the field clears the text and keeps the keyboard up; on an empty + // field, which has an X too while it has focus, it closes the search and + // the panel under it (#127). iOS has no back button to leave the field. + const clearOrCloseSearch = React.useCallback(() => { + if (searchInput.length > 0) { + setSearchInput(''); + setSearchQuery(''); + return; + } + setSearchFocused(false); + searchInputRef.current?.blur(); + }, [searchInput, setSearchQuery]); const submitSearch = React.useCallback((value: string) => { setSearchInput(value); setSearchQuery(value); @@ -1471,13 +1483,14 @@ export default function EmailListScreen({ autoCorrect={false} returnKeyType="search" /> - {searchInput.length > 0 && ( + {(searchInput.length > 0 || searchFocused) && ( { setSearchInput(''); setSearchQuery(''); }} + onPress={clearOrCloseSearch} hitSlop={8} style={styles.searchClearButton} accessibilityRole="button" - accessibilityLabel={t('contacts.clear_search', 'Clear search')} + // Named for what it does: on an empty field it closes the search. + accessibilityLabel={searchInput.length > 0 ? t('contacts.clear_search', 'Clear search') : t('common.close', 'Close')} > diff --git a/src/screens/__tests__/search-close-wiring.test.ts b/src/screens/__tests__/search-close-wiring.test.ts new file mode 100644 index 00000000..8b21fc0c --- /dev/null +++ b/src/screens/__tests__/search-close-wiring.test.ts @@ -0,0 +1,28 @@ +import { describe, expect, it } from 'vitest'; +import fs from 'node:fs'; +import path from 'node:path'; + +// #127: the X in the mail list's search field shows as soon as the field has +// focus: it clears the text, and on an empty field closes the search. iOS has +// no back button to leave an empty, focused field. No React Native renderer in the test env, so this checks the +// wiring. + +const src = fs.readFileSync(path.resolve(__dirname, '..', 'EmailListScreen.tsx'), 'utf8'); + +describe('mail search close wiring', () => { + it('shows the X while the field has focus or holds text', () => { + expect(src).toMatch(/\{\(searchInput\.length > 0 \|\| searchFocused\) && \(\s+ { + expect(src).toMatch( + /onPress=\{clearOrCloseSearch\}[\s\S]{0,200}?accessibilityLabel=\{searchInput\.length > 0 \? t\('contacts\.clear_search', 'Clear search'\) : t\('common\.close', 'Close'\)\}/, + ); + }); + + it('clears typed text first, then closes the panel and lets go of the keyboard', () => { + expect(src).toMatch( + /const clearOrCloseSearch = React\.useCallback\(\(\) => \{\s+if \(searchInput\.length > 0\) \{\s+setSearchInput\(''\);\s+setSearchQuery\(''\);\s+return;\s+\}\s+setSearchFocused\(false\);\s+searchInputRef\.current\?\.blur\(\);/, + ); + }); +});