* add failing test for detached focus
* Clear focus if restore element is not in focus scope
* check for focus eligibility when restoring focus
* add failing test for second focus scope stealing focus
* if focus scope is not the current focus scope, don't attempt to set focus
* fix(input): fix AccessKeyHandler when no descendant control has focus
- Update IsFocusWithinOwner to use routed event's Source instead of re-querying KeyboardDevice.Instance.FocusedElement
- Allow access keys (Alt/mnemonics) to work immediately after Window opens when FocusedElement is null or owner itself
- Update shared test helpers in AccessKeyHandlerTests to populate event Source
Fixes#21806
* fix(input): address review feedback on AccessKeyHandler focus fix
- Type IsFocusWithinOwner's owner parameter as InputElement (matching
_owner) and drop the now-redundant `is Visual` check
- Fix indentation in IsFocusWithinOwner
- Add Should_Raise_AccessKey_When_Focus_Is_On_Descendant, covering the
IsVisualAncestorOf branch by raising KeyDown/KeyUp on a descendant
control instead of the owner
- Revert the no-op Source assignment in the KeyDown/KeyUp test helpers
- Shorten the comment in Should_Raise_Key_Events_For_Registered_Access_Key
---------
Co-authored-by: Julien Lebosquain <julien@lebosquain.net>
* fix(focus): infinite loop in tab stop search with TabNavigation=Once
`FocusManager.FindNextElement(Next|Previous)` and `TryMoveFocus(Next|Previous)` never
return when the focused element sits inside a container with
`KeyboardNavigationMode.Once`. The calling thread spins at 100% CPU forever; in a
desktop app that means a hard hang of the UI thread requiring the process to be killed.
## Root cause
The upward walk in `GetNextTabStop` / `GetPreviousTabStop` advances at the end of each
iteration with `parent = GetFocusParent(parent)`. But when the walk reaches a container
whose `TabNavigation` is `Once` (or `None`, in one branch), the code resets `parent`
from `focused` instead of from `current`:
```csharp
current = parent;
parent = FocusHelpers.GetFocusParent(focused); // focused is a loop invariant
```
`focused` never changes, so `parent` drops back down to the focused element's immediate
parent. The next iteration walks up to the same container again and takes the same
branch, so the walk oscillates between two nodes indefinitely. None of the three loop
exit conditions (`parent != null`, `!parentIsRootVisual`, `newTabStop == null`) can ever
be satisfied.
The correct form already exists a few lines below in `GetNextTabStop`, in the
structurally identical `KeyboardNavigationMode.None` branch:
```csharp
current = pIE;
parent = FocusHelpers.GetFocusParent(current); // walks up, converges
```
This changes the three remaining occurrences to match it: one in `GetNextTabStop`, two in
`GetPreviousTabStop`. The two loop initializers outside the `while` (`FocusManager.cs:647`
and `:748`) correctly keep using `focused` and are left alone.
## Reproducing it
The focused element has to be nested **at least one level below** the `Once` container.
When it is a direct child, `GetFocusParent(focused)` happens to return that same container
and the walk terminates by accident - which is likely why this went unnoticed for so long.
Minimal shape (used by both new tests):
```
StackPanel
├── StackPanel [TabNavigation=Once]
│ └── StackPanel
│ └── Button <- focused
└── Button <- expected result for Next
```
Found in a production app: FluentAvalonia's `ContentDialog` calls
`FindNextElement(NavigationDirection.Next, ...)` from its `Loaded` handler to pick an
initial focus target. With focus sitting on a nested `NavigationView` item - the `Once`
container comes from the NavigationView template - opening any dialog hung the app
permanently.
## Scope
Only the programmatic focus APIs go through this code. Pressing Tab is unaffected:
`KeyboardNavigationHandler` uses the separate, WPF-derived implementation in
`Navigation/TabNavigation.cs`, which handles `Once` correctly by passing the container
itself as the new starting point.
## Verification
- Two regression tests added to `InputElement_Focus`, covering both directions.
- Confirmed they actually catch the bug: with the `FocusManager.cs` change reverted, the
Next test ran for 90s using 89.5s of CPU and the Previous test for 60s using 59.6s
before being killed. With the fix both return immediately.
- Full `Avalonia.Base.UnitTests` suite: 2996 tests, 0 failed (2984 passed, 12 skipped).
## Not addressed here
The `Once` branches are asymmetric: `GetPreviousTabStop` returns the container when it is
focusable (`if (FocusHelpers.IsFocusable(parent)) newTabStop = parent;`), `GetNextTabStop`
has no such check. Separately, in the Previous direction this shape ends up returning the
focused element itself via the cycle fallback in `GetTabStopCandidateElement`, rather than
the element preceding the container. Both look like genuine issues, but they are
behavioural questions independent of the hang, so the Previous test only asserts that the
call terminates. Happy to follow up in a separate PR if you would like them fixed.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(focus): previous tab stop search accepted candidates after the focused element
Follow-up to the review feedback on the Previous test: asserting the exact element
exposed that `GetPreviousTabStop` could not produce it. This turned out to be a second,
independent porting error in the same code, so this commit fixes it and tightens both
tests to exact-element assertions.
## Root cause
`GetNextOrPreviousTabStopInternal` accepts an equal-tab-index candidate for the
Previous direction with:
```csharp
if (compareIndexResult < 0 || (((foundCurrent || currentPassed) || compareCurrentForPreviousElement) && compareIndexResult == 0))
```
The WinUI implementation this code is ported from (see
`CFocusManager::GetPreviousTabStopInternal`, faithfully mirrored in Uno's
`FocusManager.mux.cs`) reads:
```cpp
if (compareIndexResult < 0 ||
(((!bFoundCurrent && !bCurrentPassed) || bCurrentCompare) && compareIndexResult == 0))
```
The negations were lost in porting, inverting the condition: since `TabIndex` defaults
to `int.MaxValue`, sibling comparisons are almost always equal, so the Previous search
skipped every element *before* the focused one and accepted elements *after* it. The
Next direction's condition matches WinUI and is untouched.
## Impact
Not limited to the `Once` scenario from the previous commit - `FindNextElement(Previous)`
and `TryMoveFocus(Previous)` were wrong in a plain flat container: with focus on the
third of four buttons, the search returned a following element rather than the preceding
one, and where no following sibling existed it fell back to cycling, handing back the
last focusable element in scope (observed in the Once test as "returns the focused
element itself"). Keyboard Shift+Tab is unaffected as it uses the separate
`TabNavigation.cs` implementation.
## Tests
- `Can_Get_Previous_Element` (new): flat container, focus on target3, asserts target2 -
locks both "skip candidates after the focused element" and "keep the closest
preceding sibling" (not target1).
- `Can_Get_Previous_Element_Out_Of_Container_With_TabNavigation_Once`: now asserts the
exact element (`before`) instead of only termination, per review.
- Verified both fail with the condition reverted and pass with it.
- Full `Avalonia.Base.UnitTests`: 2997 tests, 0 failed (2985 passed, 12 skipped).
## Also spotted, not changed here
The Previous/Cycle branch in `GetPreviousTabStop` calls `GetFirstFocusableElement`
where WinUI calls `GetLastFocusableElement` (wrapping backwards inside a Cycle scope
should land on the last element). Happy to fix that here too if you want it in this PR,
otherwise I can open a separate one.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(focus): previous tab stop wrapped to the first element of a Cycle scope
Third porting divergence found while comparing this code against the WinUI original
(all three sit in the same two functions): the Previous/Cycle branch in
`GetPreviousTabStop` called `GetFirstFocusableElement` where WinUI calls
`GetLastFocusableElement`:
```cpp
// WinUI, CFocusManager::GetPreviousTabStop
if (IsValidTabStopSearchCandidate(pCurrent) && GetTabNavigation(pCurrent) == KeyboardNavigationMode::Cycle)
{
pNewTabStop = GetLastFocusableElement(pCurrent, pCurrent);
break;
}
```
Wrapping backwards inside a Cycle scope must land on the LAST focusable element,
mirroring the forward wrap (last -> first). Taking the first element instead meant
that with focus on the first tab stop of a `TabNavigation=Cycle` container,
`FindNextElement(Previous)` returned the focused element itself and
`TryMoveFocus(Previous)` was a no-op - focus could neither leave the scope (by design)
nor wrap within it (the bug).
Observed against the keyboard-path reference implementation on a Cycle container
[a, b, c] with focus on `a`: `KeyboardNavigationHandler.GetNext(a, Previous)` returns
`c`, this code returned `a`. The forward direction already wrapped correctly
(`c` -> `a`) because the Next branch happens to use the correct element there.
New test `Previous_Wraps_To_Last_Element_In_Cycle_Container` asserts the wrap target;
verified it fails (returns the focused element) with the one-line change reverted.
Full `Avalonia.Base.UnitTests`: 2998 tests, 0 failed (2986 passed, 12 skipped).
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Add tests around skipping controls for FocusManager #21620
* Return false by default in CanHaveFocusableChildren #21620
Importantly, this will return false for controls that are not focusable (e.g. TextBlock) or that have no focusable childen (e.g. an empty StackPanel)
* Add skip tests for FindFirstFocusableElement and FindLastFocusableElement #21620
Return null when an IPresentationSource can't convert from/to screen coordinates
Document exceptions in VisualExtensions PointToClient and PointToScreen
Add tests for cross-root pointer event positions
* fix: check format key in DataTransferItem.FindAccessor single-item path
The single-item fast path in FindAccessor returned the stored value for
any format query without checking if the requested format matched.
This caused TryGetRaw to return wrong data when queried with a format
different from the one stored (e.g., querying Bitmap on a text-only item
returned the text value instead of null).
Add the missing singleItem.Key.Equals(format) check, consistent with
the dictionary path (TryGetValue) and RemoveCore.
* chore: retrigger CI
* chore: retrigger CI
---------
Co-authored-by: Julien Lebosquain <julien@lebosquain.net>
* test: add DataFormat.CreateInProcessFormat tests
Cover the new InProcess format API: kind, identifier, null/empty
validation, non-ASCII identifiers, HasSystemName for all four kinds,
ToSystemName throwing, equality/inequality, DataTransferItem
integration, and coexistence with other formats in DataTransfer.
* feat: implement DataFormat.CreateInProcessFormat<T> for in-process drag/drop
Add DataFormatKind.InProcess and DataFormat.CreateInProcessFormat<T>()
so users can pass arbitrary object references during in-process
drag-and-drop without crossing serialization boundaries.
- Add HasSystemName property to indicate whether ToSystemName() is valid
- Update ToSystemName to throw for InProcess (same as Universal)
- Guard all 7 platform backends (Win32, macOS, X11, Android, Browser,
iOS) to skip InProcess formats during clipboard/drag-drop enumeration
Closes#20097
* fix: remove HasSystemName API per review feedback
* test: add regression test for access key with system key events
Regression test for #20961: verifies that access keys fire correctly
when triggered via Alt+key (system key events).
* fix: provide KeySymbol for system key events via MapVirtualKey
On Windows, WM_SYSKEYDOWN (Alt+key) intentionally skips ToUnicodeEx
to avoid corrupting keyboard state. This left KeySymbol null, which
broke access keys after #20662 switched from Key to KeySymbol.
Use MapVirtualKey(VK, MAPVK_VK_TO_CHAR) as a layout-aware fallback
for system key events — it resolves the character without touching
keyboard state.
Fixes#20961
* chore: retrigger CI
* Add FocusElement.FindNextElementOptions
* Add unit tests for FindNextElementOptions.FocusedElement
* Apply suggestions from code review
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* move gesture events from Gestures to InputElement. Fix holding gesture interactions with context menu
* update api diff
* address review comments
* fix some test types
* rename Cancelled to Canceled
* update api diff
* Extracted IInputRoot out of TopLevel
* Move some input handling out of TopLevel
* Remove old class, make layout manager private
* Removed IRenderRoot
* Make VisualTreeAttachmentEventArgs a bit more sensible
* Move ILayoutRoot to PresentationSource
# Conflicts:
# tests/Avalonia.Controls.UnitTests/TabControlTests.cs
* Updated some VisualRoot / GetVisualRoot usages
* Updated more XxxRoot usages
* More Root usages
* Addressed review
* Hurr-durr xml
* More fixes
* Maybe fix android compilation
* API diff
* Yet another cast
* I had to use MSIL analysis to detect those casts
* Fixed automation
* Fix PointerOverPreProcessor
* Fix?
* Removed yet another cast to Visual
* The amount of random downcasts is astonishing
* Maybe fix mac
* Addressed review
* Fire CaptureChanging event when source changes
* Do not notfiy platform if only source changed
* Notify ancestors of element to be captured if none yet
* Pointer capture notify on source change tests
---------
Co-authored-by: Jan Kučera <miloush@users.noreply.github.com>
* Update to xunit.v3
* Little more progress
* More fixes
* Keep VSTest supported
* Adjust Nuke
* Few fixes
* Fix for xunit 2
* Fix GetData override
* Adjust
* Use MTP for xunit 2
* Fix test
* Better fix
* --no-progress
* Few more fixes
* no progress
* Fix test
* Better fix
* TRX
* Move to Directory.Build.props
* Unify on MTP v2
* Update
* Update to stable
* 1.0.1
* 1.0.2
* Fix some warnings
* Fix more warnings
* Fix more warnings
* Enable nullability in UnitTests
* Enable nullability in Base.UnitTests
* Enable nullability in Markup.UnitTests
* Enable nullability in Markup.Xaml.UnitTests
* update mouse test to better simulate clicks on captured controls
* add tap failing test
* use captured element if available as source for tap gestures
* implement pre events for focus change
* add cancelling api to focus change
* add overload for SetFocusedElement
* add focus redirection
* update with api change requests
* search for visual parents when hittesting
* Add unit test for hit testing on disabled visual
---------
Co-authored-by: Julien Lebosquain <julien@lebosquain.net>
* Improve ToString() output in case when Key is set to Key.None
* Update KeyGesture.Parse method to support only modifiers combinations
* Update tests with empty combination and modifiers-only combinations
* Send dispose command for CompositionTarget as an OOB batch
* Make Close_Should_Remove_PointerOver to provide some render interface stub
* why ins't reactive stuff using our headless testing?
* fix?
---------
Co-authored-by: Dan Walmsley <dan@walms.co.uk>
* fix accelerator behavior for menu items and labels
* add elements with matching accelerator to test cycling in sub menus
* Add AccessKeyHandler tests for accelerators with more than one match
* Implement accelerator behavior based on WPF handling
* Remove commented code
* Remove OnAccessKey override => handled by DefaultMenuInteractionHandler
* remove obsolete test
* handle OnAccessKeyPressed for selected tab item
* fix unit tests
* use AccessKeyEvent instead of AccessKeyPressedEvent in unit tests
* navigate menu with and without ALT key
* Revert formatting changes in Tests
* Fix AccessKeyHandler comments
* move private types to bottom
* Remove lock statements, optimize removal of AccessKeyRegistrations
* remove call to Dispatcher.UIThread.Post
* simplifiy AccessKeyHandler.SortByHierarchy
* remove unnecessary method AccessKeyHandler.GetTargetsForSender
* regenerate API suppression file
* revert unneeded changes in MenuPage.axaml
* correct formatting changes
* do not sort by hierarchy if too few targets
* make AccessKeyEventArgs internal
* make AccessKeyPressedEventArgs internal
---------
Co-authored-by: Hans Docsek <hans.docsek@gmail.com>
* removed duplicated code between Window.Show and Window.ShowDialog
* Handling different cases of window initial position and size + unit test
* positioning cursor on resize grip in WindowOrder_Modal_Dialog_Stays_InFront_Of_Parent_When_Clicking_Resize_Grip test
* Fix for flaky test
* displaying decimal digits of slider value to avoid some issues with rounding
---------
Co-authored-by: Herman Kirshin <herman.kirshin@jetbrains.com>
* Basic failing unit test for UWP/WinUI XYFocus search boundary scenario.
* Change IsAllowedXYNavigationMode to return false if keyDeviceType is null and modes == disabled.
* Add helper function to find the closest InputElement to the target element whose parent does not allow XYFocus rather than always searching from the TopLevel. This restricts focus searches within a specific subtree rather than allowing searches to bridge subtrees that share an XYFocus disabled parent.
* test: CommandParameter does not change between CanExecute and Execute
* feat: CommandParameter does not change between CanExecute and Execute
* test: update
* Init
* Remove XY navigation cache as it's no use
* Use pooled collection for XY navigation
* Restructure code a bit, fix IScroller handling
* Init KeyboardNavigationTests_XY tests
* Simplify XYFocus.GetNextFocusableElement usage
* Minor fixes
* Add more tests
* Remove unused NuiKeyboardNavigationHandler
* Finalizing
* Fix tests
* Add TODO12
* Make XYFocusOptions a class
* Add TestServices.FocusableWindow and make KeyboardNavigationHandler lazy, as it can't be reused on multiple windows
* Fix KeyboardNavigationHandler events handling, when focus was not actually changed
* Add arrow key tests
* Replace XYFocusKeyboardNavigationMode with more flexible XYFocusNavigationModes, integrate with KeyDeviceType input types
* Make XY focus navigation less broken, when there is no starting focused control
* Several Android TV compatibility improvements
* Remap tizen Back button to Esc
* Introduce internal XYFocusHelpers
* Make ComboBox and AutoCompleteBox handle Key events only when it's needed
* Make TextBox handle Key events only when it's needed
* Ignore Alt+Down when XY navigation is enabled in CalendarDatePicker and SplitButton
* Rename IsAllowedXYNavigationMode
* Fix ButtonSpinner with XY navigation
* Implement a very simple focus engagement for GridSplitter and Slider
* Remove focus hack from Popup.
* Added failing focus scope tests.
* Refactor focus scopes in FocusManager.
- Store focused element within a scope using an attached property (like WPF)
- Store current focus root so that focus can be restored to that root when a focused control or active focus scope is removed
Fixes#13325
* Suppress API compat error.
This was being produced for a compiler-generated enumerable class that was erroneously being included in the reference assembly for `FocusManager`.
* Remove focus hack from ContextMenu.
And add failing test now that the hack is removed.
* Try to return a rooted host visual.
Fixes failing test from previous comment where focus wasn't restored when closing a context menu.
* Physical key handling for Windows
* Physical key handling for macOS
* Physical key handling for X11
* Physical keys: cleanup unused keys
* Key symbols: ensure consistent behavior between platforms
* Fix dead key symbol for Windows
* Physical key handling for browser
* Physical keys: use new overloads where possible
* Key symbol for VNC
* Physical key handling in previewer
* Key symbol for forwarded X11 IME key
* Key symbol for Android
* Obsolete old RawKeyEventArgs ctor
* Fix key symbols for macOS with modifiers
* Adjust PhysicalKey members naming
* Use explicit std::hash for AvnKey/AvnPhysicalKey
Should hopefully satisfy the older compiler on the CI server
* Headless: added KeyPressQwerty
---------
Co-authored-by: Dan Walmsley <dan@walms.co.uk>
Co-authored-by: Steven Kirk <grokys@users.noreply.github.com>