From 8593ef303843f9f4f1f9229b7a79957368ef477e Mon Sep 17 00:00:00 2001 From: Adam Demasi Date: Thu, 19 Feb 2026 03:20:29 +1100 Subject: [PATCH] Implement -accessibilityPlaceholderValue on macOS macOS uses a unique -accessibilityPlaceholderValue property for the placeholder of a text field; -accessibilityHelp has different meaning from HelpText on Windows. Map TextBox.PlaceholderText to be returned by -accessibilityPlaceholderValue. Ref: https://www.w3.org/TR/core-aam-1.2/#ariaPlaceholder --- native/Avalonia.Native/src/OSX/automation.mm | 5 +++++ .../Pages/AutomationPage.axaml | 2 +- .../Automation/Peers/AutomationPeer.cs | 20 ++++++++++++++++++- .../Automation/Peers/InteropAutomationPeer.cs | 1 + .../Automation/Peers/TextBoxAutomationPeer.cs | 2 ++ src/Avalonia.Native/AvnAutomationPeer.cs | 1 + src/Avalonia.Native/avn.idl | 1 + 7 files changed, 30 insertions(+), 2 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/automation.mm b/native/Avalonia.Native/src/OSX/automation.mm index f8c288c489..b42dc22f7a 100644 --- a/native/Avalonia.Native/src/OSX/automation.mm +++ b/native/Avalonia.Native/src/OSX/automation.mm @@ -220,6 +220,11 @@ return GetNSStringAndRelease(_peer->GetHelpText()); } +- (NSString *)accessibilityPlaceholderValue +{ + return GetNSStringAndRelease(_peer->GetPlaceholderText()); +} + - (id)accessibilityValue { if (_peer->IsRangeValueProvider()) diff --git a/samples/IntegrationTestApp/Pages/AutomationPage.axaml b/samples/IntegrationTestApp/Pages/AutomationPage.axaml index c02bc1baa6..91317bcb11 100644 --- a/samples/IntegrationTestApp/Pages/AutomationPage.axaml +++ b/samples/IntegrationTestApp/Pages/AutomationPage.axaml @@ -10,7 +10,7 @@ TextBlockWithNameAndAutomationId Label for TextBox - + Foo diff --git a/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs index b32b60118c..47754c9b50 100644 --- a/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs @@ -283,12 +283,29 @@ namespace Avalonia.Automation.Peers /// public string GetHelpText() => GetHelpTextCore() ?? string.Empty; + /// + /// Gets text that provides a placeholder for the element that is associated with this automation peer. + /// + /// + /// + /// + /// Windows + /// No mapping. + /// + /// + /// macOS + /// NSAccessibilityProtocol.accessibilityPlaceholderValue + /// + /// + /// + public string GetPlaceholderText() => GetPlaceholderTextCore() ?? string.Empty; + /// /// Gets the control type for the element that is associated with the UI Automation peer. /// /// /// Gets the type of the element. - /// + /// /// /// /// Windows @@ -595,6 +612,7 @@ namespace Avalonia.Automation.Peers protected abstract AutomationPeer? GetLabeledByCore(); protected abstract string? GetNameCore(); protected virtual string? GetHelpTextCore() => null; + protected virtual string? GetPlaceholderTextCore() => null; protected virtual AutomationLandmarkType? GetLandmarkTypeCore() => null; protected virtual int GetHeadingLevelCore() => 0; protected virtual string? GetItemTypeCore() => null; diff --git a/src/Avalonia.Controls/Automation/Peers/InteropAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/InteropAutomationPeer.cs index 5dfd507e1b..cdbb3286d8 100644 --- a/src/Avalonia.Controls/Automation/Peers/InteropAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/InteropAutomationPeer.cs @@ -29,6 +29,7 @@ internal class InteropAutomationPeer : AutomationPeer protected override AutomationPeer? GetLabeledByCore() => throw new NotImplementedException(); protected override string? GetNameCore() => throw new NotImplementedException(); protected override string? GetHelpTextCore() => throw new NotImplementedException(); + protected override string? GetPlaceholderTextCore() => throw new NotImplementedException(); protected override IReadOnlyList GetOrCreateChildrenCore() => throw new NotImplementedException(); protected override AutomationPeer? GetParentCore() => _parent; protected override bool HasKeyboardFocusCore() => throw new NotImplementedException(); diff --git a/src/Avalonia.Controls/Automation/Peers/TextBoxAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/TextBoxAutomationPeer.cs index dacec48d36..4f41e088c6 100644 --- a/src/Avalonia.Controls/Automation/Peers/TextBoxAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/TextBoxAutomationPeer.cs @@ -21,6 +21,8 @@ namespace Avalonia.Automation.Peers return AutomationControlType.Edit; } + protected override string? GetPlaceholderTextCore() => Owner.PlaceholderText; + protected virtual void OwnerPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) { if(e.Property == TextBox.TextProperty) diff --git a/src/Avalonia.Native/AvnAutomationPeer.cs b/src/Avalonia.Native/AvnAutomationPeer.cs index 86e0c5453d..cb418261f7 100644 --- a/src/Avalonia.Native/AvnAutomationPeer.cs +++ b/src/Avalonia.Native/AvnAutomationPeer.cs @@ -46,6 +46,7 @@ namespace Avalonia.Native public IAvnAutomationPeer? LabeledBy => Wrap(_inner.GetLabeledBy()); public IAvnString? Name => _inner.GetName().ToAvnString(); public IAvnString? HelpText => _inner.GetHelpText().ToAvnString(); + public IAvnString? PlaceholderText => _inner.GetPlaceholderText().ToAvnString(); public AvnLandmarkType LandmarkType => (AvnLandmarkType?)_inner.GetLandmarkType() ?? AvnLandmarkType.LandmarkNone; public int HeadingLevel => _inner.GetHeadingLevel(); public IAvnAutomationPeer? Parent => Wrap(_inner.GetParent()); diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index 7aabeceb72..ea8c1b4694 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -1328,6 +1328,7 @@ interface IAvnAutomationPeer : IUnknown void ValueProvider_SetValue([const] char* value); IAvnString* GetHelpText(); + IAvnString* GetPlaceholderText(); AvnLandmarkType GetLandmarkType(); int GetHeadingLevel();