@ -10,277 +10,663 @@ namespace Avalonia.Input.Navigation
/// </summary>
internal static class TabNavigation
{
/// <summary>
/// Gets the next control in the specified tab direction.
/// </summary>
/// <param name="element">The element.</param>
/// <param name="direction">The tab direction. Must be Next or Previous.</param>
/// <param name="outsideElement">
/// If true will not descend into <paramref name="element"/> to find next control.
/// </param>
/// <returns>
/// The next element in the specified direction, or null if <paramref name="element"/>
/// was the last in the requested direction.
/// </returns>
public static IInputElement ? GetNextInTabOrder (
IInputElement element ,
NavigationDirection direction ,
bool outsideElement = false )
public static IInputElement ? GetNextTab ( IInputElement e , bool goDownOnly )
{
element = element ? ? throw new ArgumentNullException ( nameof ( element ) ) ;
if ( direction ! = NavigationDirection . Next & & direction ! = NavigationDirection . Previous )
{
throw new ArgumentException ( "Invalid direction: must be Next or Previous." ) ;
}
return GetNextTab ( e , GetGroupParent ( e ) , goDownOnly ) ;
}
var container = element . GetVisualParent < IInputElement > ( ) ;
public static IInputElement ? GetNextTab ( IInputElement ? e , IInputElement container , bool goDownOnly )
{
var tabbingType = GetKeyNavigationMode ( container ) ;
if ( container ! = null )
if ( e = = null )
{
var mode = KeyboardNavigation . GetTabNavigation ( ( InputElement ) container ) ;
if ( IsTabStop ( container ) )
return container ;
switch ( mode )
// Using ActiveElement if set
var activeElement = GetActiveElement ( container ) ;
if ( activeElement ! = null )
return GetNextTab ( null , activeElement , true ) ;
}
else
{
if ( tabbingType = = KeyboardNavigationMode . Once | | tabbingType = = KeyboardNavigationMode . None )
{
case KeyboardNavigationMode . Continue :
return GetNextInContainer ( element , container , direction , outsideElement ) ? ?
GetFirstInNextContainer ( element , element , direction ) ;
case KeyboardNavigationMode . Cycle :
return GetNextInContainer ( element , container , direction , outsideElement ) ? ?
GetFocusableDescendant ( container , direction ) ;
case KeyboardNavigationMode . Contained :
return GetNextInContainer ( element , container , direction , outsideElement ) ;
default :
return GetFirstInNextContainer ( element , container , direction ) ;
if ( container ! = e )
{
if ( goDownOnly )
return null ;
var parentContainer = GetGroupParent ( container ) ;
return GetNextTab ( container , parentContainer , goDownOnly ) ;
}
}
}
else
// All groups
IInputElement ? loopStartElement = null ;
var nextTabElement = e ;
var currentTabbingType = tabbingType ;
// Search down inside the container
while ( ( nextTabElement = GetNextTabInGroup ( nextTabElement , container , currentTabbingType ) ) ! = null )
{
return GetFocusableDescendants ( element , direction ) . FirstOrDefault ( ) ;
// Avoid the endless loop here for Cycle groups
if ( loopStartElement = = nextTabElement )
break ;
if ( loopStartElement = = null )
loopStartElement = nextTabElement ;
var firstTabElementInside = GetNextTab ( null , nextTabElement , true ) ;
if ( firstTabElementInside ! = null )
return firstTabElementInside ;
// If we want to continue searching inside the Once groups, we should change the navigation mode
if ( currentTabbingType = = KeyboardNavigationMode . Once )
currentTabbingType = KeyboardNavigationMode . Contained ;
}
// If there is no next element in the group (nextTabElement == null)
// Search up in the tree if allowed
// consider: Use original tabbingType instead of currentTabbingType
if ( ! goDownOnly & & currentTabbingType ! = KeyboardNavigationMode . Contained & & GetParent ( container ) ! = null )
{
return GetNextTab ( container , GetGroupParent ( container ) , false ) ;
}
return null ;
}
/// <summary>
/// Gets the first or last focusable descendant of the specified element.
/// </summary>
/// <param name="container">The element.</param>
/// <param name="direction">The direction to search.</param>
/// <returns>The element or null if not found.##</returns>
private static IInputElement GetFocusableDescendant ( IInputElement container , NavigationDirection direction )
public static IInputElement ? GetNextTabOutside ( ICustomKeyboardNavigation e )
{
return direction = = NavigationDirection . Next ?
GetFocusableDescendants ( container , direction ) . FirstOrDefault ( ) :
GetFocusableDescendants ( container , direction ) . LastOrDefault ( ) ;
if ( e is IInputElement container )
{
var last = GetLastInTree ( container ) ;
if ( last is object )
return GetNextTab ( last , false ) ;
}
return null ;
}
/// <summary>
/// Gets the focusable descendants of the specified element.
/// </summary>
/// <param name="element">The element.</param>
/// <param name="direction">The tab direction. Must be Next or Previous.</param>
/// <returns>The element's focusable descendants.</returns>
private static IEnumerable < IInputElement > GetFocusableDescendants ( IInputElement element ,
NavigationDirection direction )
public static IInputElement ? GetPrevTab ( IInputElement ? e , IInputElement ? container , bool goDownOnly )
{
var mode = KeyboardNavigation . GetTabNavigation ( ( InputElement ) element ) ;
if ( e is null & & container is null )
throw new InvalidOperationException ( "Either 'e' or 'container' must be non-null." ) ;
if ( mode = = KeyboardNavigationMode . None )
{
yield break ;
}
if ( container is null )
container = GetGroupParent ( e ! ) ;
var children = element . GetVisualChildren ( ) . OfType < IInputElement > ( ) ;
KeyboardNavigationMode tabbingType = GetKeyNavigationMode ( container ) ;
if ( mod e = = KeyboardNavigationMode . Once )
if ( e = = null )
{
var active = KeyboardNavigation . GetTabOnceActiveElement ( ( InputElement ) element ) ;
if ( active ! = null )
// Using ActiveElement if set
var activeElement = GetActiveElement ( container ) ;
if ( activeElement ! = null )
return GetPrevTab ( null , activeElement , true ) ;
else
{
yield return active ;
yield break ;
// If we Shift+Tab on a container with KeyboardNavigationMode=Once, and ActiveElement is null
// then we want to go to the first item (not last) within the container
if ( tabbingType = = KeyboardNavigationMode . Once )
{
var firstTabElement = GetNextTabInGroup ( null , container , tabbingType ) ;
if ( firstTabElement = = null )
{
if ( IsTabStop ( container ) )
return container ;
if ( goDownOnly )
return null ;
return GetPrevTab ( container , null , false ) ;
}
else
{
return GetPrevTab ( null , firstTabElement , true ) ;
}
}
}
else
}
else
{
if ( tabbingType = = KeyboardNavigationMode . Once | | tabbingType = = KeyboardNavigationMode . None )
{
children = children . Take ( 1 ) ;
if ( goDownOnly | | container = = e )
return null ;
// FocusedElement should not be e otherwise we will delegate focus to the same element
if ( IsTabStop ( container ) )
return container ;
return GetPrevTab ( container , null , false ) ;
}
}
foreach ( var child in children )
// All groups (except Once) - continue
IInputElement ? loopStartElement = null ;
IInputElement ? nextTabElement = e ;
// Look for element with the same TabIndex before the current element
while ( ( nextTabElement = GetPrevTabInGroup ( nextTabElement , container , tabbingType ) ) ! = null )
{
var customNext = GetCustomNext ( child , direction ) ;
if ( nextTabElement = = container & & tabbingType = = KeyboardNavigationMode . Local )
break ;
// At this point nextTabElement is TabStop or TabGroup
// In case it is a TabStop only return the element
if ( IsTabStop ( nextTabElement ) & & ! IsGroup ( nextTabElement ) )
return nextTabElement ;
// Avoid the endless loop here
if ( loopStartElement = = nextTabElement )
break ;
if ( loopStartElement = = null )
loopStartElement = nextTabElement ;
// At this point nextTabElement is TabGroup
var lastTabElementInside = GetPrevTab ( null , nextTabElement , true ) ;
if ( lastTabElementInside ! = null )
return lastTabElementInside ;
}
if ( customNext . handled )
{
yield return customNext . next ! ;
}
else
if ( tabbingType = = KeyboardNavigationMode . Contained )
return null ;
if ( e ! = container & & IsTabStop ( container ) )
return container ;
// If end of the subtree is reached or there no other elements above
if ( ! goDownOnly & & GetParent ( container ) ! = null )
{
return GetPrevTab ( container , null , false ) ;
}
return null ;
}
public static IInputElement ? GetPrevTabOutside ( ICustomKeyboardNavigation e )
{
if ( e is IInputElement container )
{
var first = GetFirstChild ( container ) ;
if ( first is object )
return GetPrevTab ( first , null , false ) ;
}
return null ;
}
private static IInputElement ? FocusedElement ( IInputElement e )
{
var iie = e ;
// Focus delegation is enabled only if keyboard focus is outside the container
if ( iie ! = null & & ! iie . IsKeyboardFocusWithin )
{
var focusedElement = ( FocusManager . Instance as FocusManager ) ? . GetFocusedElement ( e ) ;
if ( focusedElement ! = null )
{
if ( child . CanFocus ( ) & & KeyboardNavigation . GetIsTabStop ( ( InputElement ) child ) )
if ( ! IsFocusScope ( e ) )
{
yield return child ;
// Verify if focusedElement is a visual descendant of e
if ( focusedElement is IVisual visualFocusedElement & &
visualFocusedElement ! = e & &
e . IsVisualAncestorOf ( visualFocusedElement ) )
{
return focusedElement ;
}
}
}
}
return null ;
}
private static IInputElement ? GetFirstChild ( IInputElement e )
{
// If the element has a FocusedElement it should be its first child
if ( FocusedElement ( e ) is IInputElement focusedElement )
return focusedElement ;
if ( child . CanFocusDescendants ( ) )
// Return the first visible element.
var uiElement = e as InputElement ;
if ( uiElement is null | | uiElement . IsVisible )
{
if ( e is IVisual elementAsVisual )
{
var children = elementAsVisual . VisualChildren ;
var count = children . Count ;
for ( int i = 0 ; i < count ; i + + )
{
foreach ( var descendant in GetFocusableDescendants ( child , direction ) )
if ( children [ i ] is InputElement ie )
{
if ( KeyboardNavigation . GetIsTabStop ( ( InputElement ) descendant ) )
if ( ie . IsVisible )
return ie ;
else
{
yield return descendant ;
var firstChild = GetFirstChild ( ie ) ;
if ( firstChild ! = null )
return firstChild ;
}
}
}
}
}
return null ;
}
/// <summary>
/// Gets the next item that should be focused in the specified container.
/// </summary>
/// <param name="element">The starting element/</param>
/// <param name="container">The container.</param>
/// <param name="direction">The direction.</param>
/// <param name="outsideElement">
/// If true will not descend into <paramref name="element"/> to find next control.
/// </param>
/// <returns>The next element, or null if the element is the last.</returns>
private static IInputElement ? GetNextInContainer (
IInputElement element ,
IInputElement container ,
NavigationDirection direction ,
bool outsideElement )
private static IInputElement ? GetLastChild ( IInputElement e )
{
IInputElement ? e = element ;
// If the element has a FocusedElement it should be its last child
if ( FocusedElement ( e ) is IInputElement focusedElement )
return focusedElement ;
if ( direction = = NavigationDirection . Next & & ! outsideElement )
{
var descendant = GetFocusableDescendants ( element , direction ) . FirstOrDefault ( ) ;
// Return the last visible element.
var uiElement = e as InputElement ;
if ( descendant ! = null )
{
return descendant ;
}
}
if ( container ! = null )
if ( uiElement = = null | | uiElement . IsVisible )
{
var navigable = container as INavigableContainer ;
var elementAsVisual = e as IVisual ;
// TODO: Do a spatial search here if the container doesn't implement
// INavigableContainer.
if ( navigable ! = null )
if ( elementAsVisual ! = null )
{
while ( e ! = null )
{
e = navigable . GetControl ( direction , e , false ) ;
var children = elementAsVisual . VisualChildren ;
var count = children . Count ;
if ( e ! = null & &
e . CanFocus ( ) & &
KeyboardNavigation . GetIsTabStop ( ( InputElement ) e ) )
for ( int i = count - 1 ; i > = 0 ; i - - )
{
if ( children [ i ] is InputElement ie )
{
break ;
if ( ie . IsVisible )
return ie ;
else
{
var lastChild = GetLastChild ( ie ) ;
if ( lastChild ! = null )
return lastChild ;
}
}
}
}
else
}
return null ;
}
private static IInputElement ? GetFirstTabInGroup ( IInputElement container )
{
IInputElement ? firstTabElement = null ;
int minIndexFirstTab = int . MinValue ;
var currElement = container ;
while ( ( currElement = GetNextInTree ( currElement , container ) ) ! = null )
{
if ( IsTabStopOrGroup ( currElement ) )
{
// TODO: Do a spatial search here if the container doesn't implement
// INavigableContainer.
e = null ;
int currPriority = KeyboardNavigation . GetTabIndex ( currElement ) ;
if ( currPriority < minIndexFirstTab | | firstTabElement = = null )
{
minIndexFirstTab = currPriority ;
firstTabElement = currElement ;
}
}
}
return firstTabElement ;
}
private static IInputElement ? GetLastInTree ( IInputElement container )
{
IInputElement ? result ;
IInputElement ? c = container ;
do
{
result = c ;
c = GetLastChild ( c ) ;
} while ( c ! = null & & ! IsGroup ( c ) ) ;
if ( c ! = null )
return c ;
return result ;
}
if ( e ! = null & & direction = = NavigationDirection . Previous )
private static IInputElement ? GetLastTabInGroup ( IInputElement container )
{
IInputElement ? lastTabElement = null ;
int maxIndexFirstTab = int . MaxValue ;
var currElement = GetLastInTree ( container ) ;
while ( currElement ! = null & & currElement ! = container )
{
if ( IsTabStopOrGroup ( currElement ) )
{
var descendant = GetFocusableDescendants ( e , direction ) . LastOrDefault ( ) ;
int currPriority = KeyboardNavigation . GetTabIndex ( currElement ) ;
if ( descendant ! = null )
if ( currPriority > maxIndexFirstTab | | lastTabElement = = null )
{
return descendant ;
maxIndexFirstTab = currPriority ;
lastTabElement = currElement ;
}
}
currElement = GetPreviousInTree ( currElement , container ) ;
}
return lastTabElement ;
}
private static IInputElement ? GetNextInTree ( IInputElement e , IInputElement container )
{
IInputElement ? result = null ;
if ( e = = container | | ! IsGroup ( e ) )
result = GetFirstChild ( e ) ;
if ( result ! = null | | e = = container )
return result ;
IInputElement ? parent = e ;
do
{
var sibling = GetNextSibling ( parent ) ;
if ( sibling ! = null )
return sibling ;
return e ;
parent = GetParent ( parent ) ;
} while ( parent ! = null & & parent ! = container ) ;
return null ;
}
private static IInputElement ? GetNextSibling ( IInputElement e )
{
if ( GetParent ( e ) is IVisual parentAsVisual & & e is IVisual elementAsVisual )
{
var children = parentAsVisual . VisualChildren ;
var count = children . Count ;
var i = 0 ;
//go till itself
for ( ; i < count ; i + + )
{
var vchild = children [ i ] ;
if ( vchild = = elementAsVisual )
break ;
}
i + + ;
//search ahead
for ( ; i < count ; i + + )
{
var visual = children [ i ] ;
if ( visual is IInputElement ie )
return ie ;
}
}
return null ;
}
/// <summary>
/// Gets the first item that should be focused in the next container.
/// </summary>
/// <param name="element">The element being navigated away from.</param>
/// <param name="container">The container.</param>
/// <param name="direction">The direction of the search.</param>
/// <returns>The first element, or null if there are no more elements.</returns>
private static IInputElement ? GetFirstInNextContainer (
IInputElement element ,
IInputElement container ,
NavigationDirection direction )
private static IInputElement ? GetNextTabInGroup ( IInputElement ? e , IInputElement container , KeyboardNavigationMode tabbingType )
{
var parent = container . GetVisualParent < IInputElement > ( ) ;
IInputElement ? next = null ;
// None groups: Tab navigation is not supported
if ( tabbingType = = KeyboardNavigationMode . None )
return null ;
if ( parent ! = null )
// e == null or e == container -> return the first TabStopOrGroup
if ( e = = null | | e = = container )
{
if ( direction = = NavigationDirection . Previous & &
parent . CanFocus ( ) & &
KeyboardNavigation . GetIsTabStop ( ( InputElement ) parent ) )
return GetFirstTabInGroup ( container ) ;
}
if ( tabbingType = = KeyboardNavigationMode . Once )
return null ;
var nextTabElement = GetNextTabWithSameIndex ( e , container ) ;
if ( nextTabElement ! = null )
return nextTabElement ;
return GetNextTabWithNextIndex ( e , container , tabbingType ) ;
}
private static IInputElement ? GetNextTabWithSameIndex ( IInputElement e , IInputElement container )
{
var elementTabPriority = KeyboardNavigation . GetTabIndex ( e ) ;
var currElement = e ;
while ( ( currElement = GetNextInTree ( currElement , container ) ) ! = null )
{
if ( IsTabStopOrGroup ( currElement ) & & KeyboardNavigation . GetTabIndex ( currElement ) = = elementTabPriority )
{
return parent ;
return currElem ent;
}
}
var allSiblings = parent . GetVisualChildren ( )
. OfType < IInputElement > ( )
. Where ( FocusExtensions . CanFocusDescendants ) ;
var siblings = direction = = NavigationDirection . Next ?
allSiblings . SkipWhile ( x = > x ! = container ) . Skip ( 1 ) :
allSiblings . TakeWhile ( x = > x ! = container ) . Reverse ( ) ;
return null ;
}
foreach ( var sibling in siblings )
private static IInputElement ? GetNextTabWithNextIndex ( IInputElement e , IInputElement container , KeyboardNavigationMode tabbingType )
{
// Find the next min index in the tree
// min (index>currentTabIndex)
IInputElement ? nextTabElement = null ;
IInputElement ? firstTabElement = null ;
int minIndexFirstTab = int . MinValue ;
int minIndex = int . MinValue ;
int elementTabPriority = KeyboardNavigation . GetTabIndex ( e ) ;
IInputElement ? currElement = container ;
while ( ( currElement = GetNextInTree ( currElement , container ) ) ! = null )
{
if ( IsTabStopOrGroup ( currElement ) )
{
var customNext = GetCustomNext ( sibling , direction ) ;
if ( customNext . handled )
int currPriority = KeyboardNavigation . GetTabIndex ( currElement ) ;
if ( currPriority > elementTabPriority )
{
return customNext . next ;
if ( currPriority < minIndex | | nextTabElement = = null )
{
minIndex = currPriority ;
nextTabElement = currElement ;
}
}
if ( sibling . CanFocus ( ) & & KeyboardNavigation . GetIsTabStop ( ( InputElement ) sibling ) )
if ( currPriority < minIndexFirstTab | | firstTabElement = = null )
{
return sibling ;
minIndexFirstTab = currPriority ;
firstTabElement = currElement ;
}
}
}
next = direction = = NavigationDirection . Next ?
GetFocusableDescendants ( sibling , direction ) . FirstOrDefault ( ) :
GetFocusableDescendants ( sibling , direction ) . LastOrDefault ( ) ;
// Cycle groups: if not found - return first element
if ( tabbingType = = KeyboardNavigationMode . Cycle & & nextTabElement = = null )
nextTabElement = firstTabElement ;
return nextTabElement ;
}
if ( next ! = null )
private static IInputElement ? GetPrevTabInGroup ( IInputElement ? e , IInputElement container , KeyboardNavigationMode tabbingType )
{
// None groups: Tab navigation is not supported
if ( tabbingType = = KeyboardNavigationMode . None )
return null ;
// Search the last index inside the group
if ( e = = null )
{
return GetLastTabInGroup ( container ) ;
}
if ( tabbingType = = KeyboardNavigationMode . Once )
return null ;
if ( e = = container )
return null ;
var nextTabElement = GetPrevTabWithSameIndex ( e , container ) ;
if ( nextTabElement ! = null )
return nextTabElement ;
return GetPrevTabWithPrevIndex ( e , container , tabbingType ) ;
}
private static IInputElement ? GetPrevTabWithSameIndex ( IInputElement e , IInputElement container )
{
int elementTabPriority = KeyboardNavigation . GetTabIndex ( e ) ;
var currElement = GetPreviousInTree ( e , container ) ;
while ( currElement ! = null )
{
if ( IsTabStopOrGroup ( currElement ) & & KeyboardNavigation . GetTabIndex ( currElement ) = = elementTabPriority & & currElement ! = container )
{
return currElement ;
}
currElement = GetPreviousInTree ( currElement , container ) ;
}
return null ;
}
private static IInputElement ? GetPrevTabWithPrevIndex ( IInputElement e , IInputElement container , KeyboardNavigationMode tabbingType )
{
// Find the next max index in the tree
// max (index<currentTabIndex)
IInputElement ? lastTabElement = null ;
IInputElement ? nextTabElement = null ;
int elementTabPriority = KeyboardNavigation . GetTabIndex ( e ) ;
int maxIndexFirstTab = Int32 . MaxValue ;
int maxIndex = Int32 . MaxValue ;
var currElement = GetLastInTree ( container ) ;
while ( currElement ! = null )
{
if ( IsTabStopOrGroup ( currElement ) & & currElement ! = container )
{
int currPriority = KeyboardNavigation . GetTabIndex ( currElement ) ;
if ( currPriority < elementTabPriority )
{
if ( currPriority > maxIndex | | nextTabElement = = null )
{
maxIndex = currPriority ;
nextTabElement = currElement ;
}
}
if ( currPriority > maxIndexFirstTab | | lastTabElement = = null )
{
return next ;
maxIndexFirstTab = currPriority ;
lastTabElement = currElement ;
}
}
next = GetFirstInNextContainer ( element , parent , direction ) ;
currElement = GetPreviousInTree ( currElement , container ) ;
}
// Cycle groups: if not found - return first element
if ( tabbingType = = KeyboardNavigationMode . Cycle & & nextTabElement = = null )
nextTabElement = lastTabElement ;
return nextTabElement ;
}
private static IInputElement ? GetPreviousInTree ( IInputElement e , IInputElement container )
{
if ( e = = container )
return null ;
var result = GetPreviousSibling ( e ) ;
if ( result ! = null )
{
if ( IsGroup ( result ) )
return result ;
else
return GetLastInTree ( result ) ;
}
else
return GetParent ( e ) ;
}
private static IInputElement ? GetPreviousSibling ( IInputElement e )
{
if ( GetParent ( e ) is IVisual parentAsVisual & & e is IVisual elementAsVisual )
{
next = direction = = NavigationDirection . Next ?
GetFocusableDescendants ( container , direction ) . FirstOrDefault ( ) :
GetFocusableDescendants ( container , direction ) . LastOrDefault ( ) ;
var children = parentAsVisual . VisualChildren ;
var count = children . Count ;
IInputElement ? prev = null ;
for ( int i = 0 ; i < count ; i + + )
{
var vchild = children [ i ] ;
if ( vchild = = elementAsVisual )
break ;
if ( vchild . IsVisible = = true & & vchild is IInputElement ie )
prev = ie ;
}
return prev ;
}
return null ;
}
return next ;
private static IInputElement ? GetActiveElement ( IInputElement e )
{
return ( ( IAvaloniaObject ) e ) . GetValue ( KeyboardNavigation . TabOnceActiveElementProperty ) ;
}
private static ( bool handled , IInputElement ? next ) GetCustomNext ( IInputElement element ,
NavigationDirection direction )
private static IInputElement GetGroupParent ( IInputElement e ) = > GetGroupParent ( e , false ) ;
private static IInputElement GetGroupParent ( IInputElement element , bool includeCurrent )
{
if ( element is ICustomKeyboardNavigation custom )
var result = element ; // Keep the last non null element
var e = element ;
// If we don't want to include the current element,
// start at the parent of the element. If the element
// is the root, then just return it as the group parent.
if ( ! includeCurrent )
{
result = e ;
e = GetParent ( e ) ;
if ( e = = null )
return result ;
}
while ( e ! = null )
{
return custom . GetNext ( element , direction ) ;
if ( IsGroup ( e ) )
return e ;
result = e ;
e = GetParent ( e ) ;
}
return ( false , null ) ;
return result ;
}
private static IInputElement ? GetParent ( IInputElement e )
{
// For Visual - go up the visual parent chain until we find Visual.
if ( e is IVisual v )
return v . FindAncestorOfType < IInputElement > ( ) ;
// This will need to be implemented when we have non-visual input elements.
throw new NotSupportedException ( ) ;
}
private static KeyboardNavigationMode GetKeyNavigationMode ( IInputElement e )
{
return ( ( IAvaloniaObject ) e ) . GetValue ( KeyboardNavigation . TabNavigationProperty ) ;
}
private static bool IsFocusScope ( IInputElement e ) = > FocusManager . GetIsFocusScope ( e ) | | GetParent ( e ) = = null ;
private static bool IsGroup ( IInputElement e ) = > GetKeyNavigationMode ( e ) ! = KeyboardNavigationMode . Continue ;
private static bool IsTabStop ( IInputElement e )
{
if ( e is InputElement ie )
return ie . Focusable & & KeyboardNavigation . GetIsTabStop ( ie ) & & ie . IsVisible & & ie . IsEnabled ;
return false ;
}
private static bool IsTabStopOrGroup ( IInputElement e ) = > IsTabStop ( e ) | | IsGroup ( e ) ;
}
}