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\(\);/,
+ );
+ });
+});