PRE-MERGE #19726 Improve screen reader announcements for search box

This commit is contained in:
Carlos Zamora
2026-01-12 17:05:26 -08:00
4 changed files with 42 additions and 16 deletions

View File

@@ -217,14 +217,6 @@
<data name="TermControlReadOnly" xml:space="preserve">
<value>Read-only mode is enabled.</value>
</data>
<data name="SearchBox_MatchesAvailable" xml:space="preserve">
<value>Results found</value>
<comment>Announced to a screen reader when the user searches for some text and there are matches for that text in the terminal.</comment>
</data>
<data name="SearchBox_NoMatches" xml:space="preserve">
<value>No results found</value>
<comment>Announced to a screen reader when the user searches for some text and there are no matches for that text in the terminal.</comment>
</data>
<data name="PasteCommandButton.Label" xml:space="preserve">
<value>Paste</value>
<comment>The label of a button for pasting the contents of the clipboard.</comment>
@@ -330,4 +322,16 @@
<value>Suggested input: {0}</value>
<comment>{Locked="{0}"} {0} will be replaced with a string of input that is suggested for the user to input</comment>
</data>
</root>
<data name="TermControl_NumResultsAccessible" xml:space="preserve">
<value>{0} of {1}</value>
<comment>{Locked="{0}"}{Locked="{1}"} Read out by the screen reader to announce number of results from a search query. "{0}" is replaced with index of search result. "{1}" is replaced by total number of results.</comment>
</data>
<data name="TermControl_UnknownSearchResultIndex" xml:space="preserve">
<value>unknown</value>
<comment>Will be read out by a screen reader when a value for the index of a search result is mismatched and unclear. Displayed as a part of TermControl_NumResultsAccessible.</comment>
</data>
<data name="TermControl_TooManySearchResults" xml:space="preserve">
<value>over 999</value>
<comment>Will be read out by a screen reader when a search returns over 999 search results.</comment>
</data>
</root>

View File

@@ -463,9 +463,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// Arguments:
// - totalMatches - total number of matches (search results)
// - currentMatch - the index of the current match (0-based)
// - isAccessible - if true, format the string for screen readers. Defaults to false.
// Return Value:
// - status message
winrt::hstring SearchBoxControl::_FormatStatus(int32_t totalMatches, int32_t currentMatch)
winrt::hstring SearchBoxControl::_FormatStatus(int32_t totalMatches, int32_t currentMatch, bool isAccessible)
{
if (totalMatches < 0)
{
@@ -482,7 +483,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
if (currentMatch < 0 || currentMatch > (MaximumTotalResultsToShowInStatus - 1))
{
currentString = CurrentIndexTooHighStatus;
currentString = isAccessible ? RS_(L"TermControl_UnknownSearchResultIndex") : CurrentIndexTooHighStatus;
}
else
{
@@ -491,13 +492,17 @@ namespace winrt::Microsoft::Terminal::Control::implementation
if (totalMatches > MaximumTotalResultsToShowInStatus)
{
totalString = TotalResultsTooHighStatus;
totalString = isAccessible ? RS_(L"TermControl_TooManySearchResults") : TotalResultsTooHighStatus;
}
else
{
totalString = fmt::to_wstring(totalMatches);
}
if (isAccessible)
{
return winrt::hstring{ RS_fmt(L"TermControl_NumResultsAccessible", currentString, totalString) };
}
return winrt::hstring{ RS_fmt(L"TermControl_NumResults", currentString, totalString) };
}
@@ -557,10 +562,22 @@ namespace winrt::Microsoft::Terminal::Control::implementation
StatusBox().Text(status);
}
// Method Description:
// - Formats and returns an accessible status message representing the search state.
// - Similar to SetStatus but returns a more descriptive string for screen readers.
hstring SearchBoxControl::GetAccessibleStatus(int32_t totalMatches, int32_t currentMatch, bool searchRegexInvalid)
{
if (searchRegexInvalid)
{
return RS_(L"SearchRegexInvalid");
}
return _FormatStatus(totalMatches, currentMatch, true);
}
// Method Description:
// - Removes the status message in the status box.
void SearchBoxControl::ClearStatus()
{
StatusBox().Text(L"");
StatusBox().Text({});
}
}

View File

@@ -44,6 +44,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void PopulateTextbox(const winrt::hstring& text);
bool ContainsFocus();
void SetStatus(int32_t totalMatches, int32_t currentMatch, bool searchRegexInvalid);
winrt::hstring GetAccessibleStatus(int32_t totalMatches, int32_t currentMatch, bool searchRegexInvalid);
void ClearStatus();
void GoBackwardClicked(const winrt::Windows::Foundation::IInspectable& /*sender*/, const winrt::Windows::UI::Xaml::RoutedEventArgs& /*e*/);
@@ -77,7 +78,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void _PlayCloseAnimation();
bool _AnimationEnabled();
static winrt::hstring _FormatStatus(int32_t totalMatches, int32_t currentMatch);
static winrt::hstring _FormatStatus(int32_t totalMatches, int32_t currentMatch, bool isAccessible = false);
static double _TextWidth(winrt::hstring text, double fontSize);
double _GetStatusMaxWidth();

View File

@@ -3767,13 +3767,17 @@ namespace winrt::Microsoft::Terminal::Control::implementation
};
_updateScrollBar->Run(update);
}
}
if (auto automationPeer{ FrameworkElementAutomationPeer::FromElement(*this) })
if (auto automationPeer{ FrameworkElementAutomationPeer::FromElement(*this) })
{
const auto status = _searchBox->GetAccessibleStatus(results.TotalMatches, results.CurrentMatch, results.SearchRegexInvalid);
if (!status.empty())
{
automationPeer.RaiseNotificationEvent(
AutomationNotificationKind::ActionCompleted,
AutomationNotificationProcessing::ImportantMostRecent,
results.TotalMatches > 0 ? RS_(L"SearchBox_MatchesAvailable") : RS_(L"SearchBox_NoMatches"), // what to announce if results were found
status,
L"SearchBoxResultAnnouncement" /* unique name for this group of notifications */);
}
}