2021-04-13 13:12:18 +08:00
using System ;
2020-06-14 18:54:14 +08:00
using System.Collections.Generic ;
2020-11-27 13:13:26 +08:00
using System.ComponentModel ;
2020-06-14 18:54:14 +08:00
using System.Linq ;
2021-05-06 13:42:19 +08:00
using System.Linq.Expressions ;
2021-09-05 18:13:38 +08:00
using System.Reflection ;
2020-11-27 13:13:26 +08:00
using System.Text.Json ;
2020-06-14 18:54:14 +08:00
using System.Threading.Tasks ;
2021-04-24 13:07:27 +08:00
using AntDesign.Core.Helpers.MemberPath ;
2020-09-09 22:12:12 +08:00
using AntDesign.Internal ;
2020-06-14 18:54:14 +08:00
using AntDesign.JsInterop ;
2021-04-13 13:12:18 +08:00
using AntDesign.Select ;
using AntDesign.Select.Internal ;
2020-06-14 18:54:14 +08:00
using Microsoft.AspNetCore.Components ;
2020-11-27 13:13:26 +08:00
using Microsoft.AspNetCore.Components.Web ;
2021-04-08 22:17:44 +08:00
using OneOf ;
2020-06-14 18:54:14 +08:00
2020-11-27 13:13:26 +08:00
#pragma warning disable CA1716 // Disable Select name warning
#pragma warning disable CA1305 // IFormatProvider warning
2020-06-14 18:54:14 +08:00
namespace AntDesign
{
2021-03-12 17:02:11 +08:00
public partial class Select < TItemValue , TItem > : AntInputComponentBase < TItemValue >
2020-06-14 18:54:14 +08:00
{
2020-11-27 13:13:26 +08:00
#region Parameters
2021-07-28 22:40:37 +08:00
/// <summary>
/// Show clear button.
/// </summary>
2020-11-27 13:13:26 +08:00
[Parameter] public bool AllowClear { get ; set ; }
2021-07-28 22:40:37 +08:00
/// <summary>
/// Whether the current search will be cleared on selecting an item.
/// </summary>
2020-11-27 13:13:26 +08:00
[Parameter] public bool AutoClearSearchValue { get ; set ; } = true ;
2021-07-28 22:40:37 +08:00
/// <summary>
/// Toggle the border style.
/// </summary>
2020-11-27 13:13:26 +08:00
[Parameter] public bool Bordered { get ; set ; } = true ;
2021-03-12 17:02:11 +08:00
2021-07-28 22:40:37 +08:00
/// <summary>
/// Converts custom tag (a string) to TItemValue type.
/// </summary>
[Parameter]
public Func < string , TItemValue > CustomTagLabelToValue { get ; set ; } =
( label ) = > ( TItemValue ) TypeDescriptor . GetConverter ( typeof ( TItemValue ) ) . ConvertFromInvariantString ( label ) ;
2021-09-05 18:13:38 +08:00
bool _dataSourceHasChanged = false ;
IEnumerable < TItem > _dataSourceShallowCopy ;
private bool? _isTItemPrimitive ;
private bool IsTItemPrimitive
{
get
{
if ( _isTItemPrimitive is null )
{
_isTItemPrimitive = IsSimpleType ( typeof ( TItem ) ) ;
}
return _isTItemPrimitive ! . Value ;
}
}
2021-07-28 22:40:37 +08:00
/// <summary>
2021-09-05 18:13:38 +08:00
/// MethodInfo will contain attached MemberwiseClone protected
/// method. Due to its protection level, it has to be accessed
/// using reflection. It will be used during generation of
/// the DataSource shallow copy (which is a new list of DataSource
/// items with shallow copy of each item).
2021-07-28 22:40:37 +08:00
/// </summary>
2021-09-05 18:13:38 +08:00
private MethodInfo _dataSourceItemShallowCopyMehtod ;
private MethodInfo GetDataSourceItemCloneMethod ( )
{
if ( _dataSourceItemShallowCopyMehtod is null )
{
_dataSourceItemShallowCopyMehtod = this . GetType ( ) . GetGenericArguments ( ) [ 1 ]
. GetMethod ( "MemberwiseClone" , BindingFlags . Instance | BindingFlags . NonPublic ) ;
if ( DataSourceEqualityComparer is null )
{
DataSourceEqualityComparer = new DataSourceEqualityComparer < TItemValue , TItem > ( this ) ;
}
}
return _dataSourceItemShallowCopyMehtod ;
}
/// <summary>
/// The datasource for this component.
/// </summary>
2021-07-28 22:40:37 +08:00
[Parameter]
public IEnumerable < TItem > DataSource
{
get = > _datasource ;
set
{
if ( value = = null & & _datasource = = null )
{
return ;
}
if ( value = = null & & _datasource ! = null )
{
if ( ! _isInitialized )
{
_selectedValue = default ;
}
else
{
SelectOptionItems . Clear ( ) ;
SelectedOptionItems . Clear ( ) ;
Value = default ;
_datasource = null ;
2021-09-05 18:13:38 +08:00
_dataSourceShallowCopy = null ;
2021-07-28 22:40:37 +08:00
OnDataSourceChanged ? . Invoke ( ) ;
}
return ;
}
if ( value ! = null & & ! value . Any ( ) & & SelectOptionItems . Any ( ) )
{
SelectOptionItems . Clear ( ) ;
SelectedOptionItems . Clear ( ) ;
Value = default ;
_datasource = value ;
2021-09-05 18:13:38 +08:00
_dataSourceShallowCopy = value . ToList ( ) ;
2021-07-28 22:40:37 +08:00
2021-09-05 18:13:38 +08:00
OnDataSourceChanged ? . Invoke ( ) ;
2021-07-28 22:40:37 +08:00
return ;
}
if ( value ! = null )
{
if ( _datasource = = null )
{
2021-09-05 18:13:38 +08:00
_dataSourceHasChanged = true ;
2021-07-28 22:40:37 +08:00
}
else
{
2021-09-05 18:13:38 +08:00
_dataSourceHasChanged = ! value . SequenceEqual ( _dataSourceShallowCopy , DataSourceEqualityComparer ) ;
2021-07-28 22:40:37 +08:00
}
2021-09-05 18:13:38 +08:00
if ( _dataSourceHasChanged )
2021-07-28 22:40:37 +08:00
{
OnDataSourceChanged ? . Invoke ( ) ;
_datasource = value ;
2021-09-05 18:13:38 +08:00
if ( IsTItemPrimitive )
{
_dataSourceShallowCopy = _datasource . ToList ( ) ;
}
else
{
var cloneMethod = GetDataSourceItemCloneMethod ( ) ;
_dataSourceShallowCopy = _datasource . Select ( x = > ( TItem ) cloneMethod . Invoke ( x , null ) ) . ToList ( ) ;
}
2021-07-28 22:40:37 +08:00
}
}
}
}
2021-09-05 18:13:38 +08:00
/// <summary>
/// EqualityComparer that will be used during DataSource change
/// detection. If no comparer set, default .Net is going to be
/// used.
/// </summary>
[Parameter]
public IEqualityComparer < TItem > DataSourceEqualityComparer { get ; set ; }
2021-07-28 22:40:37 +08:00
/// <summary>
/// Activates the first item that is not deactivated.
/// </summary>
2021-02-09 00:31:44 +08:00
[Parameter]
public bool DefaultActiveFirstOption
{
get { return _defaultActiveFirstOption ; }
2021-03-12 17:02:11 +08:00
set
{
2021-04-08 22:17:44 +08:00
_defaultActiveFirstOption = value ;
2021-02-09 00:31:44 +08:00
if ( ! _defaultActiveFirstOption )
{
_defaultActiveFirstOptionApplied = true ;
}
}
}
2021-03-12 17:02:11 +08:00
2021-07-28 22:40:37 +08:00
/// <summary>
/// Used when Mode = default - The value is used during initialization and when pressing the Reset button within Forms.
/// </summary>
[Parameter]
public TItemValue DefaultValue
{
get = > _defaultValue ;
set
{
var hasChanged = ! EqualityComparer < TItemValue > . Default . Equals ( value , _defaultValue ) ;
if ( hasChanged )
{
_defaultValueIsNotNull = ! EqualityComparer < TItemValue > . Default . Equals ( value , default ) ;
_defaultValue = value ;
}
}
}
/// <summary>
/// Used when Mode = multiple | tags - The values are used during initialization and when pressing the Reset button within Forms.
/// </summary>
[Parameter]
public IEnumerable < TItemValue > DefaultValues
{
get = > _defaultValues ;
set
{
if ( value ! = null & & _defaultValues ! = null )
{
var hasChanged = ! value . SequenceEqual ( _defaultValues ) ;
if ( ! hasChanged )
return ;
_defaultValuesHasItems = value . Any ( ) ;
_defaultValues = value ;
}
else if ( value ! = null & & _defaultValues = = null )
{
_defaultValuesHasItems = value . Any ( ) ;
_defaultValues = value ;
}
else if ( value = = null & & _defaultValues ! = null )
{
_defaultValuesHasItems = false ;
_defaultValues = default ;
}
}
}
/// <summary>
/// Whether the Select component is disabled.
/// </summary>
2020-11-27 13:13:26 +08:00
[Parameter] public bool Disabled { get ; set ; }
2021-02-28 00:13:13 +08:00
2021-07-28 22:40:37 +08:00
/// <summary>
/// The name of the property to be used as a disabled indicator.
/// </summary>
2021-02-28 00:13:13 +08:00
[Parameter]
public string DisabledName
{
get = > _disabledName ;
set
{
2021-04-24 13:07:27 +08:00
_getDisabled = string . IsNullOrWhiteSpace ( value ) ? null : PathHelper . GetDelegate < TItem , bool > ( value ) ;
2021-02-28 00:13:13 +08:00
_disabledName = value ;
}
}
2021-07-28 22:40:37 +08:00
/// <summary>
/// Will match drowdown width:
/// - for boolean: true - with widest item in the dropdown list
/// - for string: with value (e.g.: "256px")
/// </summary>
[Parameter] public OneOf < bool , string > DropdownMatchSelectWidth { get ; set ; } = true ;
/// <summary>
/// Will not allow dropdown width to grow above stated in here value (eg. "768px")
/// </summary>
[Parameter] public string DropdownMaxWidth { get ; set ; } = "auto" ;
/// <summary>
/// Customize dropdown content.
/// </summary>
2020-11-27 13:13:26 +08:00
[Parameter] public Func < RenderFragment , RenderFragment > DropdownRender { get ; set ; }
2021-07-28 22:40:37 +08:00
/// <summary>
/// Indicates whether the search function is active or not. Always true for mode tags.
/// </summary>
2020-11-27 13:13:26 +08:00
[Parameter] public bool EnableSearch { get ; set ; }
2021-02-28 00:13:13 +08:00
2021-07-28 22:40:37 +08:00
/// <summary>
/// The name of the property to be used as a group indicator.
/// If the value is set, the entries are displayed in groups.
/// Use additional SortByGroup and SortByLabel.
/// </summary>
2021-02-28 00:13:13 +08:00
[Parameter]
public string GroupName
{
get = > _groupName ;
set
{
2021-04-24 13:07:27 +08:00
_getGroup = string . IsNullOrWhiteSpace ( value ) ? null : PathHelper . GetDelegate < TItem , string > ( value ) ;
2021-02-28 00:13:13 +08:00
_groupName = value ;
}
}
2021-07-28 22:40:37 +08:00
/// <summary>
/// Hides the selected items when they are selected.
/// </summary>
2020-11-27 13:13:26 +08:00
[Parameter] public bool HideSelected { get ; set ; }
2021-07-28 22:40:37 +08:00
/// <summary>
/// Is used to increase the speed. If you expect changes to the label name,
/// group name or disabled indicator, disable this property.
/// </summary>
2020-11-27 13:13:26 +08:00
[Parameter] public bool IgnoreItemChanges { get ; set ; } = true ;
2021-07-28 22:40:37 +08:00
/// <summary>
/// Is used to customize the item style.
/// </summary>
2020-11-27 13:13:26 +08:00
[Parameter] public RenderFragment < TItem > ItemTemplate { get ; set ; }
/// <summary>
2021-07-28 22:40:37 +08:00
/// Whether to embed label in value, turn the format of value from TItemValue to string (JSON)
/// e.g. { "value": TItemValue, "label": "Label value" }
2020-11-27 13:13:26 +08:00
/// </summary>
[Parameter] public bool LabelInValue { get ; set ; }
2021-07-28 22:40:37 +08:00
/// <summary>
/// The name of the property to be used for the label.
/// </summary>
2021-02-28 00:13:13 +08:00
[Parameter]
public string LabelName
{
get = > _labelName ;
set
{
2021-06-13 00:39:31 +08:00
_getLabel = string . IsNullOrWhiteSpace ( value ) ? null : PathHelper . GetDelegate < TItem , string > ( value ) ;
2021-03-02 22:01:57 +08:00
if ( SelectMode = = SelectMode . Tags )
{
2021-06-13 00:39:31 +08:00
_setLabel = string . IsNullOrWhiteSpace ( value ) ? null : PathHelper . SetDelegate < TItem , string > ( value ) ;
2021-03-02 22:01:57 +08:00
}
2021-02-28 00:13:13 +08:00
_labelName = value ;
}
}
2021-07-28 22:40:37 +08:00
/// <summary>
/// Is used to customize the label style.
/// </summary>
2020-11-27 13:13:26 +08:00
[Parameter] public RenderFragment < TItem > LabelTemplate { get ; set ; }
2021-04-13 13:12:18 +08:00
/// <summary>
2021-07-28 22:40:37 +08:00
/// Show loading indicator. You have to write the loading logic on your own.
2021-04-13 13:12:18 +08:00
/// </summary>
2021-07-28 22:40:37 +08:00
[Parameter] public bool Loading { get ; set ; }
2021-04-13 13:12:18 +08:00
private OneOf < int , ResponsiveTag > _maxTagCount ;
2021-07-28 22:40:37 +08:00
private int _maxTagCountAsInt ;
/// <summary>
/// Max tag count to show. responsive will cost render performance.
/// </summary>
2021-04-13 13:12:18 +08:00
[Parameter]
public OneOf < int , ResponsiveTag > MaxTagCount
{
get { return _maxTagCount ; }
2021-07-28 22:40:37 +08:00
set
{
2021-04-13 13:12:18 +08:00
_maxTagCount = value ;
value . Switch ( intValue = >
{
IsResponsive = false ;
HasTagCount = intValue > 0 ;
2021-07-28 22:40:37 +08:00
_maxTagCountAsInt = intValue ;
2021-04-13 13:12:18 +08:00
} , enumValue = >
{
IsResponsive = enumValue = = ResponsiveTag . Responsive ;
HasTagCount = false ;
} ) ;
}
}
internal bool IsResponsive { get ; set ; }
internal bool HasTagCount { get ; set ; }
2021-07-28 22:40:37 +08:00
/// <summary>
/// Placeholder for hidden tags. If used with ResponsiveTag.Responsive, implement your own handling logic.
/// </summary>
2021-04-13 13:12:18 +08:00
[Parameter] public RenderFragment < IEnumerable < TItem > > MaxTagPlaceholder { get ; set ; }
2021-07-28 22:40:37 +08:00
/// <summary>
/// How long (number of characters) a tag will be.
/// Only for Mode = "multiple" or Mode = "tags"
/// </summary>
/// <value>
/// The maximum length of the tag text.
/// </value>
[Parameter] public int MaxTagTextLength { get ; set ; }
/// <summary>
/// Set mode of Select - default | multiple | tags
/// </summary>
2020-11-27 13:13:26 +08:00
[Parameter] public string Mode { get ; set ; } = "default" ;
2021-07-28 22:40:37 +08:00
/// <summary>
/// Specify content to show when no result matches.
/// </summary>
2020-11-27 13:13:26 +08:00
[Parameter] public RenderFragment NotFoundContent { get ; set ; }
2021-07-28 22:40:37 +08:00
/// <summary>
/// Called when blur.
/// </summary>
2020-11-27 13:13:26 +08:00
[Parameter] public Action OnBlur { get ; set ; }
2021-07-28 22:40:37 +08:00
/// <summary>
/// Called when the user clears the selection.
/// </summary>
2020-11-27 13:13:26 +08:00
[Parameter] public Action OnClearSelected { get ; set ; }
/// <summary>
2021-07-28 22:40:37 +08:00
/// Called when custom tag is created.
/// </summary>
[Parameter] public Action < string > OnCreateCustomTag { get ; set ; }
/// <summary>
/// Called when the datasource changes. From null to IEnumerable<TItem>, from IEnumerable<TItem> to IEnumerable<TItem> or from IEnumerable<TItem> to null.
2020-11-27 13:13:26 +08:00
/// </summary>
/// <remarks>
/// It does not trigger if a value inside the IEnumerable<TItem> changes.
/// </remarks>
[Parameter] public Action OnDataSourceChanged { get ; set ; }
2021-07-28 22:40:37 +08:00
/// <summary>
/// Called when the dropdown visibility changes.
/// </summary>
2020-11-27 13:13:26 +08:00
[Parameter] public Action < bool > OnDropdownVisibleChange { get ; set ; }
2021-07-28 22:40:37 +08:00
/// <summary>
/// Called when focus.
/// </summary>
2020-11-27 13:13:26 +08:00
[Parameter] public Action OnFocus { get ; set ; }
2021-07-28 22:40:37 +08:00
/// <summary>
/// Called when mouse enter.
/// </summary>
2020-11-27 13:13:26 +08:00
[Parameter] public Action OnMouseEnter { get ; set ; }
2021-07-28 22:40:37 +08:00
/// <summary>
/// Called when mouse leave.
/// </summary>
2020-11-27 13:13:26 +08:00
[Parameter] public Action OnMouseLeave { get ; set ; }
2021-07-28 22:40:37 +08:00
/// <summary>
/// Callback function that is fired when input changed.
/// </summary>
2020-11-27 13:13:26 +08:00
[Parameter] public Action < string > OnSearch { get ; set ; }
2021-07-28 22:40:37 +08:00
/// <summary>
/// Called when the selected item changes.
/// </summary>
2020-11-27 13:13:26 +08:00
[Parameter] public Action < TItem > OnSelectedItemChanged { get ; set ; }
2021-07-28 22:40:37 +08:00
/// <summary>
/// Called when the selected items changes.
/// </summary>
2020-11-27 13:13:26 +08:00
[Parameter] public Action < IEnumerable < TItem > > OnSelectedItemsChanged { get ; set ; }
2021-07-28 22:40:37 +08:00
/// <summary>
/// Controlled open state of dropdown.
/// </summary>
2020-11-27 13:13:26 +08:00
[Parameter] public bool Open { get ; set ; }
2021-07-28 22:40:37 +08:00
/// <summary>
/// Placeholder of select.
/// </summary>
2020-11-27 13:13:26 +08:00
[Parameter] public string Placeholder { get ; set ; }
2021-07-28 22:40:37 +08:00
/// <summary>
/// The maximum height of the popup container.
/// </summary>
2020-11-27 13:13:26 +08:00
[Parameter] public string PopupContainerMaxHeight { get ; set ; } = "256px" ;
2021-07-28 22:40:37 +08:00
/// <summary>
/// Use this to fix overlay problems e.g. #area
/// </summary>
2020-11-27 13:13:26 +08:00
[Parameter] public string PopupContainerSelector { get ; set ; } = "body" ;
2021-07-28 22:40:37 +08:00
/// <summary>
/// The custom prefix icon.
/// </summary>
[Parameter] public RenderFragment PrefixIcon { get ; set ; }
/// <summary>
/// Used for rendering select options manually.
/// </summary>
[Parameter] public RenderFragment SelectOptions { get ; set ; }
2021-04-08 22:17:44 +08:00
private bool _showArrowIconChanged ;
2021-07-28 22:40:37 +08:00
/// <summary>
/// Whether to show the drop-down arrow
/// </summary>
2021-04-08 22:17:44 +08:00
[Parameter]
public bool ShowArrowIcon
{
get { return _showArrowIcon ; }
2021-09-05 18:13:38 +08:00
set
{
2021-04-08 22:17:44 +08:00
_showArrowIcon = value ;
_showArrowIconChanged = true ;
}
}
2020-11-27 13:13:26 +08:00
2021-02-04 23:40:47 +08:00
/// <summary>
2021-07-28 22:40:37 +08:00
/// Whether show search input in single mode.
2021-02-04 23:40:47 +08:00
/// </summary>
2021-07-28 22:40:37 +08:00
[Parameter] public bool ShowSearchIcon { get ; set ; } = true ;
2020-06-14 18:54:14 +08:00
2021-07-28 22:40:37 +08:00
/// <summary>
/// Sort items by group name. None | Ascending | Descending
/// </summary>
[Parameter] public SortDirection SortByGroup { get ; set ; } = SortDirection . None ;
2020-11-27 13:13:26 +08:00
2021-07-28 22:40:37 +08:00
/// <summary>
/// Sort items by label value. None | Ascending | Descending
/// </summary>
[Parameter] public SortDirection SortByLabel { get ; set ; } = SortDirection . None ;
2020-11-27 13:13:26 +08:00
2021-07-28 22:40:37 +08:00
/// <summary>
/// The custom suffix icon.
/// </summary>
[Parameter] public RenderFragment SuffixIcon { get ; set ; }
2020-11-27 13:13:26 +08:00
2021-07-28 22:40:37 +08:00
/// <summary>
/// Define what characters will be treated as token separators for newly created tags.
/// Useful when creating new tags using only keyboard.
/// </summary>
[Parameter] public char [ ] TokenSeparators { get ; set ; }
2020-06-14 18:54:14 +08:00
2021-07-11 18:41:27 +08:00
bool _valueHasChanged ;
2021-07-28 22:40:37 +08:00
/// <summary>
/// Get or set the selected value.
/// </summary>
2020-11-27 13:13:26 +08:00
[Parameter]
public override TItemValue Value
2020-06-14 18:54:14 +08:00
{
2020-11-27 13:13:26 +08:00
get = > _selectedValue ;
set
2020-06-14 18:54:14 +08:00
{
2021-07-11 18:41:27 +08:00
_valueHasChanged = ! EqualityComparer < TItemValue > . Default . Equals ( value , _selectedValue ) ;
if ( _valueHasChanged )
2020-06-14 18:54:14 +08:00
{
2020-11-27 13:13:26 +08:00
_selectedValue = value ;
2021-09-05 18:13:38 +08:00
_valueHasChanged = _isInitialized ;
2020-06-14 18:54:14 +08:00
}
}
}
2021-07-28 22:40:37 +08:00
/// <summary>
/// Get or set the selected values.
/// </summary>
2020-11-27 13:13:26 +08:00
[Parameter]
public IEnumerable < TItemValue > Values
2020-06-14 18:54:14 +08:00
{
2020-11-27 13:13:26 +08:00
get = > _selectedValues ;
set
2020-06-14 18:54:14 +08:00
{
2020-11-27 13:13:26 +08:00
if ( value ! = null & & _selectedValues ! = null )
{
var hasChanged = ! value . SequenceEqual ( _selectedValues ) ;
2020-06-14 18:54:14 +08:00
2020-11-27 13:13:26 +08:00
if ( ! hasChanged )
return ;
2020-06-14 18:54:14 +08:00
2020-11-27 13:13:26 +08:00
_selectedValues = value ;
_ = OnValuesChangeAsync ( value ) ;
2020-06-14 18:54:14 +08:00
}
2020-11-27 13:13:26 +08:00
else if ( value ! = null & & _selectedValues = = null )
{
_selectedValues = value ;
2020-06-14 18:54:14 +08:00
2020-11-27 13:13:26 +08:00
_ = OnValuesChangeAsync ( value ) ;
}
else if ( value = = null & & _selectedValues ! = null )
{
_selectedValues = default ;
2020-06-14 18:54:14 +08:00
2020-11-27 13:13:26 +08:00
_ = OnValuesChangeAsync ( default ) ;
}
2021-05-06 13:42:19 +08:00
if ( _isNotifyFieldChanged & & ( Form ? . ValidateOnChange = = true ) )
{
EditContext ? . NotifyFieldChanged ( FieldIdentifier ) ;
}
2020-10-25 14:59:10 +08:00
}
}
2021-07-28 22:40:37 +08:00
/// <summary>
/// Used for the two-way binding.
/// </summary>
[Parameter] public override EventCallback < TItemValue > ValueChanged { get ; set ; }
/// <summary>
/// The name of the property to be used for the value.
/// </summary>
2020-11-27 13:13:26 +08:00
[Parameter]
2021-07-28 22:40:37 +08:00
public string ValueName
2020-06-14 18:54:14 +08:00
{
2021-07-28 22:40:37 +08:00
get = > _valueName ;
2020-11-27 13:13:26 +08:00
set
2020-06-14 18:54:14 +08:00
{
2021-07-28 22:40:37 +08:00
_getValue = string . IsNullOrWhiteSpace ( value ) ? null : PathHelper . GetDelegate < TItem , TItemValue > ( value ) ;
_setValue = string . IsNullOrWhiteSpace ( value ) ? null : PathHelper . SetDelegate < TItem , TItemValue > ( value ) ;
_valueName = value ;
2020-06-14 18:54:14 +08:00
}
}
2021-07-28 22:40:37 +08:00
/// <summary>
/// Used for the two-way binding.
/// </summary>
2021-09-05 18:13:38 +08:00
[Parameter] public EventCallback < IEnumerable < TItemValue > > ValuesChanged { get ; set ; }
2020-11-27 13:13:26 +08:00
#endregion Parameters
2021-09-09 12:56:11 +08:00
[Inject] private IDomEventListener DomEventListener { get ; set ; }
2021-04-08 22:17:44 +08:00
2020-11-27 13:13:26 +08:00
#region Properties
private const string ClassPrefix = "ant-select" ;
private const string DefaultWidth = "width: 100%;" ;
/// <summary>
/// Determines if SelectOptions has any selected items
/// </summary>
/// <returns>true if SelectOptions has any selected Items, otherwise false</returns>
internal bool HasValue
2020-06-14 18:54:14 +08:00
{
2021-04-16 18:50:36 +08:00
get = > SelectedOptionItems . Any ( ) | | ( AddedTags ? . Any ( ) ? ? false ) ;
2020-06-14 18:54:14 +08:00
}
2020-11-27 13:13:26 +08:00
/// <summary>
/// Returns a true/false if the placeholder should be displayed or not.
/// </summary>
/// <returns>true if SelectOptions has no values and the searchValue is empty; otherwise false </returns>
protected bool ShowPlaceholder
2020-06-14 18:54:14 +08:00
{
2020-11-27 13:13:26 +08:00
get
2020-06-14 18:54:14 +08:00
{
2020-11-27 13:13:26 +08:00
return ! HasValue & & string . IsNullOrEmpty ( _searchValue ) ;
2020-06-14 18:54:14 +08:00
}
}
2020-06-28 12:54:58 +08:00
2020-11-27 13:13:26 +08:00
/// <summary>
/// Returns the value of EnableSearch parameter
/// </summary>
/// <returns>true if search is enabled</returns>
internal bool IsSearchEnabled
2020-06-28 12:54:58 +08:00
{
2021-02-04 23:40:47 +08:00
get = > EnableSearch | | SelectMode = = SelectMode . Tags ;
2020-06-28 12:54:58 +08:00
}
2020-06-14 18:54:14 +08:00
2020-11-27 13:13:26 +08:00
/// <summary>
/// Indicates if the GroupName is used. When this value is True, the SelectOptions will be rendered in group mode.
/// </summary>
internal bool IsGroupingEnabled
2020-06-14 18:54:14 +08:00
{
2020-11-27 13:13:26 +08:00
get = > ! string . IsNullOrWhiteSpace ( GroupName ) ;
}
2020-06-14 18:54:14 +08:00
2021-03-04 23:16:24 +08:00
internal ElementReference DropDownRef = > _dropDown . GetOverlayComponent ( ) . Ref ;
2020-11-27 13:13:26 +08:00
internal SelectMode SelectMode = > Mode . ToSelectMode ( ) ;
internal bool Focused { get ; private set ; }
private string _searchValue = string . Empty ;
2021-04-08 22:17:44 +08:00
private string _prevSearchValue = string . Empty ;
2020-11-27 13:13:26 +08:00
private string _dropdownStyle = string . Empty ;
private TItemValue _selectedValue ;
private TItemValue _defaultValue ;
private bool _defaultValueIsNotNull ;
private IEnumerable < TItem > _datasource ;
private IEnumerable < TItemValue > _selectedValues ;
private IEnumerable < TItemValue > _defaultValues ;
private bool _defaultValuesHasItems ;
private bool _isInitialized ;
2021-09-05 18:13:38 +08:00
private bool _afterFirstRender ;
2021-07-20 14:09:53 +08:00
private bool _optionsHasInitialized ;
2021-02-04 23:40:47 +08:00
private bool _defaultValueApplied ;
2021-02-09 00:31:44 +08:00
private bool _defaultActiveFirstOptionApplied ;
2020-11-28 10:02:35 +08:00
private bool _waittingStateChange ;
2021-04-16 18:50:36 +08:00
private bool _isValueEnum ;
2020-11-27 13:13:26 +08:00
internal ElementReference _inputRef ;
protected OverlayTrigger _dropDown ;
2021-02-04 23:40:47 +08:00
protected SelectContent < TItemValue , TItem > _selectContent ;
2021-03-12 17:02:11 +08:00
private bool _isToken ;
2021-02-04 23:40:47 +08:00
private SelectOptionItem < TItemValue , TItem > _activeOption ;
2021-02-09 00:31:44 +08:00
private bool _defaultActiveFirstOption ;
2020-07-29 12:44:16 +08:00
2020-11-27 13:13:26 +08:00
internal HashSet < SelectOptionItem < TItemValue , TItem > > SelectOptionItems { get ; } = new HashSet < SelectOptionItem < TItemValue , TItem > > ( ) ;
2021-02-04 23:40:47 +08:00
internal List < SelectOptionItem < TItemValue , TItem > > SelectedOptionItems { get ; } = new List < SelectOptionItem < TItemValue , TItem > > ( ) ;
internal List < SelectOptionItem < TItemValue , TItem > > AddedTags { get ; } = new List < SelectOptionItem < TItemValue , TItem > > ( ) ;
internal SelectOptionItem < TItemValue , TItem > CustomTagSelectOptionItem { get ; set ; }
2021-09-05 18:13:38 +08:00
2021-03-02 22:01:57 +08:00
/// <summary>
2021-03-12 17:02:11 +08:00
/// Currently active (highlighted) option.
2021-03-02 22:01:57 +08:00
/// It does not have to be equal to selected option.
/// </summary>
2021-02-04 23:40:47 +08:00
internal SelectOptionItem < TItemValue , TItem > ActiveOption
{
get { return _activeOption ; }
2021-03-12 17:02:11 +08:00
set
{
2021-02-04 23:40:47 +08:00
if ( _activeOption ! = value )
{
if ( _activeOption ! = null & & _activeOption . IsActive )
_activeOption . IsActive = false ;
_activeOption = value ;
if ( _activeOption ! = null & & ! _activeOption . IsActive )
_activeOption . IsActive = true ;
}
}
}
2020-07-29 12:44:16 +08:00
2021-02-28 00:13:13 +08:00
private string _labelName ;
2021-09-05 18:13:38 +08:00
internal Func < TItem , string > _getLabel ;
2021-02-28 00:13:13 +08:00
private Action < TItem , string > _setLabel ;
private string _groupName = string . Empty ;
private Func < TItem , string > _getGroup ;
2021-03-02 22:01:57 +08:00
2021-02-28 00:13:13 +08:00
private string _disabledName ;
private Func < TItem , bool > _getDisabled ;
private string _valueName ;
2021-09-05 18:13:38 +08:00
internal Func < TItem , TItemValue > _getValue ;
2021-02-28 00:13:13 +08:00
private Action < TItem , TItemValue > _setValue ;
2021-03-04 23:16:24 +08:00
private bool _disableSubmitFormOnEnter ;
2021-04-08 22:17:44 +08:00
private bool _showArrowIcon = true ;
2021-05-06 13:42:19 +08:00
private Expression < Func < TItemValue > > _valueExpression ;
2021-02-28 00:13:13 +08:00
2020-11-27 13:13:26 +08:00
#endregion Properties
2021-02-28 00:13:13 +08:00
2021-02-04 23:40:47 +08:00
private static bool IsSimpleType ( Type type )
{
return
type . IsPrimitive | |
new Type [ ] {
typeof ( string ) ,
typeof ( decimal ) ,
typeof ( DateTime ) ,
typeof ( DateTimeOffset ) ,
typeof ( TimeSpan ) ,
typeof ( Guid )
} . Contains ( type ) | |
type . IsEnum | |
Convert . GetTypeCode ( type ) ! = TypeCode . Object | |
( type . IsGenericType & & type . GetGenericTypeDefinition ( ) = = typeof ( Nullable < > ) & & IsSimpleType ( type . GetGenericArguments ( ) [ 0 ] ) )
;
}
2021-04-13 13:12:18 +08:00
internal bool IsDropdownShown ( ) = > _dropDown . IsOverlayShow ( ) ;
2020-11-27 13:13:26 +08:00
protected override void OnInitialized ( )
{
2021-07-28 15:38:16 +08:00
if ( SelectOptions = = null & & typeof ( TItemValue ) ! = typeof ( TItem ) & & string . IsNullOrWhiteSpace ( ValueName ) )
2021-06-13 00:39:31 +08:00
{
throw new ArgumentNullException ( nameof ( ValueName ) ) ;
}
2020-11-27 13:13:26 +08:00
SetClassMap ( ) ;
2020-07-29 12:44:16 +08:00
2020-11-27 13:13:26 +08:00
if ( string . IsNullOrWhiteSpace ( Style ) )
Style = DefaultWidth ;
2020-07-29 12:44:16 +08:00
2021-02-04 23:40:47 +08:00
if ( ! _isInitialized )
2021-04-08 22:17:44 +08:00
{
2021-04-16 18:50:36 +08:00
_isValueEnum = typeof ( TItemValue ) . IsEnum ;
2021-04-08 22:17:44 +08:00
if ( ! _showArrowIconChanged & & SelectMode ! = SelectMode . Default )
_showArrowIcon = SuffixIcon ! = null ;
}
2020-11-27 13:13:26 +08:00
_isInitialized = true ;
2020-06-14 18:54:14 +08:00
base . OnInitialized ( ) ;
}
2021-07-20 14:09:53 +08:00
protected override void OnParametersSet ( )
2020-09-19 09:15:16 +08:00
{
2020-11-27 13:13:26 +08:00
if ( SelectOptions = = null )
2021-07-20 14:09:53 +08:00
{
2021-09-05 18:13:38 +08:00
if ( ! _optionsHasInitialized | | _dataSourceHasChanged )
{
CreateDeleteSelectOptions ( ) ;
_optionsHasInitialized = true ;
_dataSourceHasChanged = false ;
}
2021-07-20 14:09:53 +08:00
}
2020-09-19 09:15:16 +08:00
2021-07-20 14:09:53 +08:00
if ( _valueHasChanged & & _optionsHasInitialized )
2021-07-11 18:41:27 +08:00
{
_valueHasChanged = false ;
OnValueChange ( _selectedValue ) ;
if ( Form ? . ValidateOnChange = = true )
{
EditContext ? . NotifyFieldChanged ( FieldIdentifier ) ;
}
}
2021-07-20 14:09:53 +08:00
base . OnParametersSet ( ) ;
2020-09-19 09:15:16 +08:00
}
2021-09-05 18:13:38 +08:00
/// <summary>
/// Used only when ChildElement SelectOptions is used.
/// Will run this process if after initalization an item
/// is added that is also marked as selected.
/// </summary>
/// <returns></returns>
internal async Task ProcessSelectedSelectOptions ( )
{
if ( _isInitialized & & _afterFirstRender )
{
if ( Mode = = "default" )
{
if ( LastValueBeforeReset is not null )
{
OnValueChange ( LastValueBeforeReset ) ;
LastValueBeforeReset = default ;
}
else
{
OnValueChange ( Value ) ;
}
}
else
{
await OnValuesChangeAsync ( Values ) ;
}
}
}
2020-09-20 10:42:42 +08:00
protected override async Task OnAfterRenderAsync ( bool firstRender )
2020-06-14 18:54:14 +08:00
{
2021-07-20 14:09:53 +08:00
if ( SelectOptions ! = null )
{
_optionsHasInitialized = true ;
}
2020-11-27 13:13:26 +08:00
if ( firstRender )
2020-09-20 10:42:42 +08:00
{
2020-11-27 13:13:26 +08:00
await SetInitialValuesAsync ( ) ;
2021-09-09 12:56:11 +08:00
DomEventListener . AddShared < JsonElement > ( "window" , "resize" , OnWindowResize ) ;
2020-10-25 14:59:10 +08:00
await SetDropdownStyleAsync ( ) ;
2020-06-14 18:54:14 +08:00
2021-02-04 23:40:47 +08:00
_defaultValueApplied = ! ( _defaultValueIsNotNull | | _defaultValuesHasItems ) ;
2021-03-02 22:01:57 +08:00
_defaultActiveFirstOptionApplied = ! _defaultActiveFirstOption ;
2021-02-04 23:40:47 +08:00
}
2020-06-14 18:54:14 +08:00
2021-02-09 00:31:44 +08:00
if ( ! _defaultValueApplied | | ! _defaultActiveFirstOptionApplied )
2020-11-27 13:13:26 +08:00
{
2021-02-04 23:40:47 +08:00
if ( SelectMode = = SelectMode . Default )
2020-11-27 13:13:26 +08:00
{
2021-02-04 23:40:47 +08:00
if ( _defaultValueIsNotNull & & ! HasValue & & SelectOptionItems . Any ( )
2021-02-09 00:31:44 +08:00
| | DefaultActiveFirstOption & & ! HasValue & & SelectOptionItems . Any ( ) )
2021-02-04 23:40:47 +08:00
{
await TrySetDefaultValueAsync ( ) ;
}
2020-11-27 13:13:26 +08:00
}
2021-02-04 23:40:47 +08:00
else
2020-11-27 13:13:26 +08:00
{
2021-02-04 23:40:47 +08:00
if ( _defaultValuesHasItems & & ! HasValue & & SelectOptionItems . Any ( )
2021-02-09 00:31:44 +08:00
| | DefaultActiveFirstOption & & ! HasValue & & SelectOptionItems . Any ( ) )
2021-02-04 23:40:47 +08:00
{
await TrySetDefaultValuesAsync ( ) ;
}
2020-11-27 13:13:26 +08:00
}
2020-06-14 18:54:14 +08:00
}
2020-11-27 13:13:26 +08:00
2021-02-04 23:40:47 +08:00
if ( _isInitialized & & SelectOptions = = null )
2021-07-20 14:09:53 +08:00
{
2021-02-04 23:40:47 +08:00
CreateDeleteSelectOptions ( ) ;
2021-07-20 14:09:53 +08:00
_optionsHasInitialized = true ;
}
2021-02-04 23:40:47 +08:00
2020-11-28 10:02:35 +08:00
if ( _waittingStateChange )
{
_waittingStateChange = false ;
StateHasChanged ( ) ;
}
2020-11-27 13:13:26 +08:00
await base . OnAfterRenderAsync ( firstRender ) ;
2021-09-05 18:13:38 +08:00
_afterFirstRender = true ;
2020-06-14 18:54:14 +08:00
}
2021-04-08 22:17:44 +08:00
protected override void Dispose ( bool disposing )
{
2021-09-09 12:56:11 +08:00
DomEventListener . Dispose ( ) ;
2021-04-08 22:17:44 +08:00
base . Dispose ( disposing ) ;
}
protected async void OnWindowResize ( JsonElement element )
{
await SetDropdownStyleAsync ( ) ;
}
2020-11-27 13:13:26 +08:00
/// <summary>
/// Create or delete SelectOption when the datasource changed
/// </summary>
private void CreateDeleteSelectOptions ( )
2020-06-14 18:54:14 +08:00
{
2021-07-28 22:40:37 +08:00
2020-11-27 13:13:26 +08:00
if ( _datasource = = null )
return ;
2020-06-14 18:54:14 +08:00
2021-02-04 23:40:47 +08:00
Dictionary < TItem , SelectOptionItem < TItemValue , TItem > > dataStoreToSelectOptionItemsMatch = new ( ) ;
2020-11-27 13:13:26 +08:00
// Compare items of SelectOptions and the datastore
if ( SelectOptionItems . Any ( ) )
2020-09-09 22:12:12 +08:00
{
2020-11-27 13:13:26 +08:00
// Delete items from SelectOptions if it is no longer in the datastore
for ( var i = SelectOptionItems . Count - 1 ; i > = 0 ; i - - )
2020-06-14 18:54:14 +08:00
{
2020-11-27 13:13:26 +08:00
var selectOption = SelectOptionItems . ElementAt ( i ) ;
2021-02-04 23:40:47 +08:00
if ( ! selectOption . IsAddedTag )
2020-11-27 13:13:26 +08:00
{
2021-02-04 23:40:47 +08:00
var exists = _datasource . Where ( x = > x . Equals ( selectOption . Item ) ) . FirstOrDefault ( ) ;
if ( exists is null )
{
SelectOptionItems . Remove ( selectOption ) ;
2021-04-04 01:53:25 +08:00
if ( selectOption . IsSelected )
SelectedOptionItems . Remove ( selectOption ) ;
2021-02-04 23:40:47 +08:00
}
else
dataStoreToSelectOptionItemsMatch . Add ( exists , selectOption ) ;
2020-11-27 13:13:26 +08:00
}
2020-06-14 18:54:14 +08:00
}
}
2020-10-25 14:59:10 +08:00
2021-04-04 01:53:25 +08:00
//A simple approach to avoid unnecessary scanning through _selectedValues once
//all of SelectOptionItem where already marked as selected
int processedSelectedCount = 0 ;
if ( SelectMode = = SelectMode . Default & & _selectedValue ! = null )
processedSelectedCount = 1 ;
else if ( SelectMode ! = SelectMode . Default & & _selectedValues ! = null )
processedSelectedCount = _selectedValues . Count ( ) ;
2020-11-27 13:13:26 +08:00
foreach ( var item in _datasource )
{
2021-06-13 00:39:31 +08:00
TItemValue value = _getValue = = null ? THelper . ChangeType < TItemValue > ( item ) : _getValue ( item ) ;
2020-06-14 18:54:14 +08:00
2020-11-27 13:13:26 +08:00
var exists = false ;
2021-02-04 23:40:47 +08:00
SelectOptionItem < TItemValue , TItem > selectOption ;
2020-11-27 13:13:26 +08:00
SelectOptionItem < TItemValue , TItem > updateSelectOption = null ;
2020-07-29 12:44:16 +08:00
2021-02-04 23:40:47 +08:00
if ( dataStoreToSelectOptionItemsMatch . TryGetValue ( item , out selectOption ) )
2020-06-14 18:54:14 +08:00
{
2020-11-27 13:13:26 +08:00
var result = EqualityComparer < TItemValue > . Default . Equals ( selectOption . Value , value ) ;
if ( result )
{
exists = true ;
updateSelectOption = selectOption ;
}
2020-06-14 18:54:14 +08:00
}
2021-02-04 23:40:47 +08:00
var disabled = false ;
var groupName = string . Empty ;
2021-07-26 13:16:19 +08:00
var label = _getLabel = = null ? GetLabel ( item ) : _getLabel ( item ) ;
2020-11-27 13:13:26 +08:00
2021-04-04 01:53:25 +08:00
bool isSelected = false ;
if ( processedSelectedCount > 0 )
{
if ( SelectMode = = SelectMode . Default )
2021-07-28 19:41:38 +08:00
isSelected = ReferenceEquals ( value , _selectedValue ) | | value ? . Equals ( _selectedValue ) = = true ;
2021-04-04 01:53:25 +08:00
else
isSelected = _selectedValues . Contains ( value ) ;
}
2021-02-04 23:40:47 +08:00
if ( ! string . IsNullOrWhiteSpace ( DisabledName ) )
2021-02-28 00:13:13 +08:00
disabled = _getDisabled ( item ) ;
2020-11-27 13:13:26 +08:00
2021-02-04 23:40:47 +08:00
if ( ! string . IsNullOrWhiteSpace ( GroupName ) )
2021-02-28 00:13:13 +08:00
groupName = _getGroup ( item ) ;
2020-11-27 13:13:26 +08:00
2021-02-04 23:40:47 +08:00
if ( ! exists )
{
2020-11-27 13:13:26 +08:00
var newItem = new SelectOptionItem < TItemValue , TItem >
{
Label = label ,
GroupName = groupName ,
IsDisabled = disabled ,
Item = item ,
2021-04-04 01:53:25 +08:00
Value = value ,
IsSelected = isSelected ,
IsHidden = isSelected & & HideSelected
2020-11-27 13:13:26 +08:00
} ;
SelectOptionItems . Add ( newItem ) ;
2021-04-04 01:53:25 +08:00
if ( isSelected )
{
processedSelectedCount - - ;
SelectedOptionItems . Add ( newItem ) ;
}
2020-11-27 13:13:26 +08:00
}
else if ( exists & & ! IgnoreItemChanges )
2020-06-14 18:54:14 +08:00
{
2021-02-04 23:40:47 +08:00
updateSelectOption . Label = label ;
updateSelectOption . IsDisabled = disabled ;
updateSelectOption . GroupName = groupName ;
2021-04-04 01:53:25 +08:00
updateSelectOption . IsHidden = isSelected & & HideSelected ;
if ( isSelected )
{
if ( ! updateSelectOption . IsSelected )
{
updateSelectOption . IsSelected = isSelected ;
SelectedOptionItems . Add ( updateSelectOption ) ;
}
processedSelectedCount - - ;
}
2020-06-14 18:54:14 +08:00
}
}
}
2020-11-27 13:13:26 +08:00
/// <summary>
/// Sorted list of SelectOptionItems
/// </summary>
protected internal IEnumerable < SelectOptionItem < TItemValue , TItem > > SortedSelectOptionItems
2020-06-14 18:54:14 +08:00
{
2020-11-27 13:13:26 +08:00
get
2020-06-14 18:54:14 +08:00
{
2020-11-27 13:13:26 +08:00
var selectOption = SelectOptionItems ;
2020-06-14 18:54:14 +08:00
2020-11-27 13:13:26 +08:00
if ( SortByGroup = = SortDirection . Ascending & & SortByLabel = = SortDirection . None )
2020-06-14 18:54:14 +08:00
{
2020-11-27 13:13:26 +08:00
return selectOption . OrderBy ( g = > g . GroupName ) ;
2020-06-14 18:54:14 +08:00
}
2020-11-27 13:13:26 +08:00
else if ( SortByGroup = = SortDirection . Descending & & SortByLabel = = SortDirection . None )
2020-06-14 18:54:14 +08:00
{
2020-11-27 13:13:26 +08:00
return selectOption . OrderByDescending ( g = > g . GroupName ) ;
2020-06-14 18:54:14 +08:00
}
2020-11-27 13:13:26 +08:00
else if ( SortByGroup = = SortDirection . None & & SortByLabel = = SortDirection . Ascending )
2020-06-14 18:54:14 +08:00
{
2020-11-27 13:13:26 +08:00
return selectOption . OrderBy ( l = > l . Label ) ;
}
else if ( SortByGroup = = SortDirection . None & & SortByLabel = = SortDirection . Descending )
{
return selectOption . OrderByDescending ( l = > l . Label ) ;
}
else if ( SortByGroup = = SortDirection . Ascending & & SortByLabel = = SortDirection . Ascending )
{
return selectOption . OrderBy ( g = > g . GroupName ) . ThenBy ( l = > l . Label ) ;
}
else if ( SortByGroup = = SortDirection . Ascending & & SortByLabel = = SortDirection . Descending )
{
return selectOption . OrderBy ( g = > g . GroupName ) . OrderByDescending ( l = > l . Label ) ;
}
else if ( SortByGroup = = SortDirection . Descending & & SortByLabel = = SortDirection . Ascending )
{
return selectOption . OrderByDescending ( g = > g . GroupName ) . ThenBy ( l = > l . Label ) ;
}
else if ( SortByGroup = = SortDirection . Descending & & SortByLabel = = SortDirection . Descending )
{
return selectOption . OrderByDescending ( g = > g . GroupName ) . OrderByDescending ( l = > l . Label ) ;
}
2021-02-04 23:40:47 +08:00
else if ( SelectMode = = SelectMode . Tags )
{
if ( CustomTagSelectOptionItem ! = null )
{
return selectOption . OrderByDescending ( g = > g . Equals ( CustomTagSelectOptionItem ) ) ;
}
return selectOption ;
}
2020-11-27 13:13:26 +08:00
else
{
return selectOption ;
2020-06-14 18:54:14 +08:00
}
}
}
2020-11-27 13:13:26 +08:00
/// <summary>
/// Sets the CSS classes to change the visual style
/// </summary>
protected void SetClassMap ( )
2020-06-14 18:54:14 +08:00
{
2020-11-27 13:13:26 +08:00
ClassMapper . Clear ( )
. Add ( $"{ClassPrefix}" )
. If ( $"{ClassPrefix}-open" , ( ) = > _dropDown ? . IsOverlayShow ( ) ? ? false )
. If ( $"{ClassPrefix}-focused" , ( ) = > Focused )
. If ( $"{ClassPrefix}-single" , ( ) = > SelectMode = = SelectMode . Default )
. If ( $"{ClassPrefix}-multiple" , ( ) = > SelectMode ! = SelectMode . Default )
. If ( $"{ClassPrefix}-sm" , ( ) = > Size = = AntSizeLDSType . Small )
. If ( $"{ClassPrefix}-lg" , ( ) = > Size = = AntSizeLDSType . Large )
. If ( $"{ClassPrefix}-borderless" , ( ) = > ! Bordered )
. If ( $"{ClassPrefix}-show-arrow" , ( ) = > ShowArrowIcon )
2021-02-04 23:40:47 +08:00
. If ( $"{ClassPrefix}-show-search" , ( ) = > EnableSearch | | SelectMode = = SelectMode . Tags )
2020-11-27 13:13:26 +08:00
. If ( $"{ClassPrefix}-bordered" , ( ) = > Bordered )
. If ( $"{ClassPrefix}-loading" , ( ) = > Loading )
2021-03-12 17:02:11 +08:00
. If ( $"{ClassPrefix}-disabled" , ( ) = > Disabled )
. If ( $"{ClassPrefix}-rtl" , ( ) = > RTL ) ;
2020-11-27 13:13:26 +08:00
}
/// <summary>
/// Returns True if the parameter IsHidden is set to true for all entries in the SelectOptions list
/// </summary>
/// <returns>true if all items are set to IsHidden(true)</returns>
protected bool AllOptionsHidden ( )
{
2021-02-04 23:40:47 +08:00
if ( AddedTags . Count > 0 )
return SelectOptionItems . All ( x = > x . IsHidden ) & & AddedTags . All ( x = > x . IsHidden ) ;
else
return SelectOptionItems . All ( x = > x . IsHidden ) ;
2020-11-27 13:13:26 +08:00
}
/// <summary>
/// Gets the BoundingClientRect of Ref (JSInvoke) and set the min-width and width in px.
/// </summary>
protected async Task SetDropdownStyleAsync ( )
{
2021-04-08 22:17:44 +08:00
string maxWidth = "" , minWidth = "" , definedWidth = "" ;
2020-11-27 13:13:26 +08:00
var domRect = await JsInvokeAsync < DomRect > ( JSInteropConstants . GetBoundingClientRect , Ref ) ;
2021-04-17 22:14:01 +08:00
var width = domRect . Width . ToString ( "0.00" , System . Globalization . CultureInfo . InvariantCulture ) ;
2021-04-08 22:17:44 +08:00
minWidth = $"min-width: {width}px;" ;
if ( DropdownMatchSelectWidth . IsT0 & & DropdownMatchSelectWidth . AsT0 )
{
definedWidth = $"width: {width}px;" ;
}
else if ( DropdownMatchSelectWidth . IsT1 )
{
definedWidth = $"width: {DropdownMatchSelectWidth.AsT1};" ;
}
if ( ! DropdownMaxWidth . Equals ( "auto" , StringComparison . CurrentCultureIgnoreCase ) )
maxWidth = $"max-width: {DropdownMaxWidth};" ;
_dropdownStyle = minWidth + definedWidth + maxWidth ;
2020-11-27 13:13:26 +08:00
}
protected async Task OnOverlayVisibleChangeAsync ( bool visible )
{
OnDropdownVisibleChange ? . Invoke ( visible ) ;
2020-06-14 18:54:14 +08:00
2020-11-27 13:13:26 +08:00
if ( visible )
2020-06-14 18:54:14 +08:00
{
2020-11-27 13:13:26 +08:00
await SetDropdownStyleAsync ( ) ;
await SetInputFocusAsync ( ) ;
2020-07-29 12:44:16 +08:00
2020-11-27 13:13:26 +08:00
await ScrollToFirstSelectedItemAsync ( ) ;
2020-06-14 18:54:14 +08:00
}
else
{
2020-11-27 13:13:26 +08:00
OnOverlayHide ( ) ;
2020-06-14 18:54:14 +08:00
}
}
2020-11-27 13:13:26 +08:00
protected void OnOverlayHide ( )
{
if ( ! IsSearchEnabled )
return ;
2020-06-14 18:54:14 +08:00
2020-11-27 13:13:26 +08:00
if ( ! AutoClearSearchValue )
return ;
2020-06-14 18:54:14 +08:00
2020-11-27 13:13:26 +08:00
if ( string . IsNullOrWhiteSpace ( _searchValue ) )
return ;
2020-10-25 14:59:10 +08:00
2020-11-27 13:13:26 +08:00
_searchValue = string . Empty ;
2021-04-08 22:17:44 +08:00
_prevSearchValue = string . Empty ;
2020-07-21 23:48:43 +08:00
2020-11-27 13:13:26 +08:00
if ( SelectMode ! = SelectMode . Default & & HideSelected )
2020-06-14 18:54:14 +08:00
{
2020-11-27 13:13:26 +08:00
SelectOptionItems . Where ( x = > ! x . IsSelected & & x . IsHidden )
. ForEach ( i = > i . IsHidden = false ) ;
2020-06-14 18:54:14 +08:00
}
2020-11-27 13:13:26 +08:00
else
2020-06-14 18:54:14 +08:00
{
2021-02-04 23:40:47 +08:00
if ( CustomTagSelectOptionItem is not null )
{
SelectOptionItems . Remove ( CustomTagSelectOptionItem ) ;
CustomTagSelectOptionItem = null ;
}
2020-11-27 13:13:26 +08:00
SelectOptionItems . Where ( x = > x . IsHidden )
. ForEach ( i = > i . IsHidden = false ) ;
2020-06-14 18:54:14 +08:00
}
2020-11-27 13:13:26 +08:00
}
2020-06-14 18:54:14 +08:00
2020-11-27 13:13:26 +08:00
/// <summary>
/// Scrolls to the item via JavaScript.
/// </summary>
/// <param name="element"></param>
/// <returns></returns>
private async Task ElementScrollIntoViewAsync ( ElementReference element )
{
2021-07-29 22:45:47 +08:00
await JsInvokeAsync ( JSInteropConstants . ScrollTo , element ) ;
2020-11-27 13:13:26 +08:00
}
/// <summary>
/// Close the overlay
/// </summary>
/// <returns></returns>
internal async Task CloseAsync ( )
{
await _dropDown . Hide ( true ) ;
}
/// <summary>
/// Called by the Form reset method
/// </summary>
internal override void ResetValue ( )
{
_ = ClearSelectedAsync ( ) ;
}
/// <summary>
/// The method is called every time if the user select/de-select a item by mouse or keyboard.
/// Don't change the IsSelected property outside of this function.
/// </summary>
protected internal async Task SetValueAsync ( SelectOptionItem < TItemValue , TItem > selectOption )
{
if ( selectOption = = null ) throw new ArgumentNullException ( nameof ( selectOption ) ) ;
if ( SelectMode = = SelectMode . Default )
{
2021-02-04 23:40:47 +08:00
if ( SelectedOptionItems . Count > 0 )
{
SelectedOptionItems [ 0 ] . IsSelected = false ;
SelectedOptionItems [ 0 ] = selectOption ;
}
else
2021-07-23 22:48:14 +08:00
{
2021-02-04 23:40:47 +08:00
SelectedOptionItems . Add ( selectOption ) ;
2021-07-23 22:48:14 +08:00
}
2020-11-27 13:13:26 +08:00
selectOption . IsSelected = true ;
await ValueChanged . InvokeAsync ( selectOption . Value ) ;
2021-02-04 23:40:47 +08:00
InvokeOnSelectedItemChanged ( selectOption ) ;
2020-11-27 13:13:26 +08:00
}
else
{
selectOption . IsSelected = ! selectOption . IsSelected ;
if ( selectOption . IsSelected )
{
if ( HideSelected & & ! selectOption . IsHidden )
selectOption . IsHidden = true ;
2021-02-04 23:40:47 +08:00
if ( IsSearchEnabled & & ! string . IsNullOrWhiteSpace ( _searchValue ) )
ClearSearch ( ) ;
2020-11-27 13:13:26 +08:00
2021-03-02 22:01:57 +08:00
if ( selectOption . IsAddedTag )
2021-02-04 23:40:47 +08:00
{
2021-02-24 00:01:00 +08:00
CustomTagSelectOptionItem = null ;
2021-02-04 23:40:47 +08:00
AddedTags . Add ( selectOption ) ;
SelectOptionItems . Add ( selectOption ) ;
2020-11-27 13:13:26 +08:00
}
}
else
{
if ( selectOption . IsHidden )
selectOption . IsHidden = false ;
2021-02-04 23:40:47 +08:00
if ( selectOption . IsAddedTag )
{
SelectOptionItems . Remove ( selectOption ) ;
SelectedOptionItems . Remove ( selectOption ) ;
if ( selectOption . IsAddedTag & & SelectOptions ! = null )
{
AddedTags . Remove ( selectOption ) ;
}
}
2021-04-13 13:12:18 +08:00
if ( IsResponsive )
await _selectContent . RemovedItem ( ) ;
2020-06-14 18:54:14 +08:00
}
2021-04-08 22:17:44 +08:00
if ( EnableSearch | | SelectMode = = SelectMode . Tags )
await SetInputFocusAsync ( ) ;
2021-02-04 23:40:47 +08:00
await InvokeValuesChanged ( selectOption ) ;
2020-11-27 13:13:26 +08:00
await UpdateOverlayPositionAsync ( ) ;
2020-06-14 18:54:14 +08:00
}
2020-11-27 13:13:26 +08:00
}
2020-09-09 22:12:12 +08:00
2020-11-27 13:13:26 +08:00
/// <summary>
/// Clears the selectValue(s) property and send the null(default) value back through the two-way binding.
/// </summary>
protected async Task ClearSelectedAsync ( )
{
if ( SelectMode = = SelectMode . Default )
{
OnSelectedItemChanged ? . Invoke ( default ) ;
await ValueChanged . InvokeAsync ( default ) ;
}
else
{
OnSelectedItemsChanged ? . Invoke ( default ) ;
await ValuesChanged . InvokeAsync ( default ) ;
}
2020-09-09 22:12:12 +08:00
}
2020-11-27 13:13:26 +08:00
/// <summary>
/// If DefaultActiveFirstItem is True, the first item which is not IsDisabled(True) is set as selected.
/// If there is no item it falls back to the clear method.
/// </summary>
private async Task SetDefaultActiveFirstItemAsync ( )
2020-09-09 22:12:12 +08:00
{
2020-11-27 13:13:26 +08:00
if ( SelectOptionItems . Any ( ) )
2020-09-09 22:12:12 +08:00
{
2020-11-27 13:13:26 +08:00
var firstEnabled = SortedSelectOptionItems . FirstOrDefault ( x = > ! x . IsDisabled ) ;
if ( firstEnabled ! = null )
{
firstEnabled . IsSelected = true ;
if ( HideSelected )
firstEnabled . IsHidden = true ;
2021-02-04 23:40:47 +08:00
if ( SelectedOptionItems . Count = = 0 )
2021-07-23 22:48:14 +08:00
{
2021-02-04 23:40:47 +08:00
SelectedOptionItems . Add ( firstEnabled ) ;
2021-07-23 22:48:14 +08:00
}
2021-02-04 23:40:47 +08:00
else
SelectedOptionItems [ 0 ] = firstEnabled ;
2020-11-27 13:13:26 +08:00
if ( SelectMode = = SelectMode . Default )
{
await ValueChanged . InvokeAsync ( firstEnabled . Value ) ;
if ( ! ValueChanged . HasDelegate )
await InvokeStateHasChangedAsync ( ) ;
}
else
{
await InvokeValuesChanged ( ) ;
if ( ! ValuesChanged . HasDelegate )
await InvokeStateHasChangedAsync ( ) ;
}
}
else
{
await ClearSelectedAsync ( ) ;
}
}
else
{
await ClearSelectedAsync ( ) ;
2020-09-09 22:12:12 +08:00
}
2021-02-09 00:31:44 +08:00
_defaultActiveFirstOptionApplied = true ;
2020-06-14 18:54:14 +08:00
}
2020-11-27 13:13:26 +08:00
/// <summary>
/// Method invoked by OnAfterRenderAsync if the Value is null(default) and
/// DefaultValue has a value or DefaultActiveFirstItem is True.
/// </summary>
private async Task TrySetDefaultValueAsync ( )
2020-06-14 18:54:14 +08:00
{
2020-11-27 13:13:26 +08:00
if ( _defaultValueIsNotNull )
2020-06-14 18:54:14 +08:00
{
2020-11-27 13:13:26 +08:00
var result = SelectOptionItems . FirstOrDefault ( x = > EqualityComparer < TItemValue > . Default . Equals ( x . Value , _defaultValue ) ) ;
if ( result ! = null & & ! result . IsDisabled )
2020-06-14 18:54:14 +08:00
{
2020-11-27 13:13:26 +08:00
result . IsSelected = true ;
if ( HideSelected )
result . IsHidden = true ;
2020-11-28 10:02:35 +08:00
_waittingStateChange = true ;
2021-02-04 23:40:47 +08:00
if ( SelectedOptionItems . Count = = 0 )
2021-07-23 22:48:14 +08:00
{
2021-02-04 23:40:47 +08:00
SelectedOptionItems . Add ( result ) ;
2021-07-23 22:48:14 +08:00
}
2021-02-04 23:40:47 +08:00
else
2021-03-12 17:02:11 +08:00
SelectedOptionItems [ 0 ] = result ;
2020-11-27 13:13:26 +08:00
await ValueChanged . InvokeAsync ( result . Value ) ;
}
else
{
await SetDefaultActiveFirstItemAsync ( ) ;
}
}
2021-02-09 00:31:44 +08:00
else if ( DefaultActiveFirstOption )
2020-11-27 13:13:26 +08:00
{
await SetDefaultActiveFirstItemAsync ( ) ;
}
else
{
await ClearSelectedAsync ( ) ;
}
2021-02-04 23:40:47 +08:00
_defaultValueApplied = true ;
2020-11-27 13:13:26 +08:00
}
/// <summary>
/// Method invoked by OnAfterRenderAsync if the Value is null(default) and
/// DefaultValues has a values or DefaultActiveFirstItem is True.
/// </summary>
private async Task TrySetDefaultValuesAsync ( )
{
if ( _defaultValuesHasItems )
{
foreach ( var defaultValue in _defaultValues )
{
var result = SelectOptionItems . FirstOrDefault ( x = > EqualityComparer < TItemValue > . Default . Equals ( x . Value , defaultValue ) ) ;
if ( result ! = null & & ! result . IsDisabled )
{
result . IsSelected = true ;
if ( HideSelected )
result . IsHidden = true ;
}
}
var anySelected = SelectOptionItems . Any ( x = > x . IsSelected ) ;
if ( ! anySelected )
{
2021-02-09 00:31:44 +08:00
if ( DefaultActiveFirstOption )
2020-11-27 13:13:26 +08:00
{
await SetDefaultActiveFirstItemAsync ( ) ;
}
else
{
await ClearSelectedAsync ( ) ;
}
}
else
{
2020-11-28 10:02:35 +08:00
_waittingStateChange = true ;
2020-11-27 13:13:26 +08:00
await InvokeValuesChanged ( ) ;
}
}
2021-02-09 00:31:44 +08:00
else if ( DefaultActiveFirstOption )
2020-11-27 13:13:26 +08:00
{
await SetDefaultActiveFirstItemAsync ( ) ;
}
else
{
await ClearSelectedAsync ( ) ;
}
2021-02-04 23:40:47 +08:00
_defaultValueApplied = true ;
2020-11-27 13:13:26 +08:00
}
/// <summary>
/// Sets the initial values after initialization, the method should only called once.
/// </summary>
private async Task SetInitialValuesAsync ( )
{
2021-02-04 23:40:47 +08:00
SelectedOptionItems . Clear ( ) ;
2020-11-27 13:13:26 +08:00
if ( SelectMode = = SelectMode . Default )
{
if ( _selectedValue ! = null )
{
var result = SelectOptionItems . FirstOrDefault ( x = > EqualityComparer < TItemValue > . Default . Equals ( x . Value , _selectedValue ) ) ;
if ( result ! = null )
{
if ( result . IsDisabled )
2020-06-14 18:54:14 +08:00
{
2020-11-27 13:13:26 +08:00
await TrySetDefaultValueAsync ( ) ;
return ;
2020-06-14 18:54:14 +08:00
}
2020-11-27 13:13:26 +08:00
result . IsSelected = true ;
2021-03-02 22:01:57 +08:00
ActiveOption = result ;
2020-11-27 13:13:26 +08:00
if ( HideSelected )
result . IsHidden = true ;
2021-02-04 23:40:47 +08:00
SelectedOptionItems . Add ( result ) ;
2020-11-27 13:13:26 +08:00
OnSelectedItemChanged ? . Invoke ( result . Item ) ;
await ValueChanged . InvokeAsync ( result . Value ) ;
}
}
}
else
{
if ( _selectedValues ! = null )
{
foreach ( var value in _selectedValues )
{
var result = SelectOptionItems . FirstOrDefault ( c = > EqualityComparer < TItemValue > . Default . Equals ( c . Value , value ) ) ;
if ( result ! = null & & ! result . IsDisabled )
2020-06-14 18:54:14 +08:00
{
2020-11-27 13:13:26 +08:00
result . IsSelected = true ;
if ( HideSelected )
result . IsHidden = true ;
2020-06-14 18:54:14 +08:00
}
2020-11-27 13:13:26 +08:00
}
var newSelectedValues = new List < TItemValue > ( ) ;
var newSelectedItems = new List < TItem > ( ) ;
SelectOptionItems . Where ( x = > x . IsSelected )
. ForEach ( i = >
2020-06-14 18:54:14 +08:00
{
2020-11-27 13:13:26 +08:00
newSelectedValues . Add ( i . Value ) ;
newSelectedItems . Add ( i . Item ) ;
2021-02-04 23:40:47 +08:00
SelectedOptionItems . Add ( i ) ;
2020-11-27 13:13:26 +08:00
} ) ;
OnSelectedItemsChanged ? . Invoke ( newSelectedItems ) ;
await ValuesChanged . InvokeAsync ( newSelectedValues ) ;
2020-06-14 18:54:14 +08:00
}
}
2020-11-27 13:13:26 +08:00
}
/// <summary>
/// Append a label item in tag mode
/// </summary>
/// <param name="label"></param>
2021-02-04 23:40:47 +08:00
private SelectOptionItem < TItemValue , TItem > AppendLabelValue ( string label )
2020-11-27 13:13:26 +08:00
{
if ( string . IsNullOrWhiteSpace ( label ) )
2021-02-04 23:40:47 +08:00
return default ;
SelectOptionItem < TItemValue , TItem > newItem = CreateSelectOptionItem ( label , true ) ;
SelectOptionItems . Add ( newItem ) ;
return newItem ;
}
2020-06-14 18:54:14 +08:00
2021-02-04 23:40:47 +08:00
/// <summary>
/// Creates the select option item. Mostly meant to create new tags, that is why IsAddedTag is hardcoded to true.
/// </summary>
/// <param name="label">Creation based on passed label</param>
/// <param name="isActive">if set to <c>true</c> [is active].</param>
/// <returns></returns>
private SelectOptionItem < TItemValue , TItem > CreateSelectOptionItem ( string label , bool isActive )
{
TItemValue value = CustomTagLabelToValue . Invoke ( label ) ;
TItem item ;
2021-09-05 18:13:38 +08:00
if ( IsTItemPrimitive )
2021-02-04 23:40:47 +08:00
{
item = ( TItem ) TypeDescriptor . GetConverter ( typeof ( TItem ) ) . ConvertFromInvariantString ( _searchValue ) ;
}
else
{
2021-06-13 00:39:31 +08:00
if ( _setValue = = null )
{
item = THelper . ChangeType < TItem > ( value ) ;
}
else
{
item = Activator . CreateInstance < TItem > ( ) ;
_setValue ( item , value ) ;
}
_setLabel ? . Invoke ( item , _searchValue ) ;
2021-02-04 23:40:47 +08:00
}
return new SelectOptionItem < TItemValue , TItem > ( ) { Label = label , Value = value , Item = item , IsActive = isActive , IsSelected = false , IsAddedTag = true } ;
2020-06-14 18:54:14 +08:00
}
2020-11-27 13:13:26 +08:00
/// <summary>
/// A separate method to invoke ValuesChanged and OnSelectedItemsChanged to reduce code duplicates.
/// </summary>
protected void InvokeOnSelectedItemChanged ( SelectOptionItem < TItemValue , TItem > selectOptionItem = null )
2020-06-14 18:54:14 +08:00
{
2020-11-27 13:13:26 +08:00
if ( selectOptionItem = = null )
{
OnSelectedItemsChanged ? . Invoke ( default ) ;
}
else
2020-06-14 18:54:14 +08:00
{
2020-11-27 13:13:26 +08:00
if ( LabelInValue & & SelectOptions ! = null )
2020-06-14 18:54:14 +08:00
{
2020-11-27 13:13:26 +08:00
// Embed the label into the value and return the result as json string.
var valueLabel = new Select . Internal . ValueLabel < TItemValue >
{
Value = selectOptionItem . Value ,
Label = selectOptionItem . Label
} ;
var json = JsonSerializer . Serialize ( valueLabel ) ;
OnSelectedItemChanged ? . Invoke ( ( TItem ) Convert . ChangeType ( json , typeof ( TItem ) ) ) ;
2020-06-14 18:54:14 +08:00
}
else
{
2020-11-27 13:13:26 +08:00
OnSelectedItemChanged ? . Invoke ( selectOptionItem . Item ) ;
2020-06-14 18:54:14 +08:00
}
2020-11-27 13:13:26 +08:00
}
}
2021-02-04 23:40:47 +08:00
protected async Task InvokeValuesChanged ( SelectOptionItem < TItemValue , TItem > newSelection = null )
2020-11-27 13:13:26 +08:00
{
2021-02-04 23:40:47 +08:00
List < TItemValue > newSelectedValues ;
if ( newSelection is null | | Values is null )
{
newSelectedValues = new List < TItemValue > ( ) ;
SelectedOptionItems . Clear ( ) ;
SelectOptionItems . Where ( x = > x . IsSelected )
. ForEach ( i = >
{
newSelectedValues . Add ( i . Value ) ;
SelectedOptionItems . Add ( i ) ;
} ) ;
}
else
{
newSelectedValues = Values . ToList ( ) ;
if ( newSelection . IsSelected )
2020-11-27 13:13:26 +08:00
{
2021-02-04 23:40:47 +08:00
newSelectedValues . Add ( newSelection . Value ) ;
SelectedOptionItems . Add ( newSelection ) ;
}
else
{
newSelectedValues . Remove ( newSelection . Value ) ;
SelectedOptionItems . Remove ( newSelection ) ;
}
}
if ( ValuesChanged . HasDelegate )
await ValuesChanged . InvokeAsync ( newSelectedValues ) ;
else
{
Values = newSelectedValues ;
StateHasChanged ( ) ;
}
2020-11-27 13:13:26 +08:00
}
2020-06-14 18:54:14 +08:00
2020-11-27 13:13:26 +08:00
/// <summary>
/// Inform the Overlay to update the position.
/// </summary>
internal async Task UpdateOverlayPositionAsync ( )
{
2021-04-04 14:01:38 +08:00
if ( _dropDown . Visible )
await _dropDown . GetOverlayComponent ( ) . UpdatePosition ( ) ;
2020-11-27 13:13:26 +08:00
}
2021-07-26 13:16:19 +08:00
protected virtual string GetLabel ( TItem item )
{
return item . ToString ( ) ;
}
2020-11-27 13:13:26 +08:00
#region Events
2021-09-05 18:13:38 +08:00
/// <summary>
/// When newly set Value is not found in SelectOptionItems, it is reset to
/// default. This property holds the value before reset. It may be needed
/// to be reaplied (for example when new Value is set at the same time
/// as new SelectOption is added, but Value in the component is set
/// before new SelectOptionItem has been created).
/// </summary>
internal TItemValue LastValueBeforeReset { get ; set ; }
2020-11-27 13:13:26 +08:00
/// <summary>
/// The Method is called every time if the value of the @bind-Value was changed by the two-way binding.
/// </summary>
protected override void OnValueChange ( TItemValue value )
{
2021-07-20 14:09:53 +08:00
if ( ! _optionsHasInitialized ) // This is important because otherwise the initial value is overwritten by the EventCallback of ValueChanged and would be NULL.
2020-11-27 13:13:26 +08:00
return ;
2020-07-29 12:44:16 +08:00
2021-04-16 18:50:36 +08:00
if ( ! _isValueEnum & & EqualityComparer < TItemValue > . Default . Equals ( value , default ) )
2020-11-27 13:13:26 +08:00
{
2021-03-12 17:02:11 +08:00
_ = InvokeAsync ( ( ) = > OnInputClearClickAsync ( new ( ) ) ) ;
2020-11-27 13:13:26 +08:00
return ;
2020-06-14 18:54:14 +08:00
}
2020-11-27 13:13:26 +08:00
var result = SelectOptionItems . FirstOrDefault ( x = > EqualityComparer < TItemValue > . Default . Equals ( x . Value , value ) ) ;
2021-03-05 20:43:30 +08:00
if ( result = = null )
2020-11-27 13:13:26 +08:00
{
2021-09-05 18:13:38 +08:00
if ( SelectOptions is not null )
{
LastValueBeforeReset = value ;
}
2021-03-05 20:43:30 +08:00
if ( ! AllowClear )
_ = TrySetDefaultValueAsync ( ) ;
2021-03-12 17:02:11 +08:00
else
2021-03-05 20:43:30 +08:00
{
2021-03-12 17:02:11 +08:00
//Reset value if not found - needed if value changed
2021-03-05 20:43:30 +08:00
//outside of the component
_ = InvokeAsync ( ( ) = > OnInputClearClickAsync ( new ( ) ) ) ;
}
2020-11-27 13:13:26 +08:00
return ;
}
if ( result . IsDisabled )
{
_ = TrySetDefaultValueAsync ( ) ;
return ;
}
result . IsSelected = true ;
2021-03-02 22:01:57 +08:00
EvaluateValueChangedOutsideComponent ( result , value ) ;
2020-11-27 13:13:26 +08:00
if ( HideSelected )
result . IsHidden = true ;
ValueChanged . InvokeAsync ( result . Value ) ;
}
2021-03-02 22:01:57 +08:00
/// <summary>
2021-03-12 17:02:11 +08:00
/// When bind-Value is changed outside of the component, then component
2021-03-02 22:01:57 +08:00
/// selected items have to be reselected according to new value passed.
/// </summary>
/// <param name="optionItem">The option item that has been selected.</param>
/// <param name="value">The value of the selected option item.</param>
private void EvaluateValueChangedOutsideComponent ( SelectOptionItem < TItemValue , TItem > optionItem , TItemValue value )
{
if ( ActiveOption ! = null & & ! ActiveOption . Value . Equals ( value ) )
{
ActiveOption . IsSelected = false ;
ActiveOption = optionItem ;
}
if ( SelectedOptionItems . Count > 0 )
{
if ( ! SelectedOptionItems [ 0 ] . Value . Equals ( value ) )
{
SelectedOptionItems [ 0 ] . IsSelected = false ;
SelectedOptionItems [ 0 ] = optionItem ;
}
}
else
2021-07-23 22:48:14 +08:00
{
2021-03-02 22:01:57 +08:00
SelectedOptionItems . Add ( optionItem ) ;
2021-07-23 22:48:14 +08:00
}
2021-03-02 22:01:57 +08:00
}
2020-11-27 13:13:26 +08:00
/// <summary>
/// The Method is called every time if the value of the @bind-Values was changed by the two-way binding.
/// </summary>
protected async Task OnValuesChangeAsync ( IEnumerable < TItemValue > values )
{
if ( ! _isInitialized ) // This is important because otherwise the initial value is overwritten by the EventCallback of ValueChanged and would be NULL.
return ;
if ( ! SelectOptionItems . Any ( ) )
return ;
if ( values = = null )
{
await ValuesChanged . InvokeAsync ( default ) ;
OnSelectedItemsChanged ? . Invoke ( default ) ;
return ;
}
2021-03-02 22:01:57 +08:00
EvaluateValuesChangedOutsideComponent ( values ) ;
if ( _dropDown . IsOverlayShow ( ) )
{
//A delay forces a refresh better than StateHasChanged().
//For example when a tag is added that is causing SelectContent to grow,
//this Task.Delay will actually allow to reposition the Overlay to match
//new size of SelectContent.
await Task . Delay ( 1 ) ;
await UpdateOverlayPositionAsync ( ) ;
}
OnSelectedItemsChanged ? . Invoke ( SelectedOptionItems . Select ( s = > s . Item ) ) ;
await ValuesChanged . InvokeAsync ( Values ) ;
}
/// <summary>
/// When bind-Values is changed outside of the component, then component
/// selected items have to be reselected according to new values passed.
/// TODO: (Perf) Consider using hash to identify if the passed values are different from currently selected.
/// </summary>
/// <param name="values">The values that need to be selected.</param>
private void EvaluateValuesChangedOutsideComponent ( IEnumerable < TItemValue > values )
{
2021-02-04 23:40:47 +08:00
var newSelectedItems = new List < TItem > ( ) ;
2021-02-09 00:31:44 +08:00
var deselectList = SelectedOptionItems . ToDictionary ( item = > item . Value , item = > item ) ;
2021-03-02 22:01:57 +08:00
foreach ( var value in values . ToList ( ) )
2020-11-27 13:13:26 +08:00
{
2021-03-02 22:01:57 +08:00
SelectOptionItem < TItemValue , TItem > result ;
if ( SelectMode = = SelectMode . Multiple )
2020-11-27 13:13:26 +08:00
{
2021-03-02 22:01:57 +08:00
result = SelectOptionItems . FirstOrDefault ( x = > ! x . IsSelected & & EqualityComparer < TItemValue > . Default . Equals ( x . Value , value ) ) ;
if ( result ! = null & & ! result . IsDisabled )
{
result . IsSelected = true ;
SelectedOptionItems . Add ( result ) ;
}
deselectList . Remove ( value ) ;
}
else
{
result = SelectOptionItems . FirstOrDefault ( x = > EqualityComparer < TItemValue > . Default . Equals ( x . Value , value ) ) ;
if ( result is null ) //tag delivered from outside, needs to be added to the list of options
{
result = CreateSelectOptionItem ( value . ToString ( ) , true ) ;
result . IsSelected = true ;
AddedTags . Add ( result ) ;
SelectOptionItems . Add ( result ) ;
SelectedOptionItems . Add ( result ) ;
}
else if ( result ! = null & & ! result . IsSelected & & ! result . IsDisabled )
{
result . IsSelected = true ;
SelectedOptionItems . Add ( result ) ;
}
deselectList . Remove ( value ) ;
2021-02-09 00:31:44 +08:00
}
}
if ( deselectList . Count > 0 )
{
foreach ( var item in deselectList )
{
item . Value . IsSelected = false ;
SelectedOptionItems . Remove ( item . Value ) ;
2021-03-02 22:01:57 +08:00
if ( item . Value . IsAddedTag )
{
SelectOptionItems . Remove ( item . Value ) ;
AddedTags . Remove ( item . Value ) ;
}
2020-11-27 13:13:26 +08:00
}
}
}
/// <summary>
/// Method is called via EventCallBack if the value of the Input element was changed by keyboard
/// </summary>
/// <param name="e">Contains the value of the Input element</param>
protected async void OnInputAsync ( ChangeEventArgs e )
{
if ( e = = null ) throw new ArgumentNullException ( nameof ( e ) ) ;
if ( ! IsSearchEnabled )
2020-06-14 18:54:14 +08:00
{
2020-11-27 13:13:26 +08:00
return ;
}
if ( ! _dropDown . IsOverlayShow ( ) )
{
await _dropDown . Show ( ) ;
}
2021-02-04 23:40:47 +08:00
bool containsToken = false ;
2021-04-08 22:17:44 +08:00
_prevSearchValue = _searchValue ;
2021-02-04 23:40:47 +08:00
if ( _isToken )
_searchValue = e . Value ? . ToString ( ) . TrimEnd ( TokenSeparators ) ;
else
{
_searchValue = e . Value ? . ToString ( ) ;
if ( TokenSeparators is not null & & TokenSeparators . Length > 0 )
{
containsToken = TokenSeparators . Any ( t = > _searchValue . Contains ( t ) ) ;
}
}
2020-11-27 13:13:26 +08:00
//_inputWidth = string.IsNullOrEmpty(_searchValue) ? InputDefaultWidth : $"{4 + _searchValue.Length * 8}px";
2021-02-04 23:40:47 +08:00
if ( containsToken )
{
await TokenizeSearchedPhrase ( _searchValue ) ;
}
if ( ! string . IsNullOrWhiteSpace ( _searchValue ) )
{
FilterOptionItems ( _searchValue ) ;
}
2021-03-12 17:02:11 +08:00
else
2020-11-27 13:13:26 +08:00
{
SelectOptionItems . Where ( x = > x . IsHidden ) . ForEach ( i = > i . IsHidden = false ) ;
2021-02-04 23:40:47 +08:00
if ( SelectMode = = SelectMode . Tags & & CustomTagSelectOptionItem is not null )
{
SelectOptionItems . Remove ( CustomTagSelectOptionItem ) ;
CustomTagSelectOptionItem = null ;
}
}
OnSearch ? . Invoke ( _searchValue ) ;
}
private async Task TokenizeSearchedPhrase ( string searchValue )
{
Dictionary < string , SelectOptionItem < TItemValue , TItem > > tokenItemMatch = new ( ) ;
tokenItemMatch = searchValue . Split ( TokenSeparators ) . Distinct ( ) . ToDictionary (
item = > item . Trim ( ) ,
_ = > default ( SelectOptionItem < TItemValue , TItem > ) ) ;
2020-11-27 13:13:26 +08:00
2021-02-04 23:40:47 +08:00
if ( SelectMode = = SelectMode . Tags )
{
List < SelectOptionItem < TItemValue , TItem > > selectOptionItems ;
if ( AddedTags . Count > 0 )
2020-06-14 18:54:14 +08:00
{
2021-02-04 23:40:47 +08:00
selectOptionItems = SelectOptionItems . ToList ( ) ;
selectOptionItems . AddRange ( AddedTags ) ;
}
else
selectOptionItems = SelectOptionItems . ToList ( ) ;
foreach ( var item in selectOptionItems )
{
if ( tokenItemMatch . ContainsKey ( item . Label ) )
{
await SetValueAsync ( item ) ;
tokenItemMatch [ item . Label ] = item ;
}
}
foreach ( KeyValuePair < string , SelectOptionItem < TItemValue , TItem > > tokenItem in tokenItemMatch )
{
if ( tokenItem . Value = = null )
{
tokenItemMatch [ tokenItem . Key ] = CreateSelectOptionItem ( tokenItem . Key , false ) ;
SelectOptionItems . Add ( tokenItemMatch [ tokenItem . Key ] ) ;
await SetValueAsync ( tokenItemMatch [ tokenItem . Key ] ) ;
}
}
}
else
{
foreach ( var item in SelectOptionItems )
{
if ( tokenItemMatch . ContainsKey ( item . Label ) )
{
await SetValueAsync ( item ) ;
}
}
}
if ( _dropDown . IsOverlayShow ( ) )
{
await CloseAsync ( ) ;
}
await SetInputBlurAsync ( ) ;
}
private void FilterOptionItems ( string searchValue )
{
if ( SelectMode ! = SelectMode . Tags )
{
bool firstDone = false ;
foreach ( var item in SelectOptionItems )
{
if ( item . Label . Contains ( searchValue , StringComparison . InvariantCultureIgnoreCase ) )
{
if ( ! firstDone )
2020-11-27 13:13:26 +08:00
{
2021-02-04 23:40:47 +08:00
item . IsActive = true ;
firstDone = true ;
}
else if ( item . IsActive )
{
item . IsActive = false ;
}
if ( item . IsHidden )
item . IsHidden = false ;
}
else
{
if ( ! item . IsHidden )
item . IsHidden = true ;
item . IsActive = false ;
}
2020-11-27 13:13:26 +08:00
}
2021-02-04 23:40:47 +08:00
}
else
{
FilterTagsOptionItems ( searchValue ) ;
}
}
2020-11-27 13:13:26 +08:00
2021-02-04 23:40:47 +08:00
private void FilterTagsOptionItems ( string searchValue )
{
SelectOptionItem < TItemValue , TItem > activeCanditate = null ;
List < SelectOptionItem < TItemValue , TItem > > selectOptionItems ;
if ( AddedTags . Count > 0 )
{
selectOptionItems = SelectOptionItems . ToList ( ) ;
selectOptionItems . AddRange ( AddedTags ) ;
2020-11-27 13:13:26 +08:00
}
2021-02-04 23:40:47 +08:00
else
selectOptionItems = SelectOptionItems . ToList ( ) ;
foreach ( var item in selectOptionItems )
2020-11-27 13:13:26 +08:00
{
2021-03-12 17:02:11 +08:00
if ( ! ( CustomTagSelectOptionItem ! = null & & item . Equals ( CustomTagSelectOptionItem ) ) ) //ignore if analyzing CustomTagSelectOptionItem
2021-02-04 23:40:47 +08:00
{
if ( item . Label . Contains ( searchValue , StringComparison . InvariantCultureIgnoreCase ) )
{
if ( item . Label . Equals ( searchValue , StringComparison . InvariantCulture ) )
{
activeCanditate = item ;
ActiveOption = item ;
item . IsActive = true ;
if ( CustomTagSelectOptionItem ! = null )
{
SelectOptionItems . Remove ( CustomTagSelectOptionItem ) ;
CustomTagSelectOptionItem = null ;
}
}
else if ( item . IsActive )
item . IsActive = false ;
if ( item . IsHidden )
item . IsHidden = false ;
}
else
{
if ( ! item . IsHidden )
item . IsHidden = true ;
item . IsActive = false ;
}
}
}
2020-11-27 13:13:26 +08:00
2021-02-04 23:40:47 +08:00
if ( activeCanditate is null )
{
//label has to be cast-able to value
TItemValue value = CustomTagLabelToValue . Invoke ( searchValue ) ;
if ( CustomTagSelectOptionItem is null )
{
CustomTagSelectOptionItem = CreateSelectOptionItem ( searchValue , true ) ;
SelectOptionItems . Add ( CustomTagSelectOptionItem ) ;
ActiveOption = CustomTagSelectOptionItem ;
}
else
{
CustomTagSelectOptionItem . Label = searchValue ;
CustomTagSelectOptionItem . Value = value ;
2021-09-05 18:13:38 +08:00
if ( IsTItemPrimitive )
2021-02-14 17:42:44 +08:00
{
CustomTagSelectOptionItem . Item = ( TItem ) TypeDescriptor . GetConverter ( typeof ( TItem ) ) . ConvertFromInvariantString ( _searchValue ) ;
}
else
{
2021-06-13 00:39:31 +08:00
_setLabel ? . Invoke ( CustomTagSelectOptionItem . Item , _searchValue ) ;
_setValue ? . Invoke ( CustomTagSelectOptionItem . Item , value ) ;
2021-02-14 17:42:44 +08:00
}
2021-02-04 23:40:47 +08:00
}
2020-11-27 13:13:26 +08:00
}
}
/// <summary>
/// Method is called via EventCallBack if the keyboard key is no longer pressed inside the Input element.
/// </summary>
/// <param name="e">Contains the key (combination) which was pressed inside the Input element</param>
protected async Task OnKeyUpAsync ( KeyboardEventArgs e )
{
if ( e = = null ) throw new ArgumentNullException ( nameof ( e ) ) ;
var key = e . Key . ToUpperInvariant ( ) ;
var overlayFirstOpen = false ;
2021-02-04 23:40:47 +08:00
if ( _isToken & & SelectMode = = SelectMode . Tags )
{
if ( ! _dropDown . IsOverlayShow ( ) )
return ;
if ( ! SelectOptionItems . Any ( ) )
return ;
SelectOptionItem < TItemValue , TItem > firstActive ;
if ( ActiveOption . IsAddedTag )
{
firstActive = SelectOptionItems . FirstOrDefault ( x = > x . Value . Equals ( ActiveOption . Value ) ) ;
if ( firstActive is null )
firstActive = ActiveOption ;
}
else
firstActive = ActiveOption ; // SelectOptionItems.FirstOrDefault(x => x.IsActive);
if ( AllOptionsHidden ( ) | | firstActive is null )
{
var newItem = AppendLabelValue ( _searchValue ) ;
await SetValueAsync ( newItem ) ;
OnCreateCustomTag ? . Invoke ( _searchValue ) ;
}
else if ( firstActive ! = null & & ! firstActive . IsDisabled )
{
CustomTagSelectOptionItem = null ;
await SetValueAsync ( firstActive ) ;
}
ClearSearch ( ) ;
return ;
}
2020-11-27 13:13:26 +08:00
if ( key = = "ENTER" )
{
if ( ! _dropDown . IsOverlayShow ( ) )
return ;
if ( ! SelectOptionItems . Any ( ) )
return ;
if ( SelectMode = = SelectMode . Default )
{
var firstActive = SelectOptionItems . FirstOrDefault ( x = > x . IsActive ) ;
if ( firstActive ! = null )
2020-06-14 18:54:14 +08:00
{
2020-11-27 13:13:26 +08:00
if ( ! firstActive . IsDisabled )
2020-06-14 18:54:14 +08:00
{
2020-11-27 13:13:26 +08:00
await SetValueAsync ( firstActive ) ;
await CloseAsync ( ) ;
2020-06-14 18:54:14 +08:00
}
}
2020-11-27 13:13:26 +08:00
return ;
}
if ( SelectMode = = SelectMode . Multiple )
{
if ( AllOptionsHidden ( ) )
return ;
var firstActive = SelectOptionItems . FirstOrDefault ( x = > x . IsActive ) ;
2021-02-04 23:40:47 +08:00
if ( firstActive ! = null & & ! firstActive . IsDisabled )
2020-06-14 18:54:14 +08:00
{
2021-02-04 23:40:47 +08:00
await SetValueAsync ( firstActive ) ;
ClearSearch ( ) ;
2020-06-14 18:54:14 +08:00
}
2020-11-27 13:13:26 +08:00
return ;
}
2020-06-14 18:54:14 +08:00
2020-11-27 13:13:26 +08:00
if ( SelectMode = = SelectMode . Tags )
{
2021-02-04 23:40:47 +08:00
SelectOptionItem < TItemValue , TItem > firstActive ;
if ( ActiveOption . IsAddedTag )
2020-06-14 18:54:14 +08:00
{
2021-02-04 23:40:47 +08:00
firstActive = SelectOptionItems . FirstOrDefault ( x = > x . Value . Equals ( ActiveOption . Value ) ) ;
if ( firstActive is null )
firstActive = ActiveOption ;
2020-06-14 18:54:14 +08:00
}
else
2021-02-04 23:40:47 +08:00
firstActive = ActiveOption ;
if ( AllOptionsHidden ( ) | | firstActive is null )
2020-06-14 18:54:14 +08:00
{
2021-02-04 23:40:47 +08:00
var newItem = AppendLabelValue ( _searchValue ) ;
await SetValueAsync ( newItem ) ;
OnCreateCustomTag ? . Invoke ( _searchValue ) ;
}
else if ( firstActive ! = null & & ! firstActive . IsDisabled )
{
await SetValueAsync ( firstActive ) ;
2020-06-14 18:54:14 +08:00
}
2020-07-29 12:44:16 +08:00
2020-11-27 13:13:26 +08:00
return ;
2020-06-14 18:54:14 +08:00
}
2020-11-27 13:13:26 +08:00
}
if ( key = = "ARROWUP" )
{
if ( ! _dropDown . IsOverlayShow ( ) & & ! Disabled )
2020-06-14 18:54:14 +08:00
{
2020-11-27 13:13:26 +08:00
await _dropDown . Show ( ) ;
overlayFirstOpen = true ;
}
if ( ! SelectOptionItems . Any ( ) )
return ;
var sortedSelectOptionItems = SortedSelectOptionItems . ToList ( ) ;
if ( overlayFirstOpen )
{
// Check if there is a selected item and set it as active
var currentSelected = sortedSelectOptionItems . FirstOrDefault ( x = > x . IsSelected ) ;
if ( currentSelected ! = null )
2020-06-14 18:54:14 +08:00
{
2020-11-27 13:13:26 +08:00
if ( currentSelected . IsActive )
return ;
sortedSelectOptionItems . Where ( x = > x . IsActive )
. ForEach ( i = > i . IsActive = false ) ;
currentSelected . IsActive = true ;
2021-02-04 23:40:47 +08:00
ActiveOption = currentSelected ;
2020-11-27 13:13:26 +08:00
// ToDo: Sometime the element does not scroll, you have to call the function twice
await ElementScrollIntoViewAsync ( currentSelected . Ref ) ;
await Task . Delay ( 1 ) ;
await ElementScrollIntoViewAsync ( currentSelected . Ref ) ;
2020-06-14 18:54:14 +08:00
}
2020-11-27 13:13:26 +08:00
return ;
}
var firstActive = sortedSelectOptionItems . FirstOrDefault ( x = > x . IsActive & & ! x . IsHidden & & ! x . IsDisabled ) ;
if ( firstActive = = null )
{
var firstOption = sortedSelectOptionItems . FirstOrDefault ( x = > ! x . IsHidden & & ! x . IsDisabled ) ;
if ( firstOption ! = null )
2020-06-14 18:54:14 +08:00
{
2020-11-27 13:13:26 +08:00
firstOption . IsActive = true ;
2021-02-04 23:40:47 +08:00
ActiveOption = firstOption ;
2020-11-27 13:13:26 +08:00
await ElementScrollIntoViewAsync ( firstOption . Ref ) ;
2020-06-14 18:54:14 +08:00
}
2020-11-27 13:13:26 +08:00
}
else
{
var possibilityCount = sortedSelectOptionItems . Where ( x = > ! x . IsHidden & & ! x . IsDisabled ) . Count ( ) ;
2020-06-14 18:54:14 +08:00
2020-11-27 13:13:26 +08:00
if ( possibilityCount = = 1 ) // Do nothing if there is only one choice
return ;
var index = sortedSelectOptionItems . FindIndex ( x = > EqualityComparer < TItemValue > . Default . Equals ( x . Value , firstActive . Value ) ) ;
index - - ;
int nextIndex ;
if ( index = = - 1 )
2020-06-14 18:54:14 +08:00
{
2020-11-27 13:13:26 +08:00
nextIndex = sortedSelectOptionItems . FindLastIndex ( x = > ! x . IsHidden & & ! x . IsDisabled ) ;
2020-06-14 18:54:14 +08:00
}
else
{
2020-11-27 13:13:26 +08:00
nextIndex = sortedSelectOptionItems . FindIndex ( index , x = > ! x . IsHidden & & ! x . IsDisabled ) ;
if ( nextIndex ! = index )
2020-06-14 18:54:14 +08:00
{
2020-11-27 13:13:26 +08:00
for ( var i = index ; i > = - 1 ; i - - )
{
if ( i < 0 )
{
nextIndex = sortedSelectOptionItems . FindLastIndex ( x = > ! x . IsHidden & & ! x . IsDisabled ) ;
break ;
}
if ( ! sortedSelectOptionItems [ i ] . IsHidden & & ! sortedSelectOptionItems [ i ] . IsDisabled )
{
nextIndex = i ;
break ;
}
}
2020-06-14 18:54:14 +08:00
}
}
2020-07-29 12:44:16 +08:00
2020-11-27 13:13:26 +08:00
if ( nextIndex = = - 1 )
return ;
// Prevent duplicate active items if search has no value
sortedSelectOptionItems . Where ( x = > x . IsActive )
. ForEach ( x = > x . IsActive = false ) ;
sortedSelectOptionItems [ nextIndex ] . IsActive = true ;
2021-02-04 23:40:47 +08:00
ActiveOption = sortedSelectOptionItems [ nextIndex ] ;
2020-11-27 13:13:26 +08:00
await ElementScrollIntoViewAsync ( sortedSelectOptionItems [ nextIndex ] . Ref ) ;
2020-06-14 18:54:14 +08:00
}
2020-11-27 13:13:26 +08:00
}
2020-07-29 12:44:16 +08:00
2020-11-27 13:13:26 +08:00
if ( key = = "ARROWDOWN" )
{
if ( ! _dropDown . IsOverlayShow ( ) & & ! Disabled )
{
await _dropDown . Show ( ) ;
overlayFirstOpen = true ;
}
if ( ! SelectOptionItems . Any ( ) )
return ;
var sortedSelectOptionItems = SortedSelectOptionItems . ToList ( ) ;
if ( overlayFirstOpen )
{
// Check if there is a selected item and set it as active
var currentSelected = sortedSelectOptionItems . FirstOrDefault ( x = > x . IsSelected ) ;
if ( currentSelected ! = null )
{
if ( currentSelected . IsActive )
return ;
sortedSelectOptionItems . Where ( x = > x . IsActive )
. ForEach ( i = > i . IsActive = false ) ;
currentSelected . IsActive = true ;
2021-02-04 23:40:47 +08:00
ActiveOption = currentSelected ;
2020-11-27 13:13:26 +08:00
// ToDo: Sometime the element does not scroll, you have to call the function twice
await ElementScrollIntoViewAsync ( currentSelected . Ref ) ;
await Task . Delay ( 1 ) ;
await ElementScrollIntoViewAsync ( currentSelected . Ref ) ;
}
return ;
}
var firstActive = sortedSelectOptionItems . FirstOrDefault ( x = > x . IsActive & & ! x . IsHidden & & ! x . IsDisabled ) ;
if ( firstActive = = null )
{
var firstOption = sortedSelectOptionItems . FirstOrDefault ( x = > ! x . IsHidden & & ! x . IsDisabled ) ;
if ( firstOption ! = null )
2021-02-04 23:40:47 +08:00
{
2020-11-27 13:13:26 +08:00
firstOption . IsActive = true ;
2021-02-04 23:40:47 +08:00
ActiveOption = firstOption ;
}
2020-11-27 13:13:26 +08:00
}
else
{
var possibilityCount = sortedSelectOptionItems . Count ( x = > ! x . IsHidden & & ! x . IsDisabled ) ;
if ( possibilityCount = = 1 ) // Do nothing if there is only one choice
return ;
var index = sortedSelectOptionItems . FindIndex ( x = > EqualityComparer < TItemValue > . Default . Equals ( x . Value , firstActive . Value ) ) ;
index + + ;
var nextIndex = sortedSelectOptionItems . FindIndex ( index , x = > ! x . IsHidden & & ! x . IsDisabled ) ;
if ( nextIndex = = - 1 ) // Maybe the next item is above the current active item
{
nextIndex = sortedSelectOptionItems . FindIndex ( 0 , x = > ! x . IsHidden & & ! x . IsDisabled ) ; // Try to find the index from the first available item
}
if ( nextIndex = = - 1 )
return ;
// Prevent duplicate active items if search has no value
sortedSelectOptionItems . Where ( x = > x . IsActive )
. ForEach ( x = > x . IsActive = false ) ;
sortedSelectOptionItems [ nextIndex ] . IsActive = true ;
2021-02-04 23:40:47 +08:00
ActiveOption = sortedSelectOptionItems [ nextIndex ] ;
2020-11-27 13:13:26 +08:00
await ElementScrollIntoViewAsync ( sortedSelectOptionItems [ nextIndex ] . Ref ) ;
}
}
if ( key = = "HOME" )
{
if ( _dropDown . IsOverlayShow ( ) )
{
if ( ! SelectOptionItems . Any ( ) )
return ;
var sortedSelectOptionItems = SortedSelectOptionItems . ToList ( ) ;
var index = sortedSelectOptionItems . FindIndex ( 0 , x = > ! x . IsHidden & & ! x . IsDisabled ) ;
if ( index = = - 1 )
return ;
// Prevent duplicate active items if search has no value
sortedSelectOptionItems . Where ( x = > x . IsActive )
. ForEach ( i = > i . IsActive = false ) ;
sortedSelectOptionItems [ index ] . IsActive = true ;
2021-02-04 23:40:47 +08:00
ActiveOption = sortedSelectOptionItems [ index ] ;
2020-11-27 13:13:26 +08:00
await ElementScrollIntoViewAsync ( sortedSelectOptionItems [ index ] . Ref ) ;
}
}
if ( key = = "END" )
{
if ( _dropDown . IsOverlayShow ( ) )
{
if ( ! SelectOptionItems . Any ( ) )
return ;
var sortedSelectOptionItems = SortedSelectOptionItems . ToList ( ) ;
var index = sortedSelectOptionItems . FindLastIndex ( x = > ! x . IsHidden & & ! x . IsDisabled ) ;
if ( index = = - 1 )
return ;
// Prevent duplicate active items if search has no value
sortedSelectOptionItems . Where ( x = > x . IsActive )
. ForEach ( i = > i . IsActive = false ) ;
sortedSelectOptionItems [ index ] . IsActive = true ;
2021-02-04 23:40:47 +08:00
ActiveOption = sortedSelectOptionItems [ index ] ;
2020-11-27 13:13:26 +08:00
await ElementScrollIntoViewAsync ( sortedSelectOptionItems [ index ] . Ref ) ;
}
}
if ( key = = "ESCAPE" )
{
if ( _dropDown . IsOverlayShow ( ) )
{
await CloseAsync ( ) ;
}
2020-06-14 18:54:14 +08:00
}
2021-03-04 23:16:24 +08:00
2021-04-15 12:55:36 +08:00
if ( key = = "BACKSPACE" & & string . IsNullOrEmpty ( _searchValue ) & &
2021-04-08 22:17:44 +08:00
( EnableSearch | | SelectMode = = SelectMode . Tags | | AllowClear ) )
2021-03-04 23:16:24 +08:00
{
2021-04-08 22:17:44 +08:00
if ( string . IsNullOrEmpty ( _prevSearchValue ) & & SelectedOptionItems . Count > 0 )
await OnRemoveSelectedAsync ( SelectedOptionItems . Last ( ) ) ;
else if ( ! string . IsNullOrEmpty ( _prevSearchValue ) )
_prevSearchValue = _searchValue ;
2021-03-04 23:16:24 +08:00
}
2020-11-27 13:13:26 +08:00
}
/// <summary>
/// Method is called via EventCallBack if the Input element get the focus
/// </summary>
protected async Task OnInputFocusAsync ( FocusEventArgs _ )
{
await SetInputFocusAsync ( ) ;
}
/// <summary>
/// Method is called via EventCallback if a key is pressed inside Input element.
/// The method is used to get the TAB event if the user press TAB to cycle trough elements.
/// If a TAB is received, the overlay will be closed and the Input element blures.
/// </summary>
protected async Task OnKeyDownAsync ( KeyboardEventArgs e )
{
if ( e = = null ) throw new ArgumentNullException ( nameof ( e ) ) ;
2020-06-14 18:54:14 +08:00
2020-11-27 13:13:26 +08:00
var key = e . Key . ToUpperInvariant ( ) ;
2020-10-25 14:59:10 +08:00
2020-11-27 13:13:26 +08:00
if ( key = = "TAB" )
2020-06-14 18:54:14 +08:00
{
2020-11-27 13:13:26 +08:00
if ( _dropDown . IsOverlayShow ( ) )
{
await CloseAsync ( ) ;
}
await SetInputBlurAsync ( ) ;
2020-06-14 18:54:14 +08:00
}
2021-02-04 23:40:47 +08:00
else if ( TokenSeparators is not null & & TokenSeparators . Length > 0 )
{
_isToken = TokenSeparators . Contains ( e . Key [ 0 ] ) ;
}
2020-06-14 18:54:14 +08:00
}
2020-11-27 13:13:26 +08:00
/// <summary>
/// Check if Focused property is False; Set the Focused property to true, change the
/// style and set the Focus on the Input element via DOM. It also invoke the OnFocus Action.
/// </summary>
protected async Task SetInputFocusAsync ( )
{
if ( ! Focused )
{
Focused = true ;
SetClassMap ( ) ;
2021-04-15 14:19:26 +08:00
await FocusAsync ( _inputRef ) ;
2020-11-27 13:13:26 +08:00
OnFocus ? . Invoke ( ) ;
}
}
/// <summary>
/// Method is called via EventCallBack if the Input element loses the focus
/// </summary>
protected async Task OnInputBlurAsync ( FocusEventArgs _ )
{
await SetInputBlurAsync ( ) ;
}
/// <summary>
/// Check if Focused property is true; Set the Focused property to false, change the
/// style and blures the Input element via DOM. It also invoke the OnBlur Action.
/// </summary>
/// <returns></returns>
protected async Task SetInputBlurAsync ( )
2020-06-14 18:54:14 +08:00
{
2020-11-27 13:13:26 +08:00
if ( Focused )
2020-06-14 18:54:14 +08:00
{
2020-11-27 13:13:26 +08:00
Focused = false ;
SetClassMap ( ) ;
await JsInvokeAsync ( JSInteropConstants . Blur , _inputRef ) ;
OnBlur ? . Invoke ( ) ;
2020-06-14 18:54:14 +08:00
}
2020-11-27 13:13:26 +08:00
}
2020-06-14 18:54:14 +08:00
2020-11-27 13:13:26 +08:00
protected void ClearSearch ( )
{
if ( SelectMode ! = SelectMode . Default )
2020-06-14 18:54:14 +08:00
{
2021-02-04 23:40:47 +08:00
foreach ( var item in SelectOptionItems )
2020-06-14 18:54:14 +08:00
{
2021-02-04 23:40:47 +08:00
if ( item . IsHidden )
{
if ( ( HideSelected & & ! item . IsSelected ) | | ! HideSelected )
item . IsHidden = false ;
}
2020-06-14 18:54:14 +08:00
}
2021-02-04 23:40:47 +08:00
foreach ( var item in AddedTags )
2020-06-14 18:54:14 +08:00
{
2021-02-04 23:40:47 +08:00
if ( item . IsHidden )
{
if ( ( HideSelected & & ! item . IsSelected ) | | ! HideSelected )
item . IsHidden = false ;
}
2020-06-14 18:54:14 +08:00
}
}
2020-11-27 13:13:26 +08:00
_searchValue = string . Empty ;
2021-04-08 22:17:44 +08:00
_prevSearchValue = string . Empty ;
2020-06-14 18:54:14 +08:00
}
2020-06-28 12:54:58 +08:00
2020-11-27 13:13:26 +08:00
/// <summary>
/// Search the first selected item, set IsActive to False for all other items and call the scrollIntoView function via JavaScript.
/// The method is used to scroll to the first selected item after opening the overlay.
/// </summary>
protected async Task ScrollToFirstSelectedItemAsync ( )
2020-06-28 12:54:58 +08:00
{
2020-11-27 13:13:26 +08:00
// Check if there is a selected item and set it as active
var currentSelected = SelectOptionItems . FirstOrDefault ( x = > x . IsSelected ) ;
if ( currentSelected ! = null )
{
SelectOptionItems . Where ( x = > x . IsActive )
. ForEach ( i = > i . IsActive = false ) ;
currentSelected . IsActive = true ;
2021-02-04 23:40:47 +08:00
ActiveOption = currentSelected ;
2020-11-27 13:13:26 +08:00
// ToDo: Sometime the element does not scroll, you have to call the function twice
await ElementScrollIntoViewAsync ( currentSelected . Ref ) ;
await Task . Delay ( 1 ) ;
await ElementScrollIntoViewAsync ( currentSelected . Ref ) ;
}
2021-02-04 23:40:47 +08:00
else if ( ActiveOption = = null ) //position on first element in the list
{
var selectionCandidate = SelectOptionItems . FirstOrDefault ( ) ;
if ( selectionCandidate ! = null )
ActiveOption = selectionCandidate ;
}
2020-06-28 12:54:58 +08:00
}
2020-10-25 14:59:10 +08:00
2020-11-27 13:13:26 +08:00
/// <summary>
/// Method is called via EventCallBack after the user clicked on the Clear icon inside the Input element.
/// Set the IsSelected and IsHidden properties for all items to False. It updates the overlay position if
/// the SelectMode is Tags or Multiple. Invoke the OnClearSelected Action. Set the Value(s) to default.
/// </summary>
protected async Task OnInputClearClickAsync ( MouseEventArgs _ )
{
2021-02-04 23:40:47 +08:00
List < SelectOptionItem < TItemValue , TItem > > tagItems = new ( ) ;
2020-11-27 13:13:26 +08:00
SelectOptionItems . Where ( c = > c . IsSelected )
. ForEach ( i = >
{
i . IsSelected = false ;
i . IsHidden = false ;
2021-02-04 23:40:47 +08:00
if ( i . IsAddedTag )
tagItems . Add ( i ) ;
2020-11-27 13:13:26 +08:00
} ) ;
2021-02-04 23:40:47 +08:00
//When clearing, also remove all added tags that are kept after adding in SelectOptionItems
if ( tagItems . Count > 0 )
{
foreach ( var item in tagItems )
{
SelectOptionItems . Remove ( item ) ;
}
}
AddedTags . Clear ( ) ;
ActiveOption = SelectOptionItems . FirstOrDefault ( ) ;
CustomTagSelectOptionItem = null ;
SelectedOptionItems . Clear ( ) ;
2020-09-09 22:12:12 +08:00
2020-11-27 13:13:26 +08:00
await ClearSelectedAsync ( ) ;
if ( SelectMode ! = SelectMode . Default )
{
await Task . Delay ( 1 ) ; // Todo - Workaround because UI does not refresh
await UpdateOverlayPositionAsync ( ) ;
StateHasChanged ( ) ; // Todo - Workaround because UI does not refresh
}
OnClearSelected ? . Invoke ( ) ;
}
/// <summary>
/// Method is called via EventCallBack if the user clicked on the Close icon of a Tag.
/// </summary>
protected async Task OnRemoveSelectedAsync ( SelectOptionItem < TItemValue , TItem > selectOption )
2020-09-09 22:12:12 +08:00
{
2020-11-27 13:13:26 +08:00
if ( selectOption = = null ) throw new ArgumentNullException ( nameof ( selectOption ) ) ;
await SetValueAsync ( selectOption ) ;
2020-09-09 22:12:12 +08:00
}
2020-11-27 13:13:26 +08:00
2021-01-21 17:20:10 +08:00
internal async Task OnArrowClick ( MouseEventArgs args )
{
await _dropDown . OnClickDiv ( args ) ;
}
2021-03-12 17:02:11 +08:00
2020-11-27 13:13:26 +08:00
#endregion Events
2020-06-14 18:54:14 +08:00
}
2021-04-16 18:50:36 +08:00
}