Skip to content

Commit ce690d5

Browse files
committed
resolve accessibility issue
1 parent 5720452 commit ce690d5

File tree

3 files changed

+245
-44
lines changed

3 files changed

+245
-44
lines changed

maui/src/TextInputLayout/SfTextInputLayout.Methods.cs

Lines changed: 195 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -855,6 +855,19 @@ void UnwireEvents()
855855
}
856856

857857
UnWireLabelStyleEvents();
858+
859+
#if ANDROID
860+
// Unwire assistive label handler changed events
861+
if (_helperLabel != null)
862+
{
863+
_helperLabel.HandlerChanged -= OnAssistiveLabelHandlerChanged;
864+
}
865+
if (_errorLabel != null)
866+
{
867+
_errorLabel.HandlerChanged -= OnAssistiveLabelHandlerChanged;
868+
}
869+
#endif
870+
DisposeAssistiveLabels();
858871
}
859872

860873
/// <summary>
@@ -2077,46 +2090,9 @@ void DrawHintText(ICanvas canvas, RectF dirtyRect)
20772090

20782091
void DrawAssistiveText(ICanvas canvas, RectF dirtyRect)
20792092
{
2080-
if (HasError)
2081-
{
2082-
DrawErrorText(canvas, dirtyRect);
2083-
}
2084-
else
2085-
{
2086-
DrawHelperText(canvas, dirtyRect);
2087-
}
2088-
20892093
DrawCounterText(canvas, dirtyRect);
20902094
}
20912095

2092-
void DrawHelperText(ICanvas canvas, RectF dirtyRect)
2093-
{
2094-
if (ShowHelperText && !string.IsNullOrEmpty(HelperText) && HelperLabelStyle != null)
2095-
{
2096-
canvas.CanvasSaveState();
2097-
UpdateHelperTextPosition();
2098-
UpdateHelperTextColor();
2099-
2100-
canvas.DrawText(HelperText, _helperTextRect, IsRTL ? HorizontalAlignment.Right : HorizontalAlignment.Left, VerticalAlignment.Top, _internalHelperLabelStyle);
2101-
2102-
canvas.CanvasRestoreState();
2103-
}
2104-
}
2105-
2106-
void DrawErrorText(ICanvas canvas, RectF dirtyRect)
2107-
{
2108-
if (!string.IsNullOrEmpty(ErrorText) && ErrorLabelStyle != null)
2109-
{
2110-
canvas.CanvasSaveState();
2111-
UpdateErrorTextPosition();
2112-
UpdateErrorTextColor();
2113-
2114-
canvas.DrawText(ErrorText, _errorTextRect, IsRTL ? HorizontalAlignment.Right : HorizontalAlignment.Left, VerticalAlignment.Top, _internalErrorLabelStyle);
2115-
2116-
canvas.CanvasRestoreState();
2117-
}
2118-
}
2119-
21202096
void DrawCounterText(ICanvas canvas, RectF dirtyRect)
21212097
{
21222098
if (ShowCharCount && !string.IsNullOrEmpty(_counterText) && CounterLabelStyle != null)
@@ -2239,6 +2215,188 @@ void OnTranslateAnimationEnded(double value, bool isCompleted)
22392215
IsHintDownToUp = !IsHintDownToUp;
22402216
}
22412217

2218+
/// <summary>
2219+
/// Initializes the assistive labels (helper and error text labels).
2220+
/// </summary>
2221+
void InitializeAssistiveLabels()
2222+
{
2223+
if (_helperLabel != null && Children.Contains(_helperLabel))
2224+
Remove(_helperLabel);
2225+
if (_errorLabel != null && Children.Contains(_errorLabel))
2226+
Remove(_errorLabel);
2227+
2228+
if (_helperLabel == null)
2229+
{
2230+
_helperLabel = new Label
2231+
{
2232+
IsVisible = false,
2233+
LineBreakMode = LineBreakMode.WordWrap,
2234+
};
2235+
}
2236+
2237+
if (_errorLabel == null)
2238+
{
2239+
_errorLabel = new Label
2240+
{
2241+
IsVisible = false,
2242+
LineBreakMode = LineBreakMode.WordWrap,
2243+
};
2244+
}
2245+
2246+
ConfigureAccessibilityForAssistiveLabels();
2247+
2248+
Add(_helperLabel);
2249+
Add(_errorLabel);
2250+
}
2251+
2252+
/// <summary>
2253+
/// Configures accessibility properties for assistive labels to ensure proper focus order on Android.
2254+
/// </summary>
2255+
void ConfigureAccessibilityForAssistiveLabels()
2256+
{
2257+
#if ANDROID
2258+
// Configure helper label accessibility
2259+
if (_helperLabel != null)
2260+
{
2261+
UpdateLabelAccessibilityImportance(_helperLabel, false);
2262+
_helperLabel.HandlerChanged += OnAssistiveLabelHandlerChanged;
2263+
}
2264+
2265+
// Configure error label accessibility
2266+
if (_errorLabel != null)
2267+
{
2268+
UpdateLabelAccessibilityImportance(_errorLabel, false);
2269+
_errorLabel.HandlerChanged += OnAssistiveLabelHandlerChanged;
2270+
}
2271+
#endif
2272+
}
2273+
2274+
#if ANDROID
2275+
/// <summary>
2276+
/// Handles the HandlerChanged event for assistive labels to ensure proper accessibility setup.
2277+
/// </summary>
2278+
void OnAssistiveLabelHandlerChanged(object? sender, EventArgs e)
2279+
{
2280+
if (sender is Label label)
2281+
{
2282+
bool isVisible = label.IsVisible && !string.IsNullOrEmpty(label.Text);
2283+
UpdateLabelAccessibilityImportance(label, isVisible);
2284+
}
2285+
}
2286+
2287+
/// <summary>
2288+
/// Updates the accessibility importance of a label based on its visibility and content.
2289+
/// This ensures proper focus order in TalkBack.
2290+
/// </summary>
2291+
/// <param name="label">The label to update</param>
2292+
/// <param name="isVisible">Whether the label should be important for accessibility</param>
2293+
void UpdateLabelAccessibilityImportance(Label label, bool isVisible)
2294+
{
2295+
if (label?.Handler?.PlatformView is Android.Views.View androidView)
2296+
{
2297+
if (isVisible && !string.IsNullOrEmpty(label.Text))
2298+
{
2299+
androidView.ImportantForAccessibility = Android.Views.ImportantForAccessibility.Yes;
2300+
androidView.Focusable = true;
2301+
}
2302+
else
2303+
{
2304+
androidView.ImportantForAccessibility = Android.Views.ImportantForAccessibility.No;
2305+
androidView.Focusable = false;
2306+
}
2307+
}
2308+
}
2309+
#endif
2310+
2311+
/// <summary>
2312+
/// Updates the assistive labels visibility and content.
2313+
/// </summary>
2314+
void UpdateAssistiveLabels()
2315+
{
2316+
// Initialize labels if not already created
2317+
if (_helperLabel == null || _errorLabel == null)
2318+
{
2319+
InitializeAssistiveLabels();
2320+
}
2321+
2322+
if(_helperLabel != null)
2323+
{
2324+
if (ShowHelperText && !string.IsNullOrEmpty(HelperText) && !HasError && ReserveSpaceForAssistiveLabels)
2325+
{
2326+
_helperLabel.Text = HelperText;
2327+
_helperLabel.IsVisible = true;
2328+
UpdateHelperTextPosition();
2329+
UpdateHelperTextColor();
2330+
ApplyLabelStyle(_helperLabel, _internalHelperLabelStyle);
2331+
#if ANDROID
2332+
UpdateLabelAccessibilityImportance(_helperLabel, true);
2333+
#endif
2334+
AbsoluteLayout.SetLayoutBounds(_helperLabel, _helperTextRect);
2335+
}
2336+
else
2337+
{
2338+
_helperLabel.IsVisible = false;
2339+
#if ANDROID
2340+
UpdateLabelAccessibilityImportance(_helperLabel, false);
2341+
#endif
2342+
}
2343+
}
2344+
2345+
if (_errorLabel != null)
2346+
{
2347+
if (HasError && !string.IsNullOrEmpty(ErrorText) && ReserveSpaceForAssistiveLabels)
2348+
{
2349+
_errorLabel.Text = ErrorText;
2350+
_errorLabel.IsVisible = true;
2351+
UpdateErrorTextPosition();
2352+
UpdateErrorTextColor();
2353+
ApplyLabelStyle(_errorLabel, _internalErrorLabelStyle);
2354+
#if ANDROID
2355+
UpdateLabelAccessibilityImportance(_errorLabel, true);
2356+
#endif
2357+
AbsoluteLayout.SetLayoutBounds(_errorLabel, _errorTextRect);
2358+
}
2359+
else
2360+
{
2361+
_errorLabel.IsVisible = false;
2362+
#if ANDROID
2363+
UpdateLabelAccessibilityImportance(_errorLabel, false);
2364+
#endif
2365+
}
2366+
}
2367+
2368+
}
2369+
2370+
/// <summary>
2371+
/// Applies the label style to a label control.
2372+
/// </summary>
2373+
void ApplyLabelStyle(Label label, LabelStyle style)
2374+
{
2375+
if (label == null || style == null)
2376+
return;
2377+
2378+
label.FontSize = style.FontSize;
2379+
label.FontFamily = style.FontFamily;
2380+
label.FontAttributes = style.FontAttributes;
2381+
label.TextColor = style.TextColor;
2382+
label.FontAutoScalingEnabled = style.FontAutoScalingEnabled;
2383+
2384+
}
2385+
2386+
/// <summary>
2387+
/// Disposes and removes the assistive labels (helper and error labels) from the control
2388+
/// </summary>
2389+
void DisposeAssistiveLabels()
2390+
{
2391+
if (_helperLabel != null && Children.Contains(_helperLabel))
2392+
Children.Remove(_helperLabel);
2393+
if (_errorLabel != null && Children.Contains(_errorLabel))
2394+
Children.Remove(_errorLabel);
2395+
2396+
_helperLabel = null;
2397+
_errorLabel = null;
2398+
}
2399+
22422400
/// <summary>
22432401
/// Returns the resource dictionary for the current theme of the parent element.
22442402
/// </summary>

maui/src/TextInputLayout/SfTextInputLayout.Properties.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2394,6 +2394,8 @@ static void OnPropertyChanged(BindableObject bindable, object oldValue, object n
23942394
if (bindable is SfTextInputLayout inputLayout && inputLayout._initialLoaded)
23952395
{
23962396
inputLayout.UpdateViewBounds();
2397+
// Update assistive labels when relevant properties change
2398+
inputLayout.UpdateAssistiveLabels();
23972399
inputLayout.ResetSemantics();
23982400
}
23992401
}

maui/src/TextInputLayout/SfTextInputLayout.cs

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
using Syncfusion.Maui.Toolkit.EntryRenderer;
1515
using Syncfusion.Maui.Toolkit.EntryView;
1616
using Syncfusion.Maui.Toolkit.NumericUpDown;
17+
using System.Runtime.CompilerServices;
1718

1819
namespace Syncfusion.Maui.Toolkit.TextInputLayout
1920
{
@@ -309,7 +310,17 @@ public partial class SfTextInputLayout : SfContentView, ITouchListener, IParentT
309310
/// </summary>
310311
static readonly Color ClearIconStrokeColor = Color.FromArgb("#49454F");
311312

312-
readonly EffectsRenderer _effectsRenderer;
313+
/// <summary>
314+
/// Label control for displaying helper text
315+
/// </summary>
316+
Label? _helperLabel;
317+
318+
/// <summary>
319+
/// Label control for displaying error text
320+
/// </summary>
321+
Label? _errorLabel;
322+
323+
readonly EffectsRenderer _effectsRenderer;
313324

314325
readonly PathBuilder _pathBuilder = new();
315326

@@ -376,8 +387,6 @@ public partial class SfTextInputLayout : SfContentView, ITouchListener, IParentT
376387
UIKit.UITextField? uiEntry;
377388
#endif
378389

379-
private string _initialContentDescription = string.Empty;
380-
381390
#endregion
382391

383392
#region Constructor
@@ -789,6 +798,34 @@ SemanticsNode CreateSemanticsNode(int id, Rect rect, string description) =>
789798

790799
#region Override Methods
791800

801+
#if IOS
802+
/// <summary>
803+
/// Handles property changes and updates child element flow directions appropriately, especially for right-to-left layouts.
804+
/// </summary>
805+
/// <param name="propertyName">The name of the property that changed.</param>
806+
protected override void OnPropertyChanged([CallerMemberName] string propertyName = "")
807+
{
808+
base.OnPropertyChanged(propertyName);
809+
if (Content == null || string.IsNullOrEmpty(propertyName))
810+
return;
811+
812+
if (propertyName == nameof(FlowDirection))
813+
{
814+
var flowDirection = IsRTL ? FlowDirection.RightToLeft : FlowDirection.LeftToRight;
815+
Content.FlowDirection = flowDirection;
816+
if (_helperLabel != null)
817+
{
818+
_helperLabel.FlowDirection = flowDirection;;
819+
}
820+
if (_errorLabel != null)
821+
{
822+
_errorLabel.FlowDirection = flowDirection;
823+
}
824+
}
825+
}
826+
827+
#endif
828+
792829
/// <summary>
793830
/// Returns the semantics node list.
794831
/// </summary>
@@ -854,7 +891,6 @@ protected override void OnContentChanged(object oldValue, object newValue)
854891
if (newValue is InputView entryEditorContent)
855892
{
856893
entryEditorContent.Opacity = IsHintFloated ? 1 : minOpacity;
857-
_initialContentDescription = SemanticProperties.GetDescription(entryEditorContent);
858894
}
859895
else if (newValue is SfView numericEntryContent && numericEntryContent.Children.Count > 0)
860896
{
@@ -877,6 +913,7 @@ protected override void OnContentChanged(object oldValue, object newValue)
877913
{
878914
OnEnabledPropertyChanged(IsEnabled);
879915
}
916+
InitializeAssistiveLabels();
880917
SetCustomDescription(newValue);
881918
ResetSemantics();
882919
}
@@ -900,7 +937,7 @@ private void SetCustomDescription(object content)
900937

901938
var layoutDescription = GetLayoutDescription();
902939

903-
var contentDescription = layoutDescription + (IsHintFloated ? customDescription + _initialContentDescription : string.Empty);
940+
var contentDescription = layoutDescription + (IsHintFloated ? customDescription : string.Empty);
904941

905942
SemanticProperties.SetDescription(this.Content, contentDescription);
906943
#elif WINDOWS
@@ -1250,7 +1287,8 @@ protected override void OnDraw(ICanvas canvas, RectF dirtyRect)
12501287
DrawClearIcon(canvas, _clearIconRectF);
12511288
DrawUpDownIcon(canvas, dirtyRect);
12521289
DrawAssistiveText(canvas, dirtyRect);
1253-
DrawPasswordToggleIcon(canvas, dirtyRect);
1290+
UpdateAssistiveLabels();
1291+
DrawPasswordToggleIcon(canvas, dirtyRect);
12541292
if (_effectsRenderer != null)
12551293
{
12561294
_effectsRenderer.ControlWidth = Width;
@@ -1377,7 +1415,10 @@ protected override void OnHandlerChanged()
13771415
{
13781416
WireEvents();
13791417
OnTextInputViewHandlerChanged(this.Content, new EventArgs());
1380-
}
1418+
#if ANDROID
1419+
ConfigureAccessibilityForAssistiveLabels();
1420+
#endif
1421+
}
13811422
else
13821423
{
13831424
if (HintLabelStyle != null && HelperLabelStyle != null && ErrorLabelStyle != null)

0 commit comments

Comments
 (0)