-
Notifications
You must be signed in to change notification settings - Fork 459
[dev-v5] Convert the Calendar and DatePicker components to a generic type #4153
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[dev-v5] Convert the Calendar and DatePicker components to a generic type #4153
Conversation
… enhanced flexibility
… selected dates, improving type safety and flexibility.
…pes in `CalendarDefault.razor`.
Updated calendar components to support nullable DateTime values and generic date representations. Key changes include: - Modified `DisabledDate` methods to accept `DateTime?`. - Changed `SelectedDay` in `CalendarSelection.razor` to a non-nullable `DateTime`. - Updated `DisabledDateFunc` in `FluentCalendarBase.cs` to use a generic type `TValue`. - Refactored `OnSelectedDateHandlerAsync` to accept `TValue`. - Implemented conversions to `TValue` in various methods for consistency. - Enhanced `PickerMonthChanged` to handle nullable month values in `FluentDatePicker.razor`. - Streamlined `OnSelectedDateAsync` to directly use `TValue`. These changes improve the flexibility and robustness of the calendar components.
|
✅ All tests passed successfully Details on your Workflow / Core Tests page. |
Summary - Unit Tests Code CoverageSummary
CoverageMicrosoft.FluentUI.AspNetCore.Components - 98.9%
|
|
@dvoituron just curious. Does this also support |
|
Yes. Check the sample in the description 🙂 |
|
Oh sorry. Not yet. The TimePicker is not yet migrated. But I will do that |
|
Nice! Also i have more of a general question. protected FluentCalendarBase(LibraryConfiguration configuration) : base(configuration)
{
if (typeof(TValue).IsNotDateType())
{
throw new InvalidOperationException($"The type parameter {typeof(TValue)} is not supported. Supported types are DateTime, DateTime?, DateOnly, and DateOnly?.");
}
}Can we provide our own roslyn analyzer(s) for things like this? [DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class FluentCalendarTValueUsageAnalyzer : DiagnosticAnalyzer
{
public const string DiagnosticId = "FUB002";
private const string Title = "Unsupported TValue on FluentCalendarBase";
private const string MessageFormat = "Class '{0}' inherits 'FluentCalendarBase<{1}>' but '{1}' is not a supported date type. Supported types: DateTime, DateTime?, DateOnly, DateOnly?, DateTimeOffset, DateTimeOffset?.";
private const string Description = "FluentCalendarBase<TValue> only supports specific date types. Use one of the supported types or keep the type parameter generic.";
private const string Category = "Usage";
private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(
DiagnosticId,
Title,
MessageFormat,
Category,
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: Description);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.RegisterSymbolAction(AnalyzeNamedType, SymbolKind.NamedType);
}
private static void AnalyzeNamedType(SymbolAnalysisContext context)
{
var namedType = (INamedTypeSymbol)context.Symbol;
// Only examine classes (includes partials generated for .razor)
if (namedType.TypeKind != TypeKind.Class)
return;
// Find a base type that is FluentCalendarBase<T>
var compilation = context.Compilation;
var fluentCalendarBaseSymbol = compilation.GetTypeByMetadataName("Microsoft.FluentUI.AspNetCore.Components.FluentCalendarBase`1");
INamedTypeSymbol? foundConstructedBase = null;
for (var bt = namedType.BaseType; bt != null; bt = bt.BaseType)
{
if (bt is INamedTypeSymbol namedBase)
{
if (fluentCalendarBaseSymbol is not null)
{
if (SymbolEqualityComparer.Default.Equals(namedBase.OriginalDefinition, fluentCalendarBaseSymbol))
{
foundConstructedBase = namedBase;
break;
}
}
else
{
// Fallback: match by name if metadata lookup failed
if (namedBase.OriginalDefinition?.Name == "FluentCalendarBase`1")
{
foundConstructedBase = namedBase;
break;
}
}
}
}
if (foundConstructedBase is null)
return; // not inheriting FluentCalendarBase<T>
// Get the TValue type argument
if (foundConstructedBase.TypeArguments.Length != 1)
return;
var tArg = foundConstructedBase.TypeArguments[0];
// If TValue is a type parameter (e.g. class Foo<TValue> : FluentCalendarBase<TValue>), skip - cannot decide at compile time
if (tArg.TypeKind == TypeKind.TypeParameter)
return;
// Build symbols for supported types
var dateTimeSym = compilation.GetSpecialType(SpecialType.System_DateTime);
var dateTimeOffsetSym = compilation.GetSpecialType(SpecialType.System_DateTimeOffset);
var dateOnlySym = compilation.GetTypeByMetadataName("System.DateOnly");
var nullableOriginal = compilation.GetSpecialType(SpecialType.System_Nullable_T);
bool IsSupported(ITypeSymbol type)
{
// Nullable<T>
if (type is INamedTypeSymbol named && SymbolEqualityComparer.Default.Equals(named.OriginalDefinition, nullableOriginal))
{
var inner = named.TypeArguments.Length > 0 ? named.TypeArguments[0] : null;
if (inner is null) return false;
return IsSupported(inner);
}
if (SymbolEqualityComparer.Default.Equals(type, dateTimeSym))
return true;
if (SymbolEqualityComparer.Default.Equals(type, dateTimeOffsetSym))
return true;
if (dateOnlySym is not null && SymbolEqualityComparer.Default.Equals(type, dateOnlySym))
return true;
return false;
}
if (IsSupported(tArg))
return;
// Report diagnostic. Try to get a good location: use base list syntax if available, otherwise class identifier.
Location? diagLocation = null;
foreach (var syntaxRef in namedType.DeclaringSyntaxReferences)
{
var node = syntaxRef.GetSyntax(context.CancellationToken);
var classDecl = node as Microsoft.CodeAnalysis.CSharp.Syntax.ClassDeclarationSyntax;
if (classDecl == null)
continue;
// Try to find the base type syntax that corresponds to FluentCalendarBase<...>
var baseList = classDecl.BaseList;
if (baseList != null)
{
foreach (var baseTypeSyntax in baseList.Types)
{
// locate the syntax for FluentCalendarBase
var text = baseTypeSyntax.Type.ToString();
if (text.StartsWith("FluentCalendarBase", StringComparison.Ordinal) || text.Contains("FluentCalendarBase<"))
{
diagLocation = baseTypeSyntax.GetLocation();
break;
}
}
}
if (diagLocation == null)
{
diagLocation = classDecl.Identifier.GetLocation();
}
if (diagLocation != null)
break;
}
var typeDisplay = tArg.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat);
var diag = Diagnostic.Create(Rule, diagLocation ?? namedType.Locations[0], namedType.Name, typeDisplay);
context.ReportDiagnostic(diag);
}
}It won't be a big issue but if we can spot the issue at compile time instead of runtime, isn't that better? |
This could help some developers. Although this problem already exists in several other components, such as FluentSlider, for example. However, this is not the purpose of this library. Adding an analyzer could be done in another NuGet Package that requires a reference to |
[dev-v5] Convert the Calendar and DatePicker components to a generic type
The FluentCalendar and FluentDatePicker components are now generic components, so you can use it with other date types such as
DateTime?,DateTime,DateOnly?orDateOnly.Blazor will automatically infer the type based on the value you provide to the
ValueorSelectedDatesparameters.You can also explicitly set the type using the generic type parameter:
TValue=“DateOnly?”.Example
Unit Tests
Added and updated