Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions src/screens/EmailListScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -1471,13 +1483,14 @@ export default function EmailListScreen({
autoCorrect={false}
returnKeyType="search"
/>
{searchInput.length > 0 && (
{(searchInput.length > 0 || searchFocused) && (
<Pressable
onPress={() => { 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')}
>
<X size={14} color={c.textMuted} />
</Pressable>
Expand Down
28 changes: 28 additions & 0 deletions src/screens/__tests__/search-close-wiring.test.ts
Original file line number Diff line number Diff line change
@@ -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+<Pressable\s+onPress=\{clearOrCloseSearch\}/);
});

it('is read out as what it does: Clear search with text, Close on an empty field', () => {
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\(\);/,
);
});
});
Loading