Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[A11y] Add SemanticProperties.Description to the Toolbar Icon #27119

Merged
merged 7 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
#nullable disable
#if __MOBILE__
using System;
using ObjCRuntime;
using UIKit;
using NativeView = UIKit.UIView;

namespace Microsoft.Maui.Controls.Compatibility.Platform.iOS
#else
using NativeView = AppKit.NSView;

namespace Microsoft.Maui.Controls.Compatibility.Platform.MacOS
#endif
{
public static class AccessibilityExtensions
{
Expand All @@ -25,32 +20,42 @@ public static void SetAccessibilityProperties(this NativeView nativeViewElement,
SetAccessibilityElementsHidden(nativeViewElement, element);
}

// TODO OBSOLETE FOR NET10
public static string SetAccessibilityHint(this NativeView Control, Element Element, string _defaultAccessibilityHint = null)
{
if (Element == null || Control == null)
return _defaultAccessibilityHint;
#if __MOBILE__

var semantics = SemanticProperties.UpdateSemantics(Element, null);
if (semantics is not null)
{
Microsoft.Maui.Platform.SemanticExtensions.UpdateSemantics(Control, semantics);
return String.Empty;
}

if (_defaultAccessibilityHint == null)
_defaultAccessibilityHint = Control.AccessibilityHint;

#pragma warning disable CS0618 // Type or member is obsolete
Control.AccessibilityHint = (string)Element.GetValue(AutomationProperties.HelpTextProperty) ?? _defaultAccessibilityHint;
#pragma warning restore CS0618 // Type or member is obsolete
#else
if (_defaultAccessibilityHint == null)
_defaultAccessibilityHint = Control.AccessibilityTitle;

Control.AccessibilityTitle = (string)Element.GetValue(AutomationProperties.HelpTextProperty) ?? _defaultAccessibilityHint;
#endif

return _defaultAccessibilityHint;
}

// TODO OBSOLETE FOR NET10
public static string SetAccessibilityLabel(this NativeView Control, Element Element, string _defaultAccessibilityLabel = null)
{
if (Element == null || Control == null)
return _defaultAccessibilityLabel;

var semantics = SemanticProperties.UpdateSemantics(Element, null);
if (semantics is not null)
{
Microsoft.Maui.Platform.SemanticExtensions.UpdateSemantics(Control, semantics);
return String.Empty;
}

if (_defaultAccessibilityLabel == null)
_defaultAccessibilityLabel = Control.AccessibilityLabel;

Expand All @@ -61,11 +66,18 @@ public static string SetAccessibilityLabel(this NativeView Control, Element Elem
return _defaultAccessibilityLabel;
}

#if __MOBILE__
// TODO OBSOLETE FOR NET10
public static string SetAccessibilityHint(this UIBarItem Control, Element Element, string _defaultAccessibilityHint = null)
{
if (Element == null || Control == null)
return _defaultAccessibilityHint;

var semantics = SemanticProperties.UpdateSemantics(Element, null);
if (semantics is not null)
{
Microsoft.Maui.Platform.SemanticExtensions.UpdateSemantics(Control, semantics);
return String.Empty;
}

if (_defaultAccessibilityHint == null)
_defaultAccessibilityHint = Control.AccessibilityHint;
Expand All @@ -78,10 +90,29 @@ public static string SetAccessibilityHint(this UIBarItem Control, Element Elemen

}

// TODO OBSOLETE FOR NET10
public static string SetAccessibilityLabel(this UIBarItem Control, Element Element, string _defaultAccessibilityLabel = null)
{
if (Element == null || Control == null)
return _defaultAccessibilityLabel;

var semantics = SemanticProperties.UpdateSemantics(Element, null);
if (semantics is not null)
{
semantics.Description = semantics.Description ?? (Element as ToolbarItem)?.Text;
Microsoft.Maui.Platform.SemanticExtensions.UpdateSemantics(Control, semantics);
return String.Empty;
}
else if (!string.IsNullOrEmpty((Element as ToolbarItem)?.Text))
{
// If there is an icon and text on the UIBarItem, the platforms will behave as follows:
// On Windows both will be displayed and the text will be read by screenreaders.
// On Android only the icon will be displayed but the text will be read by screenreaders.
// On MacCatalyst and iOS only the icon will be displayed but the text will NOT be read by screenreaders.
// As a result, we will add the text value to the Accessibility Label to ensure that the text is read by screenreaders on all the platforms.
Microsoft.Maui.Platform.SemanticExtensions.UpdateSemantics(Control, new Semantics() { Description = (Element as ToolbarItem)?.Text });
return String.Empty;
}

if (_defaultAccessibilityLabel == null)
_defaultAccessibilityLabel = Control.AccessibilityLabel;
Expand All @@ -92,7 +123,6 @@ public static string SetAccessibilityLabel(this UIBarItem Control, Element Eleme

return _defaultAccessibilityLabel;
}
#endif

public static bool? SetIsAccessibilityElement(this NativeView Control, Element Element, bool? _defaultIsAccessibilityElement = null)
{
Expand All @@ -103,7 +133,6 @@ public static string SetAccessibilityLabel(this UIBarItem Control, Element Eleme
if (!Element.IsSet(AutomationProperties.IsInAccessibleTreeProperty))
return null;

#if __MOBILE__
if (!_defaultIsAccessibilityElement.HasValue)
{
// iOS sets the default value for IsAccessibilityElement late in the layout cycle
Expand All @@ -119,12 +148,6 @@ public static string SetAccessibilityLabel(this UIBarItem Control, Element Eleme
}

Control.IsAccessibilityElement = (bool)((bool?)Element.GetValue(AutomationProperties.IsInAccessibleTreeProperty) ?? _defaultIsAccessibilityElement);
#else
if (!_defaultIsAccessibilityElement.HasValue)
_defaultIsAccessibilityElement = Control.AccessibilityElement;

Control.AccessibilityElement = (bool)((bool?)Element.GetValue(AutomationProperties.IsInAccessibleTreeProperty) ?? _defaultIsAccessibilityElement);
#endif

return _defaultIsAccessibilityElement;
}
Expand All @@ -137,19 +160,12 @@ public static string SetAccessibilityLabel(this UIBarItem Control, Element Eleme
if (!Element.IsSet(AutomationProperties.ExcludedWithChildrenProperty))
return null;

#if __MOBILE__
if (!_defaultAccessibilityElementsHidden.HasValue)
{
_defaultAccessibilityElementsHidden = Control.AccessibilityElementsHidden || Control is UIControl;
}

Control.AccessibilityElementsHidden = (bool)((bool?)Element.GetValue(AutomationProperties.ExcludedWithChildrenProperty) ?? _defaultAccessibilityElementsHidden);
#else
if (!_defaultAccessibilityElementsHidden.HasValue)
_defaultAccessibilityElementsHidden = Control.AccessibilityElementsHidden;

Control.AccessibilityElementsHidden = (bool)((bool?)Element.GetValue(AutomationProperties.ExcludedWithChildrenProperty) ?? _defaultAccessibilityElementsHidden);
#endif

return _defaultAccessibilityElementsHidden;
}
Expand Down
17 changes: 17 additions & 0 deletions src/Controls/src/Core/SemanticProperties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,5 +89,22 @@ void CopyProperty(BindableProperty bp, BindableObject source, BindableObject des
dest.SetValue(bp, source.GetValue(bp));
}
}

#nullable enable
internal static Semantics? UpdateSemantics(BindableObject bindable, Semantics? semantics)
{
if (!bindable.IsSet(HintProperty) &&
!bindable.IsSet(DescriptionProperty) &&
!bindable.IsSet(HeadingLevelProperty))
{
return null;
}

semantics ??= new Semantics();
semantics.Description = GetDescription(bindable);
semantics.HeadingLevel = GetHeadingLevel(bindable);
semantics.Hint = GetHint(bindable);
return semantics;
}
}
}
18 changes: 2 additions & 16 deletions src/Controls/src/Core/VisualElement/VisualElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1979,22 +1979,8 @@ bool IView.IsFocused
/// <inheritdoc/>
Semantics? IView.Semantics => UpdateSemantics();

private protected virtual Semantics? UpdateSemantics()
{
if (!this.IsSet(SemanticProperties.HintProperty) &&
!this.IsSet(SemanticProperties.DescriptionProperty) &&
!this.IsSet(SemanticProperties.HeadingLevelProperty))
{
_semantics = null;
return _semantics;
}

_semantics ??= new Semantics();
_semantics.Description = SemanticProperties.GetDescription(this);
_semantics.HeadingLevel = SemanticProperties.GetHeadingLevel(this);
_semantics.Hint = SemanticProperties.GetHint(this);
return _semantics;
}
private protected virtual Semantics? UpdateSemantics() =>
_semantics = SemanticProperties.UpdateSemantics(this, _semantics);

static double EnsurePositive(double value)
{
Expand Down
34 changes: 31 additions & 3 deletions src/Core/src/Platform/iOS/SemanticExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,38 @@ namespace Microsoft.Maui.Platform
{
public static partial class SemanticExtensions
{
public static void UpdateSemantics(this UIView platformView, IView view)
internal static void UpdateSemantics(this UIBarItem platformView, Semantics? semantics)
{
var semantics = view.Semantics;
if (semantics == null)
return;

platformView.AccessibilityLabel = semantics.Description;
platformView.AccessibilityHint = semantics.Hint;

var accessibilityTraits = platformView.AccessibilityTraits;
var hasHeader = (accessibilityTraits & UIAccessibilityTrait.Header) == UIAccessibilityTrait.Header;

if (semantics.IsHeading)
{
if (!hasHeader)
{
platformView.AccessibilityTraits = accessibilityTraits | UIAccessibilityTrait.Header;
}
}
else
{
if (hasHeader)
{
platformView.AccessibilityTraits = accessibilityTraits & ~UIAccessibilityTrait.Header;
}
}
}

public static void UpdateSemantics(this UIView platformView, IView view) =>
UpdateSemantics(platformView, view?.Semantics);

internal static void UpdateSemantics(this UIView platformView, Semantics? semantics)
{
if (semantics == null)
return;

Expand All @@ -24,7 +52,7 @@ public static void UpdateSemantics(this UIView platformView, IView view)
platformView.AccessibilityLabel = semantics.Description;
platformView.AccessibilityHint = semantics.Hint;

if ((!string.IsNullOrWhiteSpace(semantics.Hint) || !string.IsNullOrWhiteSpace(semantics.Description)))
if (!string.IsNullOrWhiteSpace(semantics.Hint) || !string.IsNullOrWhiteSpace(semantics.Description))
{
// Most UIControl elements automatically have IsAccessibilityElement set to true
if (platformView is not UIControl)
Expand Down
2 changes: 2 additions & 0 deletions src/Core/src/SemanticExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

namespace Microsoft.Maui
{
// TODO MARK THIS OBSOLETE FOR NET10
// There's already one of these inside the Microsoft.Maui.Platform namespace
public static partial class SemanticExtensions
{
public static void SetSemanticFocus(this IView element)
Expand Down
Loading