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