Fix compose initial focus behavior

This commit is contained in:
Burak Kaan Köse
2026-04-15 02:12:01 +02:00
parent aac9f9fec3
commit 7e1731f4dc
2 changed files with 80 additions and 12 deletions
@@ -296,12 +296,18 @@ public sealed partial class WebViewEditorControl : Control, IDisposable, IEditor
return; return;
} }
if (focusControlAsWell)
{
Focus(FocusState.Programmatic);
_chromium.Focus(FocusState.Programmatic);
_chromium.Focus(FocusState.Keyboard);
}
await _chromium.ExecuteScriptSafeAsync("focusEditor();"); await _chromium.ExecuteScriptSafeAsync("focusEditor();");
if (focusControlAsWell) if (focusControlAsWell)
{ {
_chromium.Focus(FocusState.Keyboard); _chromium.Focus(FocusState.Keyboard);
_chromium.Focus(FocusState.Programmatic);
} }
} }
+73 -11
View File
@@ -38,7 +38,10 @@ public sealed partial class ComposePage : ComposePageAbstract,
IPopoutClient, IPopoutClient,
IRecipient<ApplicationThemeChanged> IRecipient<ApplicationThemeChanged>
{ {
private const int InitialFocusRetryCount = 3;
private bool _isPoppedOut; private bool _isPoppedOut;
private bool _isInitialFocusHandled;
public bool SupportsPopOut => !_isPoppedOut; public bool SupportsPopOut => !_isPoppedOut;
public event EventHandler<PopOutRequestedEventArgs>? PopOutRequested; public event EventHandler<PopOutRequestedEventArgs>? PopOutRequested;
@@ -307,7 +310,7 @@ public sealed partial class ComposePage : ComposePageAbstract,
_disposables.Add(WebViewEditor); _disposables.Add(WebViewEditor);
ViewModel.GetHTMLBodyFunction = WebViewEditor.GetHtmlBodyAsync; ViewModel.GetHTMLBodyFunction = WebViewEditor.GetHtmlBodyAsync;
ViewModel.RenderHtmlBodyAsyncFunc = WebViewEditor.RenderHtmlAsync; ViewModel.RenderHtmlBodyAsyncFunc = RenderComposeHtmlAsync;
} }
private void ShowCCBCCClicked(object sender, RoutedEventArgs e) private void ShowCCBCCClicked(object sender, RoutedEventArgs e)
@@ -373,9 +376,10 @@ public sealed partial class ComposePage : ComposePageAbstract,
{ {
if (draftMailItemViewModel == null || !draftMailItemViewModel.IsDraft) return; if (draftMailItemViewModel == null || !draftMailItemViewModel.IsDraft) return;
// Reset the initial focus flag so ToBox gets focus for the new draft. // Reset the initial focus flag for the newly loaded draft.
isInitialFocusHandled = false; _isInitialFocusHandled = false;
await ViewModel.RefreshDraftAsync(draftMailItemViewModel); await ViewModel.RefreshDraftAsync(draftMailItemViewModel);
await ApplyInitialFocusAsync();
} }
private void ImportanceClicked(object sender, RoutedEventArgs e) private void ImportanceClicked(object sender, RoutedEventArgs e)
@@ -434,21 +438,19 @@ public sealed partial class ComposePage : ComposePageAbstract,
} }
} }
// Hack: Tokenizing text box losing focus somehow on page Loaded and shifting focus to this element.
// For once we'll switch back to it once CCBBCGotFocus element got focus.
private bool isInitialFocusHandled = false;
private void ComposerLoaded(object sender, RoutedEventArgs e) private void ComposerLoaded(object sender, RoutedEventArgs e)
{ {
ToBox.Focus(FocusState.Programmatic); if (ShouldFocusRecipients())
{
ToBox.Focus(FocusState.Programmatic);
}
} }
private void CCBBCGotFocus(object sender, RoutedEventArgs e) private void CCBBCGotFocus(object sender, RoutedEventArgs e)
{ {
if (!isInitialFocusHandled) if (ShouldFocusRecipients() && !_isInitialFocusHandled)
{ {
isInitialFocusHandled = true; _isInitialFocusHandled = true;
ToBox.Focus(FocusState.Programmatic); ToBox.Focus(FocusState.Programmatic);
} }
} }
@@ -555,4 +557,64 @@ public sealed partial class ComposePage : ComposePageAbstract,
} }
finally { deferral.Complete(); } finally { deferral.Complete(); }
} }
private bool ShouldFocusRecipients()
=> !ShouldFocusEditor();
private bool ShouldFocusEditor()
{
var inReplyTo = ViewModel.CurrentMimeMessage?.InReplyTo;
if (string.IsNullOrWhiteSpace(inReplyTo))
{
inReplyTo = ViewModel.CurrentMailDraftItem?.MailCopy?.InReplyTo;
}
if (string.IsNullOrWhiteSpace(inReplyTo) && ViewModel.CurrentMimeMessage?.Headers.Contains(HeaderId.InReplyTo) == true)
{
inReplyTo = ViewModel.CurrentMimeMessage.Headers[HeaderId.InReplyTo];
}
return !string.IsNullOrWhiteSpace(inReplyTo);
}
private async Task ApplyInitialFocusAsync()
{
if (_isInitialFocusHandled)
{
return;
}
_isInitialFocusHandled = true;
for (var attempt = 0; attempt < InitialFocusRetryCount; attempt++)
{
if (ShouldFocusEditor())
{
await WebViewEditor.FocusEditorAsync(true);
if (FocusManager.GetFocusedElement(XamlRoot) is WebView2)
{
return;
}
}
else
{
ToBox.Focus(FocusState.Programmatic);
if (FocusManager.GetFocusedElement(XamlRoot) == ToBox)
{
return;
}
}
await Task.Delay(TimeSpan.FromMilliseconds(50));
}
}
private async Task RenderComposeHtmlAsync(string html)
{
await WebViewEditor.RenderHtmlAsync(html);
await ApplyInitialFocusAsync();
}
} }