Skip to content

Commit 2eaaa48

Browse files
authored
Merge pull request #413 from CommunityToolkit/niels9001/settings-controls-a11y
[SettingsControls] Accessibility improvements
2 parents bfc2c90 + 04b1a96 commit 2eaaa48

File tree

9 files changed

+103
-49
lines changed

9 files changed

+103
-49
lines changed

components/SettingsControls/samples/ClickableSettingsCardSample.xaml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<!-- Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE file in the project root for more information. -->
1+
<!-- Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE file in the project root for more information. -->
22
<Page x:Class="SettingsControlsExperiment.Samples.ClickableSettingsCardSample"
33
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
44
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@@ -30,5 +30,13 @@
3030
<FontIcon Glyph="&#xE8A7;" />
3131
</labs:SettingsCard.ActionIcon>
3232
</labs:SettingsCard>
33+
<labs:SettingsCard Header="Hiding the ActionIcon"
34+
IsActionIconVisible="False"
35+
IsClickEnabled="True"
36+
IsEnabled="{x:Bind IsCardEnabled, Mode=OneWay}">
37+
<labs:SettingsCard.HeaderIcon>
38+
<FontIcon Glyph="&#xE72E;" />
39+
</labs:SettingsCard.HeaderIcon>
40+
</labs:SettingsCard>
3341
</StackPanel>
3442
</Page>

components/SettingsControls/samples/SettingsCard.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,6 @@ You can set the `Header`, `Description`, `HeaderIcon` and `Content` properties t
1818

1919
> [!SAMPLE SettingsCardSample]
2020
21-
SettingsCard can also be turned into a button, by setting the `IsClickEnabled` property. This can be useful whenever you want your settings component to navigate to a detail page or open an external link:
21+
SettingsCard can also be turned into a button, by setting the `IsClickEnabled` property. This can be useful whenever you want your settings component to navigate to a detail page or open an external link. You can set a custom icon by setting the `ActionIcon`, or hiding it completely by setting the `IsActionIconVisible` to `false`.
2222

2323
> [!SAMPLE ClickableSettingsCardSample]

components/SettingsControls/src/CommunityToolkit.Labs.WinUI.SettingsControls.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<PropertyGroup>
33
<ToolkitComponentName>SettingsControls</ToolkitComponentName>
44
<Description>This package contains the SettingsCard and SettingsExpander controls.</Description>
5-
<Version>0.0.17</Version>
5+
<Version>0.0.18</Version>
66
<LangVersion>10.0</LangVersion>
77

88
<!-- Rns suffix is required for namespaces shared across projects. See https://github.com/CommunityToolkit/Labs-Windows/issues/152 -->
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
namespace CommunityToolkit.Labs.WinUI;
6+
internal static partial class ControlHelpers
7+
{
8+
internal static bool IsXamlRootAvailable { get; } = Windows.Foundation.Metadata.ApiInformation.IsPropertyPresent("Windows.UI.Xaml.UIElement", "XamlRoot");
9+
}

components/SettingsControls/src/SettingsCard/SettingsCard.Properties.cs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ public partial class SettingsCard : ButtonBase
5959
typeof(SettingsCard),
6060
new PropertyMetadata(defaultValue: false, (d, e) => ((SettingsCard)d).OnIsClickEnabledPropertyChanged((bool)e.OldValue, (bool)e.NewValue)));
6161

62-
6362
/// <summary>
6463
/// The backing <see cref="DependencyProperty"/> for the <see cref="ContentAlignment"/> property.
6564
/// </summary>
@@ -69,6 +68,15 @@ public partial class SettingsCard : ButtonBase
6968
typeof(SettingsCard),
7069
new PropertyMetadata(defaultValue: ContentAlignment.Right));
7170

71+
/// <summary>
72+
/// The backing <see cref="DependencyProperty"/> for the <see cref="IsActionIconVisible"/> property.
73+
/// </summary>
74+
public static readonly DependencyProperty IsActionIconVisibleProperty = DependencyProperty.Register(
75+
nameof(IsActionIconVisible),
76+
typeof(bool),
77+
typeof(SettingsCard),
78+
new PropertyMetadata(defaultValue: true, (d, e) => ((SettingsCard)d).OnIsActionIconVisiblePropertyChanged((bool)e.OldValue, (bool)e.NewValue)));
79+
7280
/// <summary>
7381
/// Gets or sets the Header.
7482
/// </summary>
@@ -134,6 +142,15 @@ public ContentAlignment ContentAlignment
134142
set => SetValue(ContentAlignmentProperty, value);
135143
}
136144

145+
/// <summary>
146+
/// Gets or sets if the ActionIcon is shown.
147+
/// </summary>
148+
public bool IsActionIconVisible
149+
{
150+
get => (bool)GetValue(IsActionIconVisibleProperty);
151+
set => SetValue(IsActionIconVisibleProperty, value);
152+
}
153+
137154
protected virtual void OnIsClickEnabledPropertyChanged(bool oldValue, bool newValue)
138155
{
139156
OnIsClickEnabledChanged();
@@ -152,6 +169,11 @@ protected virtual void OnDescriptionPropertyChanged(object oldValue, object newV
152169
{
153170
OnDescriptionChanged();
154171
}
172+
173+
protected virtual void OnIsActionIconVisiblePropertyChanged(bool oldValue, bool newValue)
174+
{
175+
OnActionIconChanged();
176+
}
155177
}
156178

157179
public enum ContentAlignment

components/SettingsControls/src/SettingsCard/SettingsCard.cs

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace CommunityToolkit.Labs.WinUI;
99
/// A SettingsCard can also be hosted within a SettingsExpander.
1010
/// </summary>
1111

12-
[TemplatePart(Name = ActionIconPresenter, Type = typeof(ContentControl))]
12+
[TemplatePart(Name = ActionIconPresenterHolder, Type = typeof(Viewbox))]
1313
[TemplatePart(Name = HeaderPresenter, Type = typeof(ContentPresenter))]
1414
[TemplatePart(Name = DescriptionPresenter, Type = typeof(ContentPresenter))]
1515
[TemplatePart(Name = HeaderIconPresenterHolder, Type = typeof(Viewbox))]
@@ -20,7 +20,7 @@ public partial class SettingsCard : ButtonBase
2020
internal const string PressedState = "Pressed";
2121
internal const string DisabledState = "Disabled";
2222

23-
internal const string ActionIconPresenter = "PART_ActionIconPresenter";
23+
internal const string ActionIconPresenterHolder = "PART_ActionIconPresenterHolder";
2424
internal const string HeaderPresenter = "PART_HeaderPresenter";
2525
internal const string DescriptionPresenter = "PART_DescriptionPresenter";
2626
internal const string HeaderIconPresenterHolder = "PART_HeaderIconPresenterHolder";
@@ -37,7 +37,7 @@ protected override void OnApplyTemplate()
3737
{
3838
base.OnApplyTemplate();
3939
IsEnabledChanged -= OnIsEnabledChanged;
40-
OnButtonIconChanged();
40+
OnActionIconChanged();
4141
OnHeaderChanged();
4242
OnHeaderIconChanged();
4343
OnDescriptionChanged();
@@ -64,6 +64,7 @@ private void EnableButtonInteraction()
6464
{
6565
DisableButtonInteraction();
6666

67+
IsTabStop = true;
6768
PointerEntered += Control_PointerEntered;
6869
PointerExited += Control_PointerExited;
6970
PointerCaptureLost += Control_PointerCaptureLost;
@@ -74,6 +75,7 @@ private void EnableButtonInteraction()
7475

7576
private void DisableButtonInteraction()
7677
{
78+
IsTabStop = false;
7779
PointerEntered -= Control_PointerEntered;
7880
PointerExited -= Control_PointerExited;
7981
PointerCaptureLost -= Control_PointerCaptureLost;
@@ -94,7 +96,11 @@ private void Control_PreviewKeyDown(object sender, KeyRoutedEventArgs e)
9496
{
9597
if (e.Key == Windows.System.VirtualKey.Enter || e.Key == Windows.System.VirtualKey.Space || e.Key == Windows.System.VirtualKey.GamepadA)
9698
{
97-
VisualStateManager.GoToState(this, PressedState, true);
99+
// Check if the active focus is on the card itself - only then we show the pressed state.
100+
if (GetFocusedElement() is SettingsCard)
101+
{
102+
VisualStateManager.GoToState(this, PressedState, true);
103+
}
98104
}
99105
}
100106

@@ -152,7 +158,7 @@ protected override AutomationPeer OnCreateAutomationPeer()
152158

153159
private void OnIsClickEnabledChanged()
154160
{
155-
OnButtonIconChanged();
161+
OnActionIconChanged();
156162
if (IsClickEnabled)
157163
{
158164
EnableButtonInteraction();
@@ -168,13 +174,18 @@ private void OnIsEnabledChanged(object sender, DependencyPropertyChangedEventArg
168174
VisualStateManager.GoToState(this, IsEnabled ? NormalState : DisabledState, true);
169175
}
170176

171-
private void OnButtonIconChanged()
177+
private void OnActionIconChanged()
172178
{
173-
if (GetTemplateChild(ActionIconPresenter) is FrameworkElement buttonIconPresenter)
179+
if (GetTemplateChild(ActionIconPresenterHolder) is FrameworkElement actionIconPresenter)
174180
{
175-
buttonIconPresenter.Visibility = IsClickEnabled
176-
? Visibility.Visible
177-
: Visibility.Collapsed;
181+
if (IsClickEnabled && IsActionIconVisible)
182+
{
183+
actionIconPresenter.Visibility = Visibility.Visible;
184+
}
185+
else
186+
{
187+
actionIconPresenter.Visibility =Visibility.Collapsed;
188+
}
178189
}
179190
}
180191

@@ -207,4 +218,16 @@ private void OnHeaderChanged()
207218
: Visibility.Collapsed;
208219
}
209220
}
221+
222+
private FrameworkElement? GetFocusedElement()
223+
{
224+
if (ControlHelpers.IsXamlRootAvailable && XamlRoot != null)
225+
{
226+
return FocusManager.GetFocusedElement(XamlRoot) as FrameworkElement;
227+
}
228+
else
229+
{
230+
return FocusManager.GetFocusedElement() as FrameworkElement;
231+
}
232+
}
210233
}

components/SettingsControls/src/SettingsCard/SettingsCard.xaml

Lines changed: 20 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -96,17 +96,15 @@
9696
</ResourceDictionary.ThemeDictionaries>
9797
<Thickness x:Key="SettingsCardBorderThickness">1</Thickness>
9898
<Thickness x:Key="SettingsCardPadding">16,16,16,16</Thickness>
99-
<Thickness x:Key="SettingsCardIconMargin">2,0,20,0</Thickness>
10099
<x:Double x:Key="SettingsCardMinWidth">148</x:Double>
101100
<x:Double x:Key="SettingsCardMinHeight">68</x:Double>
102-
<x:Double x:Key="SettingsCardActionButtonWidth">32</x:Double>
103-
<x:Double x:Key="SettingsCardActionButtonHeight">32</x:Double>
104101
<x:Double x:Key="SettingsCardDescriptionFontSize">12</x:Double>
105102
<x:Double x:Key="SettingsCardHeaderIconMaxSize">20</x:Double>
106-
<x:Double x:Key="SettingsCardActionIconMaxSize">13</x:Double>
107103
<x:Double x:Key="SettingsCardLeftIndention">0</x:Double>
108104
<x:Double x:Key="SettingsCardContentMinWidth">120</x:Double>
109105
<Thickness x:Key="SettingsCardHeaderIconMargin">2,0,20,0</Thickness>
106+
<Thickness x:Key="SettingsCardActionIconMargin">14,0,0,0</Thickness>
107+
<x:Double x:Key="SettingsCardActionIconMaxSize">13</x:Double>
110108
<Thickness x:Key="SettingsCardVerticalHeaderContentSpacing">0,8,0,0</Thickness>
111109
<x:Double x:Key="SettingsCardWrapThreshold">476</x:Double>
112110
<x:Double x:Key="SettingsCardWrapNoIconThreshold">286</x:Double>
@@ -385,6 +383,7 @@
385383
MaxHeight="{ThemeResource SettingsCardHeaderIconMaxSize}"
386384
Margin="{ThemeResource SettingsCardHeaderIconMargin}">
387385
<ContentPresenter x:Name="PART_HeaderIconPresenter"
386+
win:AutomationProperties.AccessibilityView="Raw"
388387
win:HighContrastAdjustment="None"
389388
Content="{TemplateBinding HeaderIcon}" />
390389
</Viewbox>
@@ -439,32 +438,23 @@
439438
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
440439
Content="{TemplateBinding Content}" />
441440

442-
<ContentControl x:Name="PART_ActionIconPresenter"
443-
Grid.RowSpan="2"
444-
Grid.Column="3"
445-
Width="{ThemeResource SettingsCardActionButtonWidth}"
446-
Height="{ThemeResource SettingsCardActionButtonHeight}"
447-
Margin="4,0,-8,0"
448-
HorizontalAlignment="Center"
449-
VerticalAlignment="Center"
450-
HorizontalContentAlignment="Center"
451-
VerticalContentAlignment="Center"
452-
win:AutomationProperties.LocalizedControlType="button"
453-
win:AutomationProperties.Name="{TemplateBinding ActionIconToolTip}"
454-
win:HighContrastAdjustment="None"
455-
CornerRadius="{ThemeResource ControlCornerRadius}"
456-
FocusVisualMargin="-3"
457-
FontFamily="{ThemeResource SymbolThemeFontFamily}"
458-
IsTabStop="True"
459-
ToolTipService.ToolTip="{TemplateBinding ActionIconToolTip}"
460-
UseSystemFocusVisuals="True"
461-
Visibility="Collapsed">
462-
<Viewbox MaxWidth="{ThemeResource SettingsCardActionIconMaxSize}"
463-
MaxHeight="{ThemeResource SettingsCardActionIconMaxSize}">
464-
<ContentPresenter win:HighContrastAdjustment="None"
465-
Content="{TemplateBinding ActionIcon}" />
466-
</Viewbox>
467-
</ContentControl>
441+
<Viewbox x:Name="PART_ActionIconPresenterHolder"
442+
Grid.RowSpan="2"
443+
Grid.Column="3"
444+
MaxWidth="{ThemeResource SettingsCardActionIconMaxSize}"
445+
MaxHeight="{ThemeResource SettingsCardActionIconMaxSize}"
446+
Margin="{ThemeResource SettingsCardActionIconMargin}"
447+
HorizontalAlignment="Center"
448+
VerticalAlignment="Center"
449+
win:HighContrastAdjustment="None"
450+
Visibility="Collapsed">
451+
<ContentPresenter x:Name="PART_ActionIconPresenter"
452+
win:AutomationProperties.AccessibilityView="Raw"
453+
win:HighContrastAdjustment="None"
454+
Content="{TemplateBinding ActionIcon}"
455+
FontFamily="{ThemeResource SymbolThemeFontFamily}"
456+
ToolTipService.ToolTip="{TemplateBinding ActionIconToolTip}" />
457+
</Viewbox>
468458
</Grid>
469459
</ControlTemplate>
470460
</Setter.Value>

components/SettingsControls/src/SettingsExpander/SettingsExpander.xaml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@
1212
</ResourceDictionary.MergedDictionaries>
1313

1414
<x:String x:Key="SettingsExpanderChevronToolTip">Show all settings</x:String>
15-
<Thickness x:Key="SettingsExpanderHeaderPadding">16,16,0,16</Thickness>
15+
<Thickness x:Key="SettingsExpanderHeaderPadding">16,16,4,16</Thickness>
1616
<Thickness x:Key="SettingsExpanderItemPadding">58,8,44,8</Thickness>
1717
<Thickness x:Key="SettingsExpanderItemBorderThickness">0,1,0,0</Thickness>
1818
<Thickness x:Key="ClickableSettingsExpanderItemPadding">58,8,16,8</Thickness>
1919
<x:Double x:Key="SettingsExpanderContentMinHeight">16</x:Double>
20+
<x:Double x:Key="SettingsExpanderChevronButtonWidth">32</x:Double>
21+
<x:Double x:Key="SettingsExpanderChevronButtonHeight">32</x:Double>
2022

2123
<Style x:Key="DefaultSettingsExpanderItemStyle"
2224
BasedOn="{StaticResource DefaultSettingsCardStyle}"
@@ -533,9 +535,9 @@
533535

534536
<ContentControl x:Name="ExpandCollapseChevronBorder"
535537
Grid.Column="1"
536-
Width="{StaticResource SettingsCardActionButtonWidth}"
537-
Height="{StaticResource SettingsCardActionButtonHeight}"
538-
Margin="4,0,8,0"
538+
Width="{StaticResource SettingsExpanderChevronButtonWidth}"
539+
Height="{StaticResource SettingsExpanderChevronButtonHeight}"
540+
Margin="0,0,8,0"
539541
HorizontalAlignment="Center"
540542
VerticalAlignment="Center"
541543
HorizontalContentAlignment="Center"

components/SettingsControls/src/SettingsExpander/SettingsExpanderAutomationPeer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public SettingsExpanderAutomationPeer(SettingsExpander owner)
2828
/// <returns>The control type.</returns>
2929
protected override AutomationControlType GetAutomationControlTypeCore()
3030
{
31-
return AutomationControlType.Button;
31+
return AutomationControlType.Group;
3232
}
3333

3434
/// <summary>

0 commit comments

Comments
 (0)