From 98c2b0f9e0418877fdb28f64684b8831ad6d1f3c Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 29 Oct 2020 20:27:36 +0000 Subject: [PATCH 1/9] Initial implementation of IsKeyboardFocusWithin --- src/Avalonia.Input/ApiCompatBaseline.txt | 4 ++ src/Avalonia.Input/IInputElement.cs | 5 ++ src/Avalonia.Input/InputElement.cs | 24 +++++++- src/Avalonia.Input/KeyboardDevice.cs | 71 ++++++++++++++++++++++++ 4 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 src/Avalonia.Input/ApiCompatBaseline.txt diff --git a/src/Avalonia.Input/ApiCompatBaseline.txt b/src/Avalonia.Input/ApiCompatBaseline.txt new file mode 100644 index 0000000000..d960664c1b --- /dev/null +++ b/src/Avalonia.Input/ApiCompatBaseline.txt @@ -0,0 +1,4 @@ +Compat issues with assembly Avalonia.Input: +InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Input.IInputElement.IsKeyboardFocusWithin' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Input.IInputElement.IsKeyboardFocusWithin.get()' is present in the implementation but not in the contract. +Total Issues: 2 diff --git a/src/Avalonia.Input/IInputElement.cs b/src/Avalonia.Input/IInputElement.cs index 12fec82368..7aa9c32bca 100644 --- a/src/Avalonia.Input/IInputElement.cs +++ b/src/Avalonia.Input/IInputElement.cs @@ -89,6 +89,11 @@ namespace Avalonia.Input /// value of this control and its parent controls. /// bool IsEffectivelyEnabled { get; } + + /// + /// Gets a value indicating whether keyboard focus is anywhere within the element or its visual tree child elements. + /// + bool IsKeyboardFocusWithin { get; } /// /// Gets a value indicating whether the control is focused. diff --git a/src/Avalonia.Input/InputElement.cs b/src/Avalonia.Input/InputElement.cs index 9ace7fd92d..1db7db6ba2 100644 --- a/src/Avalonia.Input/InputElement.cs +++ b/src/Avalonia.Input/InputElement.cs @@ -42,6 +42,14 @@ namespace Avalonia.Input public static readonly StyledProperty CursorProperty = AvaloniaProperty.Register(nameof(Cursor), null, true); + /// + /// Defines the property. + /// + public static readonly DirectProperty IsKeyboardFocusWithinProperty = + AvaloniaProperty.RegisterDirect( + nameof(IsKeyboardFocusWithin), + o => o.IsKeyboardFocusWithin); + /// /// Defines the property. /// @@ -160,6 +168,7 @@ namespace Avalonia.Input private bool _isEffectivelyEnabled = true; private bool _isFocused; + private bool _isKeyboardFocusWithin; private bool _isFocusVisible; private bool _isPointerOver; private GestureRecognizerCollection? _gestureRecognizers; @@ -343,6 +352,15 @@ namespace Avalonia.Input get { return GetValue(CursorProperty); } set { SetValue(CursorProperty, value); } } + + /// + /// Gets a value indicating whether keyboard focus is anywhere within the element or its visual tree child elements. + /// + public bool IsKeyboardFocusWithin + { + get => _isKeyboardFocusWithin; + internal set => SetAndRaise(IsKeyboardFocusWithinProperty, ref _isKeyboardFocusWithin, value); + } /// /// Gets a value indicating whether the control is focused. @@ -423,7 +441,7 @@ namespace Avalonia.Input base.OnAttachedToVisualTreeCore(e); UpdateIsEffectivelyEnabled(); } - + /// /// Called before the event occurs. /// @@ -544,6 +562,10 @@ namespace Avalonia.Input { UpdatePseudoClasses(null, change.NewValue.GetValueOrDefault()); } + else if (change.Property == IsKeyboardFocusWithinProperty) + { + PseudoClasses.Set(":focus-within", _isKeyboardFocusWithin); + } } /// diff --git a/src/Avalonia.Input/KeyboardDevice.cs b/src/Avalonia.Input/KeyboardDevice.cs index 187670a26b..758a398f3a 100644 --- a/src/Avalonia.Input/KeyboardDevice.cs +++ b/src/Avalonia.Input/KeyboardDevice.cs @@ -31,6 +31,74 @@ namespace Avalonia.Input RaisePropertyChanged(); } } + + private void ClearFocusWithin(IInputElement element, bool clearRoot) + { + foreach (IInputElement el in element.VisualChildren) + { + if (el.IsKeyboardFocusWithin) + { + ClearFocusWithin(el, true); + break; + } + } + + if(clearRoot) + { + if (element is InputElement ie) + { + ie.IsKeyboardFocusWithin = false; + } + } + } + + private void SetIsFocusWithin(InputElement oldElement, InputElement newElement) + { + InputElement? branch = null; + + InputElement el = newElement; + + while (el != null) + { + if (el.IsKeyboardFocusWithin) + { + branch = el; + break; + } + + if ((el as IInputElement).VisualParent is InputElement ie) + { + el = ie; + } + else + { + break; + } + } + + el = oldElement; + + if (el != null && branch != null) + { + ClearFocusWithin(branch, false); + } + + el = newElement; + + while (el != null && el != branch) + { + el.IsKeyboardFocusWithin = true; + + if ((el as IInputElement).VisualParent is InputElement ie) + { + el = ie; + } + else + { + break; + } + } + } public void SetFocusedElement( IInputElement? element, @@ -40,6 +108,9 @@ namespace Avalonia.Input if (element != FocusedElement) { var interactive = FocusedElement as IInteractive; + + SetIsFocusWithin(FocusedElement as InputElement, element as InputElement); + FocusedElement = element; interactive?.RaiseEvent(new RoutedEventArgs From 7cae6af637aff6ab3ee180049911d2a1df01ba44 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 29 Oct 2020 21:24:43 +0000 Subject: [PATCH 2/9] less casting. --- src/Avalonia.Input/KeyboardDevice.cs | 31 +++++++++------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/src/Avalonia.Input/KeyboardDevice.cs b/src/Avalonia.Input/KeyboardDevice.cs index 758a398f3a..9653b78e2d 100644 --- a/src/Avalonia.Input/KeyboardDevice.cs +++ b/src/Avalonia.Input/KeyboardDevice.cs @@ -52,11 +52,11 @@ namespace Avalonia.Input } } - private void SetIsFocusWithin(InputElement oldElement, InputElement newElement) + private void SetIsFocusWithin(IInputElement oldElement, IInputElement newElement) { - InputElement? branch = null; + IInputElement? branch = null; - InputElement el = newElement; + IInputElement el = newElement; while (el != null) { @@ -66,14 +66,7 @@ namespace Avalonia.Input break; } - if ((el as IInputElement).VisualParent is InputElement ie) - { - el = ie; - } - else - { - break; - } + el = (IInputElement)el.VisualParent; } el = oldElement; @@ -87,16 +80,12 @@ namespace Avalonia.Input while (el != null && el != branch) { - el.IsKeyboardFocusWithin = true; - - if ((el as IInputElement).VisualParent is InputElement ie) + if (el is InputElement ie) { - el = ie; - } - else - { - break; + ie.IsKeyboardFocusWithin = true; } + + el = (IInputElement)el.VisualParent; } } @@ -108,8 +97,8 @@ namespace Avalonia.Input if (element != FocusedElement) { var interactive = FocusedElement as IInteractive; - - SetIsFocusWithin(FocusedElement as InputElement, element as InputElement); + + SetIsFocusWithin(FocusedElement, element); FocusedElement = element; From deb0a58eb48e7f8eb4e0f401809fdefe8a78f327 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 2 Nov 2020 11:07:01 +0000 Subject: [PATCH 3/9] Add some unit tests for FocusWithin --- .../InputElement_Focus.cs | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/tests/Avalonia.Input.UnitTests/InputElement_Focus.cs b/tests/Avalonia.Input.UnitTests/InputElement_Focus.cs index 09fae7207f..7e1f7f1dc3 100644 --- a/tests/Avalonia.Input.UnitTests/InputElement_Focus.cs +++ b/tests/Avalonia.Input.UnitTests/InputElement_Focus.cs @@ -121,5 +121,93 @@ namespace Avalonia.Input.UnitTests Assert.False(target2.Classes.Contains(":focus-visible")); } } + + [Fact] + public void Control_FocusWithin_PseudoClass_Should_Be_Applied() + { + using (UnitTestApplication.Start(TestServices.RealFocus)) + { + var target1 = new Decorator(); + var target2 = new Decorator(); + var root = new TestRoot + { + Child = new StackPanel + { + Children = + { + target1, + target2 + } + } + }; + + target1.ApplyTemplate(); + target2.ApplyTemplate(); + + FocusManager.Instance?.Focus(target1); + Assert.True(target1.IsFocused); + Assert.True(target1.Classes.Contains(":focus-within")); + Assert.True(target1.IsKeyboardFocusWithin); + Assert.True(root.Child.Classes.Contains(":focus-within")); + Assert.True(root.Child.IsKeyboardFocusWithin); + Assert.True(root.Classes.Contains(":focus-within")); + Assert.True(root.IsKeyboardFocusWithin); + } + } + + [Fact] + public void Control_FocusWithin_PseudoClass_Should_Be_Applied_and_Removed() + { + using (UnitTestApplication.Start(TestServices.RealFocus)) + { + var target1 = new Decorator(); + var target2 = new Decorator(); + var panel1 = new Panel { Children = { target1 } }; + var panel2 = new Panel { Children = { target2 } }; + var root = new TestRoot + { + Child = new StackPanel + { + Children = + { + panel1, + panel2 + } + } + }; + + target1.ApplyTemplate(); + target2.ApplyTemplate(); + + FocusManager.Instance?.Focus(target1); + Assert.True(target1.IsFocused); + Assert.True(target1.Classes.Contains(":focus-within")); + Assert.True(target1.IsKeyboardFocusWithin); + Assert.True(panel1.Classes.Contains(":focus-within")); + Assert.True(panel1.IsKeyboardFocusWithin); + Assert.True(root.Child.Classes.Contains(":focus-within")); + Assert.True(root.Child.IsKeyboardFocusWithin); + Assert.True(root.Classes.Contains(":focus-within")); + Assert.True(root.IsKeyboardFocusWithin); + + FocusManager.Instance?.Focus(target2); + + Assert.False(target1.IsFocused); + Assert.False(target1.Classes.Contains(":focus-within")); + Assert.False(target1.IsKeyboardFocusWithin); + Assert.False(panel1.Classes.Contains(":focus-within")); + Assert.False(panel1.IsKeyboardFocusWithin); + Assert.True(root.Child.Classes.Contains(":focus-within")); + Assert.True(root.Child.IsKeyboardFocusWithin); + Assert.True(root.Classes.Contains(":focus-within")); + Assert.True(root.IsKeyboardFocusWithin); + + Assert.True(target2.IsFocused); + Assert.True(target2.Classes.Contains(":focus-within")); + Assert.True(target2.IsKeyboardFocusWithin); + Assert.True(panel2.Classes.Contains(":focus-within")); + Assert.True(panel2.IsKeyboardFocusWithin); + } + } } } From f9f4e73bd3f5691e3fa5b99baff125ecb7934aab Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 2 Nov 2020 11:09:07 +0000 Subject: [PATCH 4/9] whitespace. --- src/Avalonia.Input/InputElement.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Input/InputElement.cs b/src/Avalonia.Input/InputElement.cs index 1db7db6ba2..66fb9cfb1c 100644 --- a/src/Avalonia.Input/InputElement.cs +++ b/src/Avalonia.Input/InputElement.cs @@ -441,7 +441,7 @@ namespace Avalonia.Input base.OnAttachedToVisualTreeCore(e); UpdateIsEffectivelyEnabled(); } - + /// /// Called before the event occurs. /// From 8e2d55523922a5b03a388244496f6dc5587663d0 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 2 Nov 2020 11:48:27 +0000 Subject: [PATCH 5/9] focus within copes with detach from tree. --- src/Avalonia.Input/KeyboardDevice.cs | 57 ++++++++++++++++++- .../InputElement_Focus.cs | 45 +++++++++++++++ 2 files changed, 100 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Input/KeyboardDevice.cs b/src/Avalonia.Input/KeyboardDevice.cs index 9653b78e2d..43a44331a7 100644 --- a/src/Avalonia.Input/KeyboardDevice.cs +++ b/src/Avalonia.Input/KeyboardDevice.cs @@ -9,6 +9,7 @@ namespace Avalonia.Input public class KeyboardDevice : IKeyboardDevice, INotifyPropertyChanged { private IInputElement? _focusedElement; + private IInputRoot? _focusedRoot; public event PropertyChangedEventHandler? PropertyChanged; @@ -28,9 +29,34 @@ namespace Avalonia.Input private set { _focusedElement = value; + + if (_focusedElement != null && _focusedElement.IsAttachedToVisualTree) + { + _focusedRoot = _focusedElement.VisualRoot as IInputRoot; + } + else + { + _focusedRoot = null; + } + RaisePropertyChanged(); } } + + private void ClearFocusWithinAncestors(IInputElement element) + { + IInputElement el = element; + + while (el != null) + { + if (el is InputElement ie) + { + ie.IsKeyboardFocusWithin = false; + } + + el = (IInputElement)el.VisualParent; + } + } private void ClearFocusWithin(IInputElement element, bool clearRoot) { @@ -54,9 +80,15 @@ namespace Avalonia.Input private void SetIsFocusWithin(IInputElement oldElement, IInputElement newElement) { + if (newElement == null && oldElement != null) + { + ClearFocusWithinAncestors(oldElement); + return; + } + IInputElement? branch = null; - IInputElement el = newElement; + IInputElement? el = newElement; while (el != null) { @@ -69,7 +101,7 @@ namespace Avalonia.Input el = (IInputElement)el.VisualParent; } - el = oldElement; + el = oldElement!; if (el != null && branch != null) { @@ -88,6 +120,22 @@ namespace Avalonia.Input el = (IInputElement)el.VisualParent; } } + + private void ClearChildrenFocusWithin(IInputElement element,bool clearRoot) + { + foreach (IInputElement el in element.VisualChildren) + { + if (el.IsKeyboardFocusWithin) + { + ClearChildrenFocusWithin(el, true); + break; + } + } + if(clearRoot && element is InputElement ie) + { + ie.IsKeyboardFocusWithin = false; + } + } public void SetFocusedElement( IInputElement? element, @@ -98,6 +146,11 @@ namespace Avalonia.Input { var interactive = FocusedElement as IInteractive; + if (FocusedElement != null && !FocusedElement.IsAttachedToVisualTree && _focusedRoot != null) + { + ClearChildrenFocusWithin(_focusedRoot, true); + } + SetIsFocusWithin(FocusedElement, element); FocusedElement = element; diff --git a/tests/Avalonia.Input.UnitTests/InputElement_Focus.cs b/tests/Avalonia.Input.UnitTests/InputElement_Focus.cs index 7e1f7f1dc3..b79ae92ef3 100644 --- a/tests/Avalonia.Input.UnitTests/InputElement_Focus.cs +++ b/tests/Avalonia.Input.UnitTests/InputElement_Focus.cs @@ -209,5 +209,50 @@ namespace Avalonia.Input.UnitTests Assert.True(panel2.IsKeyboardFocusWithin); } } + + [Fact] + public void Control_FocusVsisible_Pseudoclass_Should_Be_Removed_When_Removed_From_Tree() + { + using (UnitTestApplication.Start(TestServices.RealFocus)) + { + var target1 = new Decorator(); + var target2 = new Decorator(); + var root = new TestRoot + { + Child = new StackPanel + { + Children = + { + target1, + target2 + } + } + }; + + target1.ApplyTemplate(); + target2.ApplyTemplate(); + + FocusManager.Instance?.Focus(target1); + Assert.True(target1.IsFocused); + Assert.True(target1.Classes.Contains(":focus-within")); + Assert.True(target1.IsKeyboardFocusWithin); + Assert.True(root.Child.Classes.Contains(":focus-within")); + Assert.True(root.Child.IsKeyboardFocusWithin); + Assert.True(root.Classes.Contains(":focus-within")); + Assert.True(root.IsKeyboardFocusWithin); + + Assert.Equal(KeyboardDevice.Instance.FocusedElement, target1); + + root.Child = null; + + Assert.Null(KeyboardDevice.Instance.FocusedElement); + + Assert.False(target1.IsFocused); + Assert.False(target1.Classes.Contains(":focus-within")); + Assert.False(target1.IsKeyboardFocusWithin); + Assert.False(root.Classes.Contains(":focus-within")); + Assert.False(root.IsKeyboardFocusWithin); + } + } } } From 2baae49a12e55fa4fe20d993d00e95f42bcc7501 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 2 Nov 2020 17:28:51 +0000 Subject: [PATCH 6/9] fix nullable reference type warnings. --- src/Avalonia.Input/KeyboardDevice.cs | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/Avalonia.Input/KeyboardDevice.cs b/src/Avalonia.Input/KeyboardDevice.cs index 43a44331a7..099fc74766 100644 --- a/src/Avalonia.Input/KeyboardDevice.cs +++ b/src/Avalonia.Input/KeyboardDevice.cs @@ -43,9 +43,9 @@ namespace Avalonia.Input } } - private void ClearFocusWithinAncestors(IInputElement element) + private void ClearFocusWithinAncestors(IInputElement? element) { - IInputElement el = element; + var el = element; while (el != null) { @@ -60,16 +60,16 @@ namespace Avalonia.Input private void ClearFocusWithin(IInputElement element, bool clearRoot) { - foreach (IInputElement el in element.VisualChildren) + foreach (var visual in element.VisualChildren) { - if (el.IsKeyboardFocusWithin) + if (visual is IInputElement el && el.IsKeyboardFocusWithin) { ClearFocusWithin(el, true); break; } } - if(clearRoot) + if (clearRoot) { if (element is InputElement ie) { @@ -78,7 +78,7 @@ namespace Avalonia.Input } } - private void SetIsFocusWithin(IInputElement oldElement, IInputElement newElement) + private void SetIsFocusWithin(IInputElement? oldElement, IInputElement? newElement) { if (newElement == null && oldElement != null) { @@ -88,7 +88,7 @@ namespace Avalonia.Input IInputElement? branch = null; - IInputElement? el = newElement; + var el = newElement; while (el != null) { @@ -101,7 +101,7 @@ namespace Avalonia.Input el = (IInputElement)el.VisualParent; } - el = oldElement!; + el = oldElement; if (el != null && branch != null) { @@ -121,17 +121,18 @@ namespace Avalonia.Input } } - private void ClearChildrenFocusWithin(IInputElement element,bool clearRoot) + private void ClearChildrenFocusWithin(IInputElement element, bool clearRoot) { - foreach (IInputElement el in element.VisualChildren) + foreach (var visual in element.VisualChildren) { - if (el.IsKeyboardFocusWithin) + if (visual is IInputElement el && el.IsKeyboardFocusWithin) { ClearChildrenFocusWithin(el, true); break; } } - if(clearRoot && element is InputElement ie) + + if (clearRoot && element is InputElement ie) { ie.IsKeyboardFocusWithin = false; } From 7354ba337a3b7883878735f4eb21995b7e53760a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 2 Nov 2020 18:50:27 +0100 Subject: [PATCH 7/9] Use as instead of cast. In case someone's doing something funky, fail gracefully. --- src/Avalonia.Input/KeyboardDevice.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Input/KeyboardDevice.cs b/src/Avalonia.Input/KeyboardDevice.cs index 099fc74766..1a161ae56a 100644 --- a/src/Avalonia.Input/KeyboardDevice.cs +++ b/src/Avalonia.Input/KeyboardDevice.cs @@ -98,7 +98,7 @@ namespace Avalonia.Input break; } - el = (IInputElement)el.VisualParent; + el = el.VisualParent as IInputElement; } el = oldElement; @@ -117,8 +117,8 @@ namespace Avalonia.Input ie.IsKeyboardFocusWithin = true; } - el = (IInputElement)el.VisualParent; - } + el = el.VisualParent as IInputElement; + } } private void ClearChildrenFocusWithin(IInputElement element, bool clearRoot) From 9dee53c7d954a39fa5b3adcfdee00f1ddf0c3d18 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 2 Nov 2020 18:58:15 +0000 Subject: [PATCH 8/9] add failing unit test for focus within tranfer of focus between roots. --- .../InputElement_Focus.cs | 66 ++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Input.UnitTests/InputElement_Focus.cs b/tests/Avalonia.Input.UnitTests/InputElement_Focus.cs index b79ae92ef3..8b8f2fa775 100644 --- a/tests/Avalonia.Input.UnitTests/InputElement_Focus.cs +++ b/tests/Avalonia.Input.UnitTests/InputElement_Focus.cs @@ -211,7 +211,7 @@ namespace Avalonia.Input.UnitTests } [Fact] - public void Control_FocusVsisible_Pseudoclass_Should_Be_Removed_When_Removed_From_Tree() + public void Control_FocusWithin_Pseudoclass_Should_Be_Removed_When_Removed_From_Tree() { using (UnitTestApplication.Start(TestServices.RealFocus)) { @@ -254,5 +254,69 @@ namespace Avalonia.Input.UnitTests Assert.False(root.IsKeyboardFocusWithin); } } + + [Fact] + public void Control_FocusWithin_Pseudoclass_Should_Be_Removed_Focus_Moves_To_Different_Root() + { + using (UnitTestApplication.Start(TestServices.RealFocus)) + { + var target1 = new Decorator(); + var target2 = new Decorator(); + + var root1 = new TestRoot + { + Child = new StackPanel + { + Children = + { + target1, + } + } + }; + + var root2 = new TestRoot + { + Child = new StackPanel + { + Children = + { + target2, + } + } + }; + + target1.ApplyTemplate(); + target2.ApplyTemplate(); + + FocusManager.Instance?.Focus(target1); + Assert.True(target1.IsFocused); + Assert.True(target1.Classes.Contains(":focus-within")); + Assert.True(target1.IsKeyboardFocusWithin); + Assert.True(root1.Child.Classes.Contains(":focus-within")); + Assert.True(root1.Child.IsKeyboardFocusWithin); + Assert.True(root1.Classes.Contains(":focus-within")); + Assert.True(root1.IsKeyboardFocusWithin); + + Assert.Equal(KeyboardDevice.Instance.FocusedElement, target1); + + FocusManager.Instance?.Focus(target2); + + Assert.False(target1.IsFocused); + Assert.False(target1.Classes.Contains(":focus-within")); + Assert.False(target1.IsKeyboardFocusWithin); + Assert.False(root1.Child.Classes.Contains(":focus-within")); + Assert.False(root1.Child.IsKeyboardFocusWithin); + Assert.False(root1.Classes.Contains(":focus-within")); + Assert.False(root1.IsKeyboardFocusWithin); + + Assert.True(target2.IsFocused); + Assert.True(target2.Classes.Contains(":focus-within")); + Assert.True(target2.IsKeyboardFocusWithin); + Assert.True(root2.Child.Classes.Contains(":focus-within")); + Assert.True(root2.Child.IsKeyboardFocusWithin); + Assert.True(root2.Classes.Contains(":focus-within")); + Assert.True(root2.IsKeyboardFocusWithin); + } + } } } From d36b7a40df223b7a3805dfb57dae9e01392fcfd1 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 2 Nov 2020 19:02:54 +0000 Subject: [PATCH 9/9] ensure focus-within is removed when focus moves to a different root. --- src/Avalonia.Input/KeyboardDevice.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Input/KeyboardDevice.cs b/src/Avalonia.Input/KeyboardDevice.cs index 1a161ae56a..6f4cb7a35c 100644 --- a/src/Avalonia.Input/KeyboardDevice.cs +++ b/src/Avalonia.Input/KeyboardDevice.cs @@ -147,7 +147,10 @@ namespace Avalonia.Input { var interactive = FocusedElement as IInteractive; - if (FocusedElement != null && !FocusedElement.IsAttachedToVisualTree && _focusedRoot != null) + if (FocusedElement != null && + (!FocusedElement.IsAttachedToVisualTree || + _focusedRoot != element?.VisualRoot as IInputRoot) && + _focusedRoot != null) { ClearChildrenFocusWithin(_focusedRoot, true); }