unoplatform / uno

Build Mobile, Desktop and WebAssembly apps with C# and XAML. Today. Open source and professionally supported.
https://platform.uno
Apache License 2.0
8.76k stars 706 forks source link

[Wasm] Focus event handling is not working properly #9246

Open mmarinchenko opened 2 years ago

mmarinchenko commented 2 years ago

Current behavior

Let's assume a simple 2-page app with login/password boxes and navigation view.

When application is loaded the LoginPage is displayed. The TextBox with username is drawn with a blue underscore as if it has a focus. But in fact this is not the case - text cursor is not displayed, the placeholder text is displayed (Enter username) overlapped by web browser's autofill value (123):

Login_Initial_Wasm

After pressing Tab button on a keyboard the placeholder text disappears, but text cursor is still not displayed, and typing does not work:

Login_Tab_Wasm

So the TextBox is not in edit mode. The second press of Tab button works correctly - focus is moved to the PasswordBox which enters into edit mode.


Another press on Tab button to move focus to Login button, then press on Enter to navigate to MainPage which contains NavigationView. NavigationView has PaneFooter which contains HyperlinkButton. HyperlinkButton gets focus, but web browser draws black border in the wrong position:

Main_Focused_Wasm

Note, that Login button performs HTTP GET request to https://demo.duendesoftware.com/.well-known/openid-configuration to trigger web browser's autofill. TextBox and PasswordBox may contain any non-null values.

Expected behavior

WinUI works correctly (but obviously there is no autofill):

Login_Initial_WinUI

Main_Focused_WinUI


Typical web login forms (like https://github.com/login) work correctly.

Uno Playground (https://playground.platform.uno/#wasm-start) works correctly - XAML editor has text cursor, etc.

How to reproduce it (as minimally and precisely as possible)

See Current behavior above.

Attachments:

Workaround

N/A

Works on UWP/WinUI

Yes

Environment

Uno.WinUI / Uno.WinUI.WebAssembly / Uno.WinUI.Skia

NuGet package version(s)

Wasm head:

Head_Wasm

WinUI head:

Head_WinUI

Affected platforms

WebAssembly

IDE

Visual Studio 2022

IDE version

17.2.5

Relevant plugins

N/A

Anything else we need to know?

N/A

mmarinchenko commented 2 years ago

Addition: it seems that both Tab when on LastFocusableElement and Shift+Tab when on FirstFocusableElement do not move the focus on WinUI but move it on Wasm. Ex., try pressing Shift+Tab when the focus is on Login TextBox.

jeromelaban commented 2 years ago

This look like a duplicate of https://github.com/unoplatform/uno/issues/7466, for which we still do not have a workaround for.

mmarinchenko commented 2 years ago

This look like a duplicate of #7466, for which we still do not have a workaround for.

Thanks, I didn't know about #7466 and #5737. I'm aware of autofill issue in web browsers.

But still, autofill apart:

mmarinchenko commented 12 months ago

I think I found an easy way to reproduce this issue.

1. I created a minimum possible wasm app using Uno VS extension. 2. Added git support, added a button to show ContentDialog, and committed my changes.

Download UnoFocusApp.zip

mmarinchenko commented 12 months ago

Steps to reproduce.

1. Run the app using wasm head. 2. Click on an empty space on the page and press Tab to see the focus mark on the button. 3. Press Enter to see ContentDialog. The focus mark will be in the wrong place: 1 4. Now expand the browser window to full screen and return to the non-expanded size again. The focus mark will get to the right place when the browser window is not expanded, and will shift again when the window is expanded. 2 5. Another interesting thing. If you click on the empty space on the page when ContentDialog is displayed, and then press Tab, the button on the main page will be focused, not the dialog button: 3

mmarinchenko commented 12 months ago
  • TextBox should be actually focused and should be in edit mode when highlighted with an accent color.

Regarding this point, perhaps the focus() method should be explicitly called by Uno for the "should-be-focused" element after rendering the DOM.

mmarinchenko commented 11 months ago
  • TextBox should be actually focused and should be in edit mode when highlighted with an accent color.

Regarding this point, perhaps the focus() method should be explicitly called by Uno for the "should-be-focused" element after rendering the DOM.

I came up with the following workaround for this (no future-proof, since it relies on Uno internals). The code below assumes the use of MVVM pattern.

1. First, we need to define the LoadedCommand for LoginPage and assign a name to UserNameBox:

LoginPage.xaml (click to expand) ```xaml <...> <...> ```

2. Second, we need to pass UserNameBox to ViewModel:

LoginPage.xaml.cs (click to expand) ```csharp using Microsoft.UI.Xaml.Controls; using UnoApplication.ViewModels; namespace UnoApplication.Views; internal sealed partial class LoginPage : Page { public LoginPage() { InitializeComponent(); ViewModel.BoxToFocus = UserNameBox; } public LoginViewModel ViewModel { get; } = App.GetService(); } ```

3. And finally, the workaround:

LoginViewModel.cs (click to expand) ```csharp using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Input; using Microsoft.UI.Xaml.Media; using System; namespace UnoApplication.ViewModels; internal sealed partial class LoginViewModel : ObservableRecipient { public TextBox? BoxToFocus { get; set; } [RelayCommand] private async Task Loaded(RoutedEventArgs args) { /* Manual focusing is a workaround due to the issue with initial focus, See https://github.com/unoplatform/uno/issues/9246 */ if (BoxToFocus is null) return; var focusResult = await FocusManager.TryFocusAsync(BoxToFocus, FocusState.Pointer); if (!focusResult.Succeeded) return; static FrameworkElement? findInternalTextBoxView(DependencyObject element) { var count = VisualTreeHelper.GetChildrenCount(element); for (var index = 0; index < count; ++index) { var child = VisualTreeHelper.GetChild(element, index); if (child is null) continue; if (child.GetType().ToString().EndsWith(".TextBoxView", StringComparison.Ordinal)) return child as FrameworkElement; var subCount = VisualTreeHelper.GetChildrenCount(child); if (subCount <= 0) continue; var subChild = findInternalTextBoxView(child); if (subChild is not null) return subChild; } return default; } var viewToFocus = findInternalTextBoxView(BoxToFocus); viewToFocus?.ExecuteJavascript("element.focus();"); } } ```