Skip to content

Commit

Permalink
feat: add chip selection support for ItemsRepeater
Browse files Browse the repository at this point in the history
  • Loading branch information
Xiaoy312 committed Jan 17, 2023
1 parent 2c250c9 commit d3fb3b5
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 55 deletions.
25 changes: 24 additions & 1 deletion doc/controls/ChipAndChipGroup.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
## Summary
`Chip` is a control that can be used for selection, filtering, or performing an action from a list.
`ChipGroup` is a container that can house a collection of `Chip`s.
Alternatively, `Chip` can also be used within the data-template of an `ItemsRepeater`.

## Chip
`Chip` is derived from `ToggleButton`, a control that a user can select (check) or clear (uncheck).
`Chip` is derived from `ToggleButton`, a control that a user can select (check) or deselect (uncheck).

### C#
```csharp
Expand Down Expand Up @@ -181,3 +182,25 @@ xmlns:utu="using:Uno.Toolkit.UI"
SelectionMode="Multiple"
Style="{StaticResource SuggestionChipGroupStyle}" />
```

## Chip with ItemsRepeater

`Chip` can also be used within the data-template of `ItemsRepeater`, as shown below:

```xml
xmlns:utu="using:Uno.Toolkit.UI"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"

<muxc:ItemsRepeater ItemsSource="{Binding Items}"
utu:ChipExtensions.ChipSelectionMode="Single">
<muxc:ItemsRepeater.ItemTemplate>
<DataTemplate>
<utu:Chip Content="{Binding Name}" />
</DataTemplate>
</muxc:ItemsRepeater.ItemTemplate>
</muxc:ItemsRepeater>
```

### Remarks
For `ChipExtensions.ChipSelectionMode`, refer to the `ChipGroup.SelectionMode` property, as they function exactly in the same way.
The `Chip` must be the root-level element of the data-template.
83 changes: 83 additions & 0 deletions src/Uno.Toolkit.UI/Behaviors/ChipExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.UI.Xaml.Controls;

#if IS_WINUI
using Microsoft.UI.Xaml;
#else
using Windows.UI.Xaml;
#endif

namespace Uno.Toolkit.UI
{
public static class ChipExtensions
{
#region DependencyProperty: ChipSelectionMode = ChipSelectionMode.Single

public static DependencyProperty ChipSelectionModeProperty { get; } = DependencyProperty.RegisterAttached(
"ChipSelectionMode",
typeof(ChipSelectionMode),
typeof(ChipExtensions),
new PropertyMetadata(ChipSelectionMode.Single, OnChipSelectionModeChanged));

public static ChipSelectionMode GetChipSelectionMode(DependencyObject obj) => (ChipSelectionMode)obj.GetValue(ChipSelectionModeProperty);
public static void SetChipSelectionMode(DependencyObject obj, ChipSelectionMode value) => obj.SetValue(ChipSelectionModeProperty, value);

#endregion

private static void OnChipSelectionModeChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (sender is ItemsRepeater ir)
{
ir.ElementPrepared -= OnItemsRepeaterElementPrepared;
ir.ElementPrepared += OnItemsRepeaterElementPrepared;

EnforceItemsRepeaterChipSelection(ir);
}
else if (sender is Chip chip)
{
chip.OnChipSelectionModeChanged(e);
}
}

private static void EnforceItemsRepeaterChipSelection(ItemsRepeater ir)
{
var mode = GetChipSelectionMode(ir);
var singleSelection = default(Chip);

foreach (var chip in ir.GetChildren().OfType<Chip>())
{
if (mode is ChipSelectionMode.None)
{
chip.SetIsCheckedSilently(false);
}
else if (mode is ChipSelectionMode.Single or ChipSelectionMode.SingleOrNone && chip.IsChecked == true)
{
if (singleSelection is { })
{
chip.SetIsCheckedSilently(false);
}
else
{
singleSelection = chip;
}
}
}
if (mode is ChipSelectionMode.Single && singleSelection is null
&& ir.GetChildren().OfType<Chip>().FirstOrDefault() is { } first)
{
first.SetIsCheckedSilently(true);
}
}

private static void OnItemsRepeaterElementPrepared(ItemsRepeater sender, ItemsRepeaterElementPreparedEventArgs args)
{
if (args.Element is Chip chip)
{
SetChipSelectionMode(chip, GetChipSelectionMode(sender));
}
}
}
}
22 changes: 0 additions & 22 deletions src/Uno.Toolkit.UI/Controls/Chips/Chip.Members.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,28 +94,6 @@ public bool CanRemove

#endregion

#region DependencyProperty: IsCheckable = true

public static DependencyProperty IsCheckableProperty { get; } = DependencyProperty.Register(
nameof(IsCheckable),
typeof(bool),
typeof(Chip),
new PropertyMetadata(true, (s, e) => (s as Chip)?.OnIsCheckableChanged(e)));

/// <summary>
/// Gets or sets whether the chip can be checked. Used to prevent showing selection state.
/// </summary>
/// <remarks>
/// When nested under the <see cref="ChipGroup"/>, this property will be overwritten by <see cref="ChipGroup.SelectionMode"/>.
/// </remarks>
public bool IsCheckable
{
get => (bool)GetValue(IsCheckableProperty);
set => SetValue(IsCheckableProperty, value);
}

#endregion

#region DependencyProperty: RemovedCommand

public static DependencyProperty RemovedCommandProperty { get; } = DependencyProperty.Register(
Expand Down
36 changes: 28 additions & 8 deletions src/Uno.Toolkit.UI/Controls/Chips/Chip.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;

Expand All @@ -11,6 +12,7 @@
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using ItemsRepeater = Microsoft.UI.Xaml.Controls.ItemsRepeater;
#endif

namespace Uno.Toolkit.UI
Expand All @@ -20,7 +22,7 @@ public partial class Chip : ToggleButton
{
private const string RemoveButtonName = "PART_RemoveButton";

private bool _isMuted;
private bool _shouldRaiseIsCheckedChanged;

private ChipGroup? ChipGroup => ItemsControl.ItemsControlFromItemContainer(this) as ChipGroup;

Expand All @@ -40,17 +42,17 @@ protected override void OnApplyTemplate()
}
}

private void OnIsCheckableChanged(DependencyPropertyChangedEventArgs e)
internal void OnChipSelectionModeChanged(DependencyPropertyChangedEventArgs e)
{
if (!IsCheckable)
if (e.NewValue is ChipSelectionMode.None)
{
IsChecked = false;
}
}

private void RaiseIsCheckedChanged(object sender, RoutedEventArgs e)
{
if (!_isMuted)
if (!_shouldRaiseIsCheckedChanged)
{
IsCheckedChanged?.Invoke(sender, e);
}
Expand Down Expand Up @@ -83,24 +85,42 @@ internal void SetIsCheckedSilently(bool? value)
{
try
{
_isMuted = true;
_shouldRaiseIsCheckedChanged = true;
IsChecked = value;
}
finally
{
_isMuted = false;
_shouldRaiseIsCheckedChanged = false;
}
}

protected override void OnToggle()
{
if (!IsCheckable) return;
if (IsChecked == true && ChipGroup?.SelectionMode == ChipSelectionMode.Single)
var mode = GetSelectionMode();
if (mode is ChipSelectionMode.None ||
(mode is ChipSelectionMode.Single && IsChecked == true))
{
return;
}

// for Single && SingleOrNone mode, we need to deselect others on new selection.
// for ChipGroup setup, it is handled by ChipGroup; with ItemsRepeater setup, it is handled here:
if (IsChecked == false && // the value would be still false, until base.OnToggle()
Parent is ItemsRepeater ir &&
mode is ChipSelectionMode.Single or ChipSelectionMode.SingleOrNone)
{
foreach(var chip in ir.GetChildren().OfType<Chip>())
{
chip.SetIsCheckedSilently(false);
}
}

base.OnToggle();
}

private ChipSelectionMode GetSelectionMode() =>
ChipGroup?.SelectionMode ??
(Parent is ItemsRepeater ir ? ChipExtensions.GetChipSelectionMode(ir) : default(ChipSelectionMode?)) ??
ChipExtensions.GetChipSelectionMode(this);
}
}
2 changes: 0 additions & 2 deletions src/Uno.Toolkit.UI/Controls/Chips/ChipGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -293,8 +293,6 @@ private void EnforceSelectionMode()
var selected = default(Chip);
foreach (var container in GetItemContainers())
{
container.IsCheckable = SelectionMode != ChipSelectionMode.None;

if (IsSingleSelection && container.IsChecked == true)
{
// preserve first existing selection and clear the rest
Expand Down
8 changes: 0 additions & 8 deletions src/library/Uno.Toolkit.Material/Styles/Controls/v1/Chip.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -357,8 +357,6 @@
<Style.Setters>
<Setter Property="CanRemove"
Value="True" />
<Setter Property="IsCheckable"
Value="True" />
</Style.Setters>
</Style>

Expand All @@ -382,8 +380,6 @@
Value="{StaticResource MaterialOnSurfaceMediumBrush}" />
<Setter Property="CanRemove"
Value="False" />
<Setter Property="IsCheckable"
Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="utu:Chip">
Expand Down Expand Up @@ -596,8 +592,6 @@
BasedOn="{StaticResource BaseMaterialFilledChipStyle}">
<Setter Property="CanRemove"
Value="False" />
<Setter Property="IsCheckable"
Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="utu:Chip">
Expand Down Expand Up @@ -712,8 +706,6 @@
BasedOn="{StaticResource BaseMaterialOutlinedChipStyle}">
<Setter Property="CanRemove"
Value="False" />
<Setter Property="IsCheckable"
Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="utu:Chip">
Expand Down
14 changes: 0 additions & 14 deletions src/library/Uno.Toolkit.Material/Styles/Controls/v2/Chip.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -304,8 +304,6 @@
Value="{StaticResource OutlineBrush}" />
<Setter Property="BorderThickness"
Value="1" />
<Setter Property="IsCheckable"
Value="False" />
<Setter Property="CanRemove"
Value="False" />
</Style>
Expand All @@ -319,8 +317,6 @@
Value="{StaticResource MaterialChipElevation}" />
<Setter Property="Background"
Value="{StaticResource SurfaceBrush}" />
<Setter Property="IsCheckable"
Value="False" />
<Setter Property="CanRemove"
Value="False" />
</Style>
Expand All @@ -334,8 +330,6 @@
Value="1" />
<Setter Property="CanRemove"
Value="True" />
<Setter Property="IsCheckable"
Value="True" />
</Style>

<Style x:Key="MaterialFilterChipStyle"
Expand All @@ -347,8 +341,6 @@
Value="1" />
<Setter Property="CanRemove"
Value="True" />
<Setter Property="IsCheckable"
Value="True" />
</Style>

<Style x:Key="MaterialElevatedFilterChipStyle"
Expand All @@ -362,8 +354,6 @@
Value="{StaticResource SurfaceBrush}" />
<Setter Property="CanRemove"
Value="True" />
<Setter Property="IsCheckable"
Value="True" />
</Style>


Expand All @@ -376,8 +366,6 @@
Value="1" />
<Setter Property="CanRemove"
Value="False" />
<Setter Property="IsCheckable"
Value="True" />
</Style>

<Style x:Key="MaterialElevatedSuggestionChipStyle"
Expand All @@ -391,8 +379,6 @@
Value="{StaticResource SurfaceBrush}" />
<Setter Property="CanRemove"
Value="False" />
<Setter Property="IsCheckable"
Value="True" />
</Style>


Expand Down

0 comments on commit d3fb3b5

Please sign in to comment.