Files
radzen-blazor/Radzen.Blazor/RadzenDatePicker.razor.cs

1948 lines
68 KiB
C#

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.JSInterop;
using Radzen.Blazor.Rendering;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
namespace Radzen.Blazor
{
/// <summary>
/// A date and time picker component that provides a calendar popup for selecting dates and optional time selection.
/// RadzenDatePicker supports DateTime, DateTime?, DateTimeOffset, DateTimeOffset?, DateOnly, and DateOnly? types with extensive customization options.
/// Displays a text input with a calendar icon button. Clicking opens a popup calendar for date selection.
/// Optional time selection can be enabled via ShowTime property, supporting both 12-hour and 24-hour formats.
/// Supports features like min/max date constraints, disabled dates, initial view configuration, calendar week display,
/// inline calendar mode (always visible), time-only mode, multiple date selection, and culture-specific formatting.
/// </summary>
/// <typeparam name="TValue">The type of the date/time value. Supports DateTime, DateTime?, DateTimeOffset, DateTimeOffset?, DateOnly, and DateOnly?.</typeparam>
/// <example>
/// Basic date picker:
/// <code>
/// &lt;RadzenDatePicker @bind-Value=@birthDate TValue="DateTime" Placeholder="Select date" /&gt;
/// </code>
/// Date and time picker with 12-hour format:
/// <code>
/// &lt;RadzenDatePicker @bind-Value=@appointmentDate TValue="DateTime" ShowTime="true" TimeOnly="false" HoursStep="1" MinutesStep="15" /&gt;
/// </code>
/// Date picker with constraints:
/// <code>
/// &lt;RadzenDatePicker @bind-Value=@selectedDate TValue="DateTime" Min="@DateTime.Today" Max="@DateTime.Today.AddMonths(6)" /&gt;
/// </code>
/// </example>
public partial class RadzenDatePicker<TValue> : RadzenComponent, IRadzenFormComponent
{
/// <summary>
/// Gets or sets a value indicating whether the component should update its value on every input event
/// rather than waiting for the input to lose focus (onchange event).
/// When enabled, the bound value is updated as the user types, provided the input can be parsed as a valid date.
/// Invalid intermediate input is ignored to avoid clearing the value while the user is still typing.
/// </summary>
/// <value><c>true</c> for immediate updates; <c>false</c> for deferred updates. Default is <c>false</c>.</value>
[Parameter]
public bool Immediate { get; set; }
/// <summary>
/// Gets or sets whether the calendar week number column should be displayed in the calendar popup.
/// When enabled, each week row shows its corresponding week number according to ISO 8601.
/// </summary>
/// <value><c>true</c> to show calendar week numbers; otherwise, <c>false</c>. Default is <c>false</c>.</value>
[Parameter]
public bool ShowCalendarWeek { get; set; }
/// <summary>
/// Gets or sets whether multiple dates can be selected.
/// When enabled, users can select multiple dates from the calendar, and the value will be a collection of DateTimes.
/// </summary>
/// <value><c>true</c> to enable multiple date selection; otherwise, <c>false</c>. Default is <c>false</c>.</value>
[Parameter]
public bool Multiple { get; set; }
// Holds selected dates when Multiple is true
List<DateTime> selectedDates = new List<DateTime>();
/// <summary>
/// Gets or sets the previous month aria label text.
/// </summary>
/// <value>The previous month aria label text.</value>
[Parameter]
public string CalendarWeekTitle { get; set; } = "#";
/// <summary>
/// Gets or sets the toggle popup aria label text.
/// </summary>
/// <value>The toggle popup aria label text.</value>
[Parameter]
public string ToggleAriaLabel { get; set; } = "Toggle";
/// <summary>
/// Gets or sets the popup aria label text.
/// </summary>
/// <value>The popup aria label text.</value>
[Parameter]
public string PopupAriaLabel { get; set; } = "Date picker";
/// <summary>
/// Gets or sets the clear button aria label text.
/// </summary>
/// <value>The clear button aria label text.</value>
[Parameter]
public string ClearAriaLabel { get; set; } = "Clear";
/// <summary>
/// Gets or sets the hour input aria label text.
/// </summary>
/// <value>The hour input aria label text.</value>
[Parameter]
public string HourAriaLabel { get; set; } = "Hour";
/// <summary>
/// Gets or sets the minutes input aria label text.
/// </summary>
/// <value>The minutes input aria label text.</value>
[Parameter]
public string MinutesAriaLabel { get; set; } = "Minutes";
/// <summary>
/// Gets or sets the seconds input aria label text.
/// </summary>
/// <value>The seconds input aria label text.</value>
[Parameter]
public string SecondsAriaLabel { get; set; } = "Seconds";
/// <summary>
/// Gets or sets the OK button aria label text.
/// </summary>
/// <value>The OK button aria label text.</value>
[Parameter]
public string OkAriaLabel { get; set; } = "Ok";
/// <summary>
/// Gets or sets the previous month aria label text.
/// </summary>
/// <value>The previous month aria label text.</value>
[Parameter]
public string PrevMonthAriaLabel { get; set; } = "Previous month";
/// <summary>
/// Gets or sets the next month aria label text.
/// </summary>
/// <value>The next month aria label text.</value>
[Parameter]
public string NextMonthAriaLabel { get; set; } = "Next month";
/// <summary>
/// Gets or sets the toggle Am/Pm aria label text.
/// </summary>
/// <value>The toggle Am/Pm aria label text.</value>
[Parameter]
public string ToggleAmPmAriaLabel { get; set; } = "Toggle Am/Pm";
/// <summary>
/// Specifies additional custom attributes that will be rendered by the input.
/// </summary>
/// <value>The attributes.</value>
[Parameter]
public IReadOnlyDictionary<string, object>? InputAttributes { get; set; }
RadzenDropDown<int>? monthDropDown;
RadzenDropDown<int>? yearDropDown;
async Task ToggleAmPm()
{
if (Disabled) return;
var newHour = (CurrentDate.Hour + 12) % 24;
var newValue = new DateTime(CurrentDate.Year, CurrentDate.Month, CurrentDate.Day, newHour, CurrentDate.Minute, CurrentDate.Second);
hour = newValue.Hour;
await UpdateValueFromTime(newValue);
}
int GetHour24FormatFrom12Format(int hour12)
{
hour12 = Math.Max(Math.Min(hour12, 12), 1);
return CurrentDate.Hour < 12 ?
(hour12 == 12 ? 0 : hour12) // AM
: (hour12 == 12 ? 12 : hour12 + 12); // PM
}
int? hour;
void OnUpdateHourInput(ChangeEventArgs args)
{
var value = $"{args.Value}";
if (string.IsNullOrWhiteSpace(value) || !int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out int v))
{
hour = null;
return;
}
hour = HourFormat == "12" ? GetHour24FormatFrom12Format(v) : Math.Max(Math.Min(v, 23), 0);
}
int? minutes;
void OnUpdateHourMinutes(ChangeEventArgs args)
{
var value = $"{args.Value}";
if (string.IsNullOrWhiteSpace(value) || !int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out int v))
{
minutes = null;
return;
}
minutes = Math.Max(Math.Min(v, 59), 0);
}
int? seconds;
void OnUpdateHourSeconds(ChangeEventArgs args)
{
var value = $"{args.Value}";
if (string.IsNullOrWhiteSpace(value) || !int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out int v))
{
seconds = null;
return;
}
seconds = Math.Max(Math.Min(v, 59), 0);
}
DateTime? _valueBeforeTimeEdit;
bool _hasUncommittedTimeChange;
DateTime ClampToMinMax(DateTime value)
{
if (Min.HasValue && value < Min.Value)
{
return Min.Value;
}
if (Max.HasValue && value > Max.Value)
{
return Max.Value;
}
return value;
}
async Task UpdateValueFromTime(DateTime newValue)
{
newValue = ClampToMinMax(newValue);
if (ShowTimeOkButton)
{
if (!_hasUncommittedTimeChange)
{
_valueBeforeTimeEdit = _dateTimeValue;
_hasUncommittedTimeChange = true;
}
DateTimeValue = newValue;
CurrentDate = newValue;
}
else
{
Value = newValue;
CurrentDate = newValue;
await OnChange();
}
}
void RevertUncommittedTimeChange()
{
if (!ShowTimeOkButton || !_hasUncommittedTimeChange) return;
_hasUncommittedTimeChange = false;
if (_valueBeforeTimeEdit.HasValue)
{
_dateTimeValue = _valueBeforeTimeEdit;
_value = ConvertToTValue(_dateTimeValue);
}
else
{
_dateTimeValue = null;
_value = null;
}
_currentDate = default(DateTime);
hour = null;
minutes = null;
seconds = null;
}
/// <summary>
/// Called from JavaScript when the popup is closed (e.g. by clicking outside) in Initial render mode.
/// </summary>
[JSInvokable("OnPopupClose")]
public void OnPopupClose()
{
RevertUncommittedTimeChange();
contentStyle = "display:none;";
StateHasChanged();
}
void OnPopupCloseFromEvent()
{
RevertUncommittedTimeChange();
contentStyle = "display:none;";
StateHasChanged();
}
async Task UpdateHour(int v)
{
var newHour = HourFormat == "12" ? GetHour24FormatFrom12Format(v) : v;
var newMinute = CurrentDate.Minute;
var newSecond = CurrentDate.Second;
if (v < 0)
{
newHour = string.IsNullOrEmpty(HoursStep) ? 23 : 0;
newMinute = string.IsNullOrEmpty(MinutesStep) ? 59 : 0;
newSecond = string.IsNullOrEmpty(SecondsStep) ? 59 : 0;
}
var newValue = new DateTime(CurrentDate.Year, CurrentDate.Month, CurrentDate.Day, newHour > 23 || newHour < 0 ? 0 : newHour, newMinute, newSecond);
hour = newValue.Hour;
await UpdateValueFromTime(newValue);
}
async Task UpdateMinutes(int v)
{
var newValue = new DateTime(CurrentDate.Year, CurrentDate.Month, CurrentDate.Day, CurrentDate.Hour, v, CurrentDate.Second);
minutes = newValue.Minute;
await UpdateValueFromTime(newValue);
}
async Task UpdateSeconds(int v)
{
var newValue = new DateTime(CurrentDate.Year, CurrentDate.Month, CurrentDate.Day, CurrentDate.Hour, CurrentDate.Minute, v);
seconds = newValue.Second;
await UpdateValueFromTime(newValue);
}
async Task OnOkClick(bool shouldClose = true, bool fromOkButton = false)
{
// Prevent single-value change path in Multiple mode
if (Multiple)
{
if (Min.HasValue && CurrentDate < Min.Value || Max.HasValue && CurrentDate > Max.Value)
{
return;
}
if (!Disabled)
{
// Use the currently selected date (with current time if shown) and update the multi-selection
var date = new DateTime(CurrentDate.Year, CurrentDate.Month, CurrentDate.Day, CurrentDate.Hour, CurrentDate.Minute, CurrentDate.Second);
ToggleSelectedDate(date);
await UpdateValueFromSelectedDates(date);
}
_hasUncommittedTimeChange = false;
if (shouldClose)
{
Close();
}
if (fromOkButton && OkClick.HasDelegate)
{
await OkClick.InvokeAsync(DateTimeValue);
}
return;
}
_hasUncommittedTimeChange = false;
if (shouldClose)
{
Close();
}
if(Min.HasValue && CurrentDate < Min.Value || Max.HasValue && CurrentDate > Max.Value)
{
return;
}
if (!Disabled)
{
DateTime date = CurrentDate;
if (CurrentDate.Hour != hour && hour != null)
{
date = new DateTime(CurrentDate.Year, CurrentDate.Month, CurrentDate.Day, hour.Value, CurrentDate.Minute, CurrentDate.Second);
}
if (CurrentDate.Minute != minutes && minutes != null)
{
date = new DateTime(CurrentDate.Year, CurrentDate.Month, CurrentDate.Day, CurrentDate.Hour, minutes.Value, CurrentDate.Second);
}
if (CurrentDate.Second != seconds && seconds != null)
{
date = new DateTime(CurrentDate.Year, CurrentDate.Month, CurrentDate.Day, CurrentDate.Hour, CurrentDate.Minute, seconds.Value);
}
Value = date;
await OnChange();
if (fromOkButton && OkClick.HasDelegate)
{
await OkClick.InvokeAsync(DateTimeValue);
}
if (monthDropDown != null)
{
await monthDropDown.PopupClose();
}
if (yearDropDown != null)
{
await yearDropDown.PopupClose();
}
}
}
class NameValue
{
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>The name.</value>
public string? Name { get; set; }
/// <summary>
/// Gets or sets the value.
/// </summary>
/// <value>The value.</value>
public int Value { get; set; }
}
IList<NameValue> months = new List<NameValue>();
IList<NameValue> years = new List<NameValue>();
int YearFrom { get; set; }
int YearTo { get; set; }
/// <inheritdoc />
protected override void OnInitialized()
{
base.OnInitialized();
YearFormatter = FormatYear;
UpdateYearsAndMonths(Min, Max);
if (typeof(TValue) == typeof(TimeOnly) || typeof(TValue) == typeof(TimeOnly?))
{
TimeOnly = true;
ShowTime = true;
}
}
void UpdateYearsAndMonths(DateTime? min, DateTime? max)
{
var calendar = Culture.Calendar;
if (min.HasValue)
{
YearFrom = calendar.GetYear(min.Value);
}
else
{
var gregorianYearFrom = int.Parse(YearRange.Split(':').First(), CultureInfo.InvariantCulture);
YearFrom = calendar.GetYear(new DateTime(Math.Max(gregorianYearFrom, calendar.MinSupportedDateTime.Year), 1, 1));
}
if (max.HasValue)
{
YearTo = calendar.GetYear(max.Value);
}
else
{
var gregorianYearTo = int.Parse(YearRange.Split(':').Last(), CultureInfo.InvariantCulture);
YearTo = calendar.GetYear(new DateTime(Math.Min(gregorianYearTo, calendar.MaxSupportedDateTime.Year), 1, 1));
}
var monthsInYear = calendar.GetMonthsInYear(calendar.GetYear(CurrentDate == default(DateTime) ? DateTime.Today : CurrentDate));
months = Enumerable.Range(1, monthsInYear).Select(i => new NameValue() { Name = Culture?.DateTimeFormat?.GetMonthName(i) ?? string.Empty, Value = i }).ToList();
years = YearFrom <= YearTo ? Enumerable.Range(YearFrom, YearTo - YearFrom + 1)
.Select(i => new NameValue() { Name = YearFormatter != null ? YearFormatter(i) : null, Value = i }).ToList() : Enumerable.Empty<NameValue>().ToList();
}
private int GetCalendarYear(DateTime date) => Culture.Calendar.GetYear(date);
private int GetCalendarMonth(DateTime date) => Culture.Calendar.GetMonth(date);
private int GetCalendarDayOfMonth(DateTime date) => Culture.Calendar.GetDayOfMonth(date);
private string FormatYear(int calendarYear)
{
var date = new DateTime(calendarYear, 1, 1, Culture.Calendar);
return date.ToString(YearFormat, Culture);
}
/// <summary>
/// Gets or sets the year formatter. Set to <c>FormatYear</c> by default.
/// If set, this function will take precedence over <see cref="YearFormat"/>.
/// </summary>
[Parameter]
public Func<int, string>? YearFormatter { get; set; }
/// <summary>
/// Gets ot sets the year format. Set to <c>yyyy</c> by default.
/// </summary>
[Parameter]
public string YearFormat { get; set; } = "yyyy";
/// <summary>
/// Gets or sets a value indicating whether value can be cleared.
/// </summary>
/// <value><c>true</c> if value can be cleared; otherwise, <c>false</c>.</value>
[Parameter]
public bool AllowClear { get; set; }
/// <summary>
/// Gets or sets the tab index.
/// </summary>
/// <value>The tab index.</value>
[Parameter]
public int TabIndex { get; set; } = 0;
/// <summary>
/// Gets or sets the name of the form component.
/// </summary>
/// <value>The name.</value>
[Parameter]
public string? Name { get; set; }
/// <summary>
/// Gets or sets the input CSS class.
/// </summary>
/// <value>The input CSS class.</value>
[Parameter]
public string InputClass { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the button CSS class.
/// </summary>
/// <value>The button CSS class.</value>
[Parameter]
public string ButtonClass { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the Minimum Selectable Date.
/// </summary>
/// <value>The Minimum Selectable Date.</value>
[Parameter]
public DateTime? Min { get; set; }
/// <summary>
/// Gets or sets the Maximum Selectable Date.
/// </summary>
/// <value>The Maximum Selectable Date.</value>
[Parameter]
public DateTime? Max { get; set; }
/// <summary>
/// Gets or sets the Initial Date/Month View.
/// </summary>
/// <value>The Initial Date/Month View.</value>
[Parameter]
public DateTime? InitialViewDate { get; set; }
DateTime? _dateTimeValue;
DateTime? DateTimeValue
{
get
{
return _dateTimeValue;
}
set
{
if (_dateTimeValue != value)
{
_dateTimeValue = value;
Value = value;
}
}
}
/// <summary>
/// Gets or sets the date render callback. Use it to set attributes.
/// </summary>
/// <value>The date render callback.</value>
[Parameter]
public Action<DateRenderEventArgs>? DateRender { get; set; }
DateRenderEventArgs DateAttributes(DateTime value)
{
bool disabled;
if (ShowTime)
{
disabled = (Min.HasValue && value.Date < Min.Value.Date) || (Max.HasValue && value.Date > Max.Value.Date);
}
else
{
disabled = (Min.HasValue && value < Min.Value) || (Max.HasValue && value > Max.Value);
}
var args = new DateRenderEventArgs() { Date = value, Disabled = disabled };
if (DateRender != null)
{
DateRender(args);
}
return args;
}
async Task OnDayKeyDown(KeyboardEventArgs args, DateTime date, DateRenderEventArgs dateArgs)
{
var key = args.Code != null ? args.Code : args.Key;
if ((key == "Enter" || key == "Space") && !Disabled && !dateArgs.Disabled)
{
await SetDay(date);
}
}
/// <summary>
/// Gets or sets the kind of DateTime bind to control
/// </summary>
[Parameter]
public DateTimeKind Kind { get; set; } = DateTimeKind.Unspecified;
object? _value;
TimeSpan? dateTimeOffsetValueOffset;
/// <summary>
/// Gets or sets the value.
/// </summary>
/// <value>The value.</value>
[Parameter]
public object? Value
{
get
{
return _value;
}
set
{
if (!EqualityComparer<object>.Default.Equals(value, _value))
{
_currentDate = default(DateTime);
if (Multiple)
{
if (value == null)
{
selectedDates.Clear();
_value = null;
_dateTimeValue = null;
}
else if (value is IEnumerable<DateTime> dtEnum)
{
selectedDates = dtEnum.Where(d => d != default(DateTime) && d != DateTime.MaxValue)
.Select(d => DateTime.SpecifyKind(d.Date, Kind))
.Distinct()
.OrderBy(d => d)
.ToList();
_value = dtEnum;
_dateTimeValue = selectedDates.LastOrDefault();
}
else if (value is IEnumerable<DateTime?> ndtEnum)
{
selectedDates = ndtEnum.Where(d => d.HasValue && d.Value != default(DateTime) && d.Value != DateTime.MaxValue)
.Select(d => DateTime.SpecifyKind(d!.Value.Date, Kind))
.Distinct()
.OrderBy(d => d)
.ToList();
_value = ndtEnum;
_dateTimeValue = selectedDates.LastOrDefault();
}
else if (value is IEnumerable<DateOnly> doEnum)
{
selectedDates = doEnum.Select(d => d.ToDateTime(System.TimeOnly.MinValue, Kind).Date)
.Distinct()
.OrderBy(d => d)
.ToList();
_value = doEnum;
_dateTimeValue = selectedDates.LastOrDefault();
}
else if (value is IEnumerable<DateTimeOffset?> dtoEnum)
{
selectedDates = dtoEnum.Where(o => o.HasValue)
.Select(o =>
{
var offset = o!.Value;
if (offset.Offset == TimeSpan.Zero && Kind == DateTimeKind.Local)
{
return offset.LocalDateTime.Date;
}
else if (offset.Offset != TimeSpan.Zero && Kind == DateTimeKind.Utc)
{
return offset.UtcDateTime.Date;
}
else
{
return DateTime.SpecifyKind(offset.DateTime, Kind).Date;
}
})
.Distinct()
.OrderBy(d => d)
.ToList();
_value = dtoEnum;
_dateTimeValue = selectedDates.LastOrDefault();
}
else if (value is IEnumerable<DateOnly?> doNullableEnum)
{
selectedDates = doNullableEnum.Where(d => d.HasValue)
.Select(d => d!.Value.ToDateTime(System.TimeOnly.MinValue, Kind).Date)
.Distinct()
.OrderBy(d => d)
.ToList();
_value = doNullableEnum;
_dateTimeValue = selectedDates.LastOrDefault();
}
else if (value is IEnumerable<TimeOnly?> toNullableEnum)
{
// Map times to dates using CurrentDate's date component
var baseDate = CurrentDate == default(DateTime) ? DateTime.Today : CurrentDate.Date;
selectedDates = toNullableEnum.Where(t => t.HasValue)
.Select(t => new DateTime(baseDate.Year, baseDate.Month, baseDate.Day, t!.Value.Hour, t.Value.Minute, t.Value.Second, t.Value.Millisecond, Kind).Date)
.Distinct()
.OrderBy(d => d)
.ToList();
_value = toNullableEnum;
_dateTimeValue = selectedDates.LastOrDefault();
}
else if (value is DateTime dt && dt != default(DateTime) && dt != DateTime.MaxValue)
{
selectedDates = new List<DateTime> { DateTime.SpecifyKind(dt.Date, Kind) };
_value = selectedDates;
_dateTimeValue = dt;
}
else
{
selectedDates.Clear();
_value = null;
_dateTimeValue = null;
}
}
else
{
_value = ConvertToTValue(value);
if (value is DateTimeOffset offset)
{
dateTimeOffsetValueOffset = offset.Offset;
if (offset.Offset == TimeSpan.Zero && Kind == DateTimeKind.Local)
{
_dateTimeValue = offset.LocalDateTime;
}
else if (offset.Offset != TimeSpan.Zero && Kind == DateTimeKind.Utc)
{
_dateTimeValue = offset.UtcDateTime;
}
else
{
_dateTimeValue = DateTime.SpecifyKind(offset.DateTime, Kind);
}
_value = _dateTimeValue;
}
else
{
if (value is DateTime dateTime && dateTime != default(DateTime) && dateTime != DateTime.MaxValue)
{
_dateTimeValue = DateTime.SpecifyKind(dateTime, Kind);
}
else if (value is DateOnly dateOnly)
{
_dateTimeValue = dateOnly.ToDateTime(System.TimeOnly.MinValue, Kind);
}
else if (value is TimeOnly timeOnly)
{
_dateTimeValue = new DateTime(CurrentDate.Year, CurrentDate.Month, CurrentDate.Day, timeOnly.Hour, timeOnly.Minute, timeOnly.Second, timeOnly.Millisecond, Kind);
_currentDate = _dateTimeValue.Value;
}
else
{
_dateTimeValue = null;
}
_value = ConvertToTValue(_dateTimeValue);
}
}
}
}
}
private static object? ConvertToTValue(object? value)
{
var typeofTValue = typeof(TValue);
if (value is DateTime dt)
{
if (typeofTValue == typeof(DateOnly) || typeofTValue == typeof(DateOnly?))
{
value = DateOnly.FromDateTime(dt);
return (TValue)value;
}
if (typeofTValue == typeof(TimeOnly) || typeofTValue == typeof(TimeOnly?))
{
value = System.TimeOnly.FromDateTime(dt);
return (TValue)value;
}
}
return value;
}
DateTime _currentDate;
private DateTime CurrentDate
{
get
{
if (_currentDate == default(DateTime))
{
_currentDate = HasValue ? DateTimeValue!.Value : InitialViewDate ?? DateTime.Today;
FocusedDate = _currentDate;
}
return _currentDate;
}
set
{
_currentDate = value;
FocusedDate = value;
CurrentDateChanged.InvokeAsync(value);
}
}
/// <summary>
/// Gets or set the current date changed callback.
/// </summary>
[Parameter]
public EventCallback<DateTime> CurrentDateChanged { get; set; }
private DateTime StartDate
{
get
{
if (CurrentDate == DateTime.MinValue)
{
return DateTime.MinValue;
}
var calendar = Culture.Calendar;
var calYear = calendar.GetYear(CurrentDate);
var calMonth = calendar.GetMonth(CurrentDate);
var firstDayOfTheMonth = new DateTime(calYear, calMonth, 1, calendar);
if (firstDayOfTheMonth == DateTime.MinValue)
{
return DateTime.MinValue;
}
int diff = (7 + (firstDayOfTheMonth.DayOfWeek - Culture.DateTimeFormat.FirstDayOfWeek)) % 7;
return firstDayOfTheMonth.AddDays(-1 * diff).Date;
}
}
IEnumerable<string> ShiftedAbbreviatedDayNames
{
get
{
for (int current = (int)Culture.DateTimeFormat.FirstDayOfWeek, to = current + 7; current < to; current++)
{
yield return Culture.DateTimeFormat.AbbreviatedDayNames[current % 7];
}
}
}
/// <summary>
/// Gets a value indicating whether this instance is bound (ValueChanged callback has delegate).
/// </summary>
/// <value><c>true</c> if this instance is bound; otherwise, <c>false</c>.</value>
public bool IsBound
{
get
{
return ValueChanged.HasDelegate;
}
}
/// <summary>
/// Gets a value indicating whether this instance has value.
/// </summary>
/// <value><c>true</c> if this instance has value; otherwise, <c>false</c>.</value>
public bool HasValue
{
get
{
return Multiple ? selectedDates.Count > 0 : (DateTimeValue.HasValue && DateTimeValue != default(DateTime) && DateTimeValue != DateTime.MaxValue);
}
}
/// <summary>
/// Gets the formatted value.
/// </summary>
/// <value>The formatted value.</value>
public string FormattedValue
{
get
{
if (!HasValue)
{
return "";
}
if (Multiple)
{
var format = string.IsNullOrEmpty(DateFormat) ? "d" : DateFormat;
return string.Join(", ", selectedDates.Select(d => d.ToString(format, Culture)));
}
return HasValue ? string.Format(Culture, "{0:" + DateFormat + "}", Value) : "";
}
}
IRadzenForm? _form;
/// <summary>
/// Gets or sets the form.
/// </summary>
/// <value>The form.</value>
[CascadingParameter]
public IRadzenForm? Form
{
get
{
return _form;
}
set
{
if (_form != value && value != null)
{
_form = value;
_form.AddComponent(this);
}
}
}
/// <summary>
/// Gets input reference.
/// </summary>
protected ElementReference input;
/// <summary>
/// Parses the date.
/// </summary>
protected async Task ParseDate()
{
if (JSRuntime == null) return;
DateTime? newValue;
var inputValue = await JSRuntime.InvokeAsync<string>("Radzen.getInputValue", input);
bool valid = TryParseInput(inputValue, out DateTime value);
var nullable = Nullable.GetUnderlyingType(typeof(TValue)) != null || AllowClear;
if (valid && !DateAttributes(value).Disabled)
{
newValue = TimeOnly && CurrentDate != default(DateTime) ? new DateTime(CurrentDate.Year, CurrentDate.Month, CurrentDate.Day, value.Hour, value.Minute, value.Second) : value;
}
else
{
newValue = null;
if (nullable)
{
await JSRuntime!.InvokeAsync<string>("Radzen.setInputValue", input, "");
}
else
{
await JSRuntime!.InvokeAsync<string>("Radzen.setInputValue", input, FormattedValue);
}
}
if (Multiple)
{
if (valid)
{
selectedDates = new List<DateTime>() { value.Date };
await UpdateValueFromSelectedDates(value.Date);
}
else if (nullable)
{
selectedDates.Clear();
await UpdateValueFromSelectedDates(null);
}
}
else if (DateTimeValue != newValue && (newValue != null || nullable))
{
DateTimeValue = newValue;
if ((typeof(TValue) == typeof(DateTimeOffset) || typeof(TValue) == typeof(DateTimeOffset?)) && Value != null)
{
DateTimeOffset? offset = DateTime.SpecifyKind((DateTime)Value, Kind);
await ValueChanged.InvokeAsync((TValue)(object)offset);
}
else if ((typeof(TValue) == typeof(DateTime) || typeof(TValue) == typeof(DateTime?)) && Value != null)
{
await ValueChanged.InvokeAsync((TValue)(object)DateTime.SpecifyKind((DateTime)Value, Kind));
}
else
{
await ValueChanged.InvokeAsync(Value == null ? default(TValue) : (TValue)Value);
}
if (FieldIdentifier.FieldName != null)
{
EditContext?.NotifyFieldChanged(FieldIdentifier);
}
await Change.InvokeAsync(DateTimeValue);
StateHasChanged();
}
}
/// <summary>
/// Parses the date on input. Ignores invalid intermediate input to avoid clearing the value while the user is typing.
/// </summary>
protected async Task ParseDateImmediate()
{
if (JSRuntime == null) return;
var inputValue = await JSRuntime.InvokeAsync<string>("Radzen.getInputValue", input);
bool valid = TryParseInput(inputValue, out DateTime value);
if (!valid || DateAttributes(value).Disabled)
{
return;
}
DateTime? newValue = TimeOnly && CurrentDate != default(DateTime)
? new DateTime(CurrentDate.Year, CurrentDate.Month, CurrentDate.Day, value.Hour, value.Minute, value.Second)
: value;
if (Multiple)
{
selectedDates = new List<DateTime>() { value.Date };
await UpdateValueFromSelectedDates(value.Date);
}
else if (DateTimeValue != newValue)
{
DateTimeValue = newValue;
if ((typeof(TValue) == typeof(DateTimeOffset) || typeof(TValue) == typeof(DateTimeOffset?)) && Value != null)
{
DateTimeOffset? offset = DateTime.SpecifyKind((DateTime)Value, Kind);
await ValueChanged.InvokeAsync((TValue)(object)offset);
}
else if ((typeof(TValue) == typeof(DateTime) || typeof(TValue) == typeof(DateTime?)) && Value != null)
{
await ValueChanged.InvokeAsync((TValue)(object)DateTime.SpecifyKind((DateTime)Value, Kind));
}
else
{
await ValueChanged.InvokeAsync(Value == null ? default(TValue) : (TValue)Value);
}
if (FieldIdentifier.FieldName != null)
{
EditContext?.NotifyFieldChanged(FieldIdentifier);
}
await Change.InvokeAsync(DateTimeValue);
StateHasChanged();
}
}
/// <summary>
/// Parse the input using an function outside the Radzen-library
/// </summary>
[Parameter]
public Func<string, DateTime?>? ParseInput { get; set; }
private bool TryParseInput(string inputValue, out DateTime value)
{
value = DateTime.MinValue;
bool valid = false;
if (ParseInput != null)
{
DateTime? custom = ParseInput.Invoke(inputValue);
if (custom.HasValue)
{
valid = true;
value = custom.Value;
}
}
else
{
valid = DateTime.TryParseExact(inputValue, DateFormat, Culture, DateTimeStyles.None, out value);
if (!valid)
{
valid = DateTime.TryParse(inputValue, Culture, DateTimeStyles.None, out value);
}
}
return valid;
}
async Task Clear()
{
if (Disabled || ReadOnly)
return;
if (Multiple)
{
selectedDates.Clear();
_value = null;
_dateTimeValue = null;
await ValueChanged.InvokeAsync(default(TValue));
if (FieldIdentifier.FieldName != null)
{
EditContext?.NotifyFieldChanged(FieldIdentifier);
}
await Change.InvokeAsync(null);
StateHasChanged();
}
else
{
Value = null;
await ValueChanged.InvokeAsync(default(TValue));
if (FieldIdentifier.FieldName != null)
{
EditContext?.NotifyFieldChanged(FieldIdentifier);
}
await Change.InvokeAsync(DateTimeValue);
StateHasChanged();
}
}
private string ButtonClasses
{
get => $"notranslate rz-button-icon-left rzi rzi-{(TimeOnly ? "time" : "calendar")}";
}
/// <summary>
/// Gets or sets a value indicating whether this <see cref="RadzenDatePicker{TValue}"/> is inline - only Calender.
/// </summary>
/// <value><c>true</c> if inline; otherwise, <c>false</c>.</value>
[Parameter]
public bool Inline { get; set; }
/// <summary>
/// Gets or sets a value indicating whether time only can be set.
/// </summary>
/// <value><c>true</c> if time only can be set; otherwise, <c>false</c>.</value>
[Parameter]
public bool TimeOnly { get; set; }
/// <summary>
/// Gets or sets a value indicating whether read only.
/// </summary>
/// <value><c>true</c> if read only; otherwise, <c>false</c>.</value>
[Parameter]
public bool ReadOnly { get; set; }
/// <summary>
/// Gets or sets a value indicating whether input is allowed.
/// </summary>
/// <value><c>true</c> if input is allowed; otherwise, <c>false</c>.</value>
[Parameter]
public bool AllowInput { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether popup datepicker button is shown.
/// </summary>
/// <value><c>true</c> if need show button open datepicker popup; <c>false</c> if need hide button, click for input field open datepicker popup.</value>
[Parameter]
public bool ShowButton { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether the input box is shown. Ignored if ShowButton is false.
/// </summary>
/// <value><c>true</c> to show the input box; <c>false</c> to hide the input box.</value>
[Parameter]
public bool ShowInput { get; set; } = true;
private bool IsReadonly => ReadOnly || !AllowInput;
/// <summary>
/// Gets or sets a value indicating whether this <see cref="RadzenDatePicker{TValue}"/> is disabled.
/// </summary>
/// <value><c>true</c> if disabled; otherwise, <c>false</c>.</value>
[Parameter]
public bool Disabled { get; set; }
/// <summary>
/// Gets or sets the FormFieldContext of the component
/// </summary>
[CascadingParameter]
public IFormFieldContext? FormFieldContext { get; set; }
/// <summary>
/// Gets or sets a value indicating whether days part is shown.
/// </summary>
/// <value><c>true</c> if days part is shown; otherwise, <c>false</c>.</value>
[Parameter]
public bool ShowDays { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether time part is shown.
/// </summary>
/// <value><c>true</c> if time part is shown; otherwise, <c>false</c>.</value>
[Parameter]
public bool ShowTime { get; set; }
/// <summary>
/// Gets or sets a value indicating whether hour is shown.
/// </summary>
/// <value><c>true</c> if hour is shown; otherwise, <c>false</c>.</value>
[Parameter]
public bool ShowHour { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether minutes are shown.
/// </summary>
/// <value><c>true</c> if minutes are shown; otherwise, <c>false</c>.</value>
[Parameter]
public bool ShowMinutes { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether seconds are shown.
/// </summary>
/// <value><c>true</c> if seconds are shown; otherwise, <c>false</c>.</value>
[Parameter]
public bool ShowSeconds { get; set; }
/// <summary>
/// Gets or sets the hours step.
/// </summary>
/// <value>The hours step.</value>
[Parameter]
public string HoursStep { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the minutes step.
/// </summary>
/// <value>The minutes step.</value>
[Parameter]
public string MinutesStep { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the seconds step.
/// </summary>
/// <value>The seconds step.</value>
[Parameter]
public string SecondsStep { get; set; } = string.Empty;
/// <summary>
/// Gets or sets a value indicating whether the hour picker is padded with a leading zero.
/// </summary>
/// <value><c>true</c> if hour component is padded; otherwise, <c>false</c>.</value>
[Parameter]
public bool PadHours { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the minute picker is padded with a leading zero.
/// </summary>
/// <value><c>true</c> if hour component is padded; otherwise, <c>false</c>.</value>
[Parameter]
public bool PadMinutes { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the second picker is padded with a leading zero.
/// </summary>
/// <value><c>true</c> if hour component is padded; otherwise, <c>false</c>.</value>
[Parameter]
public bool PadSeconds { get; set; }
enum StepType
{
/// <summary>
/// The hours
/// </summary>
Hours,
/// <summary>
/// The minutes
/// </summary>
Minutes,
/// <summary>
/// The seconds
/// </summary>
Seconds
}
double getStep(StepType type)
{
double step = 1;
if (type == StepType.Hours)
{
step = parseStep(HoursStep);
}
else if (type == StepType.Minutes)
{
step = parseStep(MinutesStep);
}
else if (type == StepType.Seconds)
{
step = parseStep(SecondsStep);
}
return step;
}
double parseStep(string step)
{
return string.IsNullOrEmpty(step) || step == "any" ? 1 : double.Parse(step.Replace(",", ".", StringComparison.Ordinal), CultureInfo.InvariantCulture);
}
/// <summary>
/// Gets or sets a value indicating whether time ok button is shown.
/// </summary>
/// <value><c>true</c> if time ok button is shown; otherwise, <c>false</c>.</value>
[Parameter]
public bool ShowTimeOkButton { get; set; } = true;
/// <summary>
/// Gets or sets the date format.
/// </summary>
/// <value>The date format.</value>
[Parameter]
public string DateFormat { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the year range.
/// </summary>
/// <value>The year range.</value>
[Parameter]
public string YearRange { get; set; } = $"1950:{DateTime.Now.AddYears(30).Year}";
/// <summary>
/// Gets or sets the hour format.
/// </summary>
/// <value>The hour format.</value>
[Parameter]
public string HourFormat { get; set; } = "24";
/// <summary>
/// Gets or sets the input placeholder.
/// </summary>
/// <value>The input placeholder.</value>
[Parameter]
public string Placeholder { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the change callback.
/// </summary>
/// <value>The change callback.</value>
[Parameter]
public EventCallback<DateTime?> Change { get; set; }
/// <summary>
/// Gets or sets the OK click callback. Fires only when the user clicks the OK button
/// (visible when <see cref="ShowTimeOkButton"/> is <c>true</c>), allowing developers to
/// distinguish between intermediate day-selection changes and the final user confirmation.
/// </summary>
/// <value>The OK click callback.</value>
[Parameter]
public EventCallback<DateTime?> OkClick { get; set; }
/// <summary>
/// Gets or sets the value changed callback.
/// </summary>
/// <value>The value changed callback.</value>
[Parameter]
public EventCallback<TValue> ValueChanged { get; set; }
/// <summary>
/// Gets or sets the footer template.
/// </summary>
/// <value>The footer template.</value>
[Parameter]
public RenderFragment? FooterTemplate { get; set; }
string contentStyle = "display:none;";
private string GetStyle()
{
return $"{(Inline ? "overflow:auto;" : "")}{(Style != null ? Style : "")}";
}
/// <summary> Gets the current placeholder. Returns empty string if this component is inside a RadzenFormField.</summary>
protected string CurrentPlaceholder => FormFieldContext?.AllowFloatingLabel == true ? " " : Placeholder;
/// <summary>
/// Closes this instance popup.
/// </summary>
public void Close()
{
if (Disabled || ReadOnly || Inline)
return;
RevertUncommittedTimeChange();
if (PopupRenderMode == PopupRenderMode.OnDemand)
{
if (popup != null)
{
InvokeAsync(() => popup.CloseAsync(Element));
}
}
else if (JSRuntime != null)
{
_ = JSRuntime.InvokeVoidAsync("Radzen.closePopup", PopupID);
}
contentStyle = "display:none;";
StateHasChanged();
}
private string PopupStyle
{
get
{
if (Inline)
{
return "";
}
else
{
return $"{contentStyle}";
}
}
}
async Task OnChange()
{
// In Multiple mode we update and raise ValueChanged/Change elsewhere
if (Multiple)
{
return;
}
// IMPORTANT: internally we often store DateTime even when TValue is DateTimeOffset.
// Always decide the outgoing type from TValue to avoid InvalidCastException.
var typeofTValue = typeof(TValue);
if ((typeofTValue == typeof(DateTimeOffset) || typeofTValue == typeof(DateTimeOffset?)) && Value is DateTime dateTimeValue)
{
var specified = DateTime.SpecifyKind(dateTimeValue, Kind);
// Preserve the original offset when possible; otherwise fall back to the DateTime's offset.
var adjusted = dateTimeOffsetValueOffset.HasValue
? new DateTimeOffset(specified, dateTimeOffsetValueOffset.Value)
: new DateTimeOffset(specified);
await ValueChanged.InvokeAsync((TValue)(object)adjusted);
}
else if (Value is DateTime dtValue)
{
var specified = DateTime.SpecifyKind(dtValue, Kind);
await ValueChanged.InvokeAsync((TValue)(object)specified);
}
else
{
await ValueChanged.InvokeAsync(Value != null ? (TValue)Value : default(TValue)!);
}
if (FieldIdentifier.FieldName != null) { EditContext?.NotifyFieldChanged(FieldIdentifier); }
await Change.InvokeAsync(DateTimeValue);
}
/// <inheritdoc />
protected override string GetComponentCssClass()
{
return ClassList.Create("rz-datepicker")
.Add("rz-datepicker-inline", Inline)
.AddDisabled(Disabled)
.Add("rz-state-empty", !HasValue)
.Add(FieldIdentifier, EditContext)
.ToString();
}
private async Task SetDay(DateTime newValue)
{
if (Multiple)
{
var picked = new DateTime(newValue.Year, newValue.Month, newValue.Day, 0, 0, 0);
ToggleSelectedDate(picked);
await UpdateValueFromSelectedDates(picked);
}
else if (ShowTimeOkButton)
{
CurrentDate = ClampToMinMax(new DateTime(newValue.Year, newValue.Month, newValue.Day, CurrentDate.Hour, CurrentDate.Minute, CurrentDate.Second));
await OnOkClick(!ShowTime);
}
else
{
var v = ClampToMinMax(new DateTime(newValue.Year, newValue.Month, newValue.Day, CurrentDate.Hour, CurrentDate.Minute, CurrentDate.Second));
if (v != DateTimeValue)
{
DateTimeValue = v;
await OnChange();
Close();
}
}
if (!Multiple)
{
await FocusAsync();
}
}
void ToggleSelectedDate(DateTime date)
{
date = date.Date;
var index = selectedDates.FindIndex(d => d.Date == date);
if (index >= 0)
{
selectedDates.RemoveAt(index);
}
else
{
selectedDates.Add(DateTime.SpecifyKind(date, Kind));
}
}
async Task UpdateValueFromSelectedDates(DateTime? lastSelected)
{
object? newValue = null;
var typeofTValue = typeof(TValue);
if (typeofTValue.IsArray && typeofTValue.GetElementType() == typeof(DateTime))
{
newValue = selectedDates.ToArray();
}
else if (typeof(IEnumerable<DateTime>).IsAssignableFrom(typeofTValue))
{
newValue = selectedDates.ToList();
}
else if (typeof(IEnumerable<DateTimeOffset?>).IsAssignableFrom(typeofTValue))
{
var list = new List<DateTimeOffset?>();
foreach (var d in selectedDates)
{
var kinded = DateTime.SpecifyKind(d, Kind);
list.Add(new DateTimeOffset(kinded));
}
newValue = list;
}
else if (typeof(IEnumerable<DateTime?>).IsAssignableFrom(typeofTValue))
{
newValue = selectedDates.Select(d => (DateTime?)d).ToList();
}
else if (typeof(IEnumerable<DateOnly>).IsAssignableFrom(typeofTValue))
{
newValue = selectedDates.Select(d => DateOnly.FromDateTime(d)).ToList();
}
else if (typeof(IEnumerable<DateOnly?>).IsAssignableFrom(typeofTValue))
{
newValue = selectedDates.Select(d => (DateOnly?)DateOnly.FromDateTime(d)).ToList();
}
else if (typeof(IEnumerable<TimeOnly?>).IsAssignableFrom(typeofTValue))
{
newValue = selectedDates.Select(d => (TimeOnly?)new TimeOnly(d.Hour, d.Minute, d.Second, d.Millisecond)).ToList();
}
else
{
newValue = selectedDates.ToList();
}
_value = newValue;
_dateTimeValue = lastSelected;
if (ValueChanged.HasDelegate)
{
await ValueChanged.InvokeAsync((TValue)(object)newValue);
}
if (FieldIdentifier.FieldName != null)
{
EditContext?.NotifyFieldChanged(FieldIdentifier);
}
await Change.InvokeAsync(_dateTimeValue);
StateHasChanged();
}
private void SetMonth(int calendarMonth)
{
var calendar = Culture.Calendar;
var calYear = calendar.GetYear(CurrentDate);
var calDay = Math.Min(calendar.GetDayOfMonth(CurrentDate), calendar.GetDaysInMonth(calYear, calendarMonth));
var newValue = new DateTime(calYear, calendarMonth, calDay, CurrentDate.Hour, CurrentDate.Minute, CurrentDate.Second, calendar);
CurrentDate = newValue;
}
private void SetYear(int calendarYear)
{
var calendar = Culture.Calendar;
var calMonth = calendar.GetMonth(CurrentDate);
if (calMonth > calendar.GetMonthsInYear(calendarYear))
{
calMonth = calendar.GetMonthsInYear(calendarYear);
}
var calDay = Math.Min(calendar.GetDayOfMonth(CurrentDate), calendar.GetDaysInMonth(calendarYear, calMonth));
var newValue = new DateTime(calendarYear, calMonth, calDay, CurrentDate.Hour, CurrentDate.Minute, CurrentDate.Second, calendar);
CurrentDate = newValue;
}
/// <summary>
/// Gets or sets the edit context.
/// </summary>
/// <value>The edit context.</value>
[CascadingParameter]
public EditContext? EditContext { get; set; }
/// <summary>
/// Gets the field identifier.
/// </summary>
/// <value>The field identifier.</value>
[Parameter]
public FieldIdentifier FieldIdentifier { get; set; }
/// <summary>
/// Gets or sets the value expression.
/// </summary>
/// <value>The value expression.</value>
[Parameter]
public Expression<Func<TValue>>? ValueExpression { get; set; }
/// <inheritdoc />
public override async Task SetParametersAsync(ParameterView parameters)
{
if (parameters.DidParameterChange(nameof(InitialViewDate), InitialViewDate) &&
(DateTimeValue == default(DateTime) || DateTimeValue == null))
{
_currentDate = default(DateTime);
}
if (parameters.DidParameterChange(nameof(Min), Min) || parameters.DidParameterChange(nameof(Max), Max))
{
var min = parameters.GetValueOrDefault<DateTime?>(nameof(Min));
var max = parameters.GetValueOrDefault<DateTime?>(nameof(Max));
UpdateYearsAndMonths(min, max);
}
var shouldClose = false;
if (parameters.DidParameterChange(nameof(Visible), Visible))
{
var visible = parameters.GetValueOrDefault<bool>(nameof(Visible));
shouldClose = !visible;
}
var disabledChanged = parameters.DidParameterChange(nameof(Disabled), Disabled);
await base.SetParametersAsync(parameters);
if (disabledChanged)
{
FormFieldContext?.DisabledChanged(Disabled);
}
if (shouldClose && !firstRender && IsJSRuntimeAvailable && JSRuntime != null)
{
await JSRuntime.InvokeVoidAsync("Radzen.destroyPopup", PopupID);
}
if (EditContext != null && ValueExpression != null && FieldIdentifier.Model != EditContext.Model)
{
FieldIdentifier = FieldIdentifier.Create(ValueExpression);
EditContext.OnValidationStateChanged -= ValidationStateChanged;
EditContext.OnValidationStateChanged += ValidationStateChanged;
}
}
bool firstRender;
/// <inheritdoc />
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
this.firstRender = firstRender;
if (Visible && !Disabled && !ReadOnly && !Inline && PopupRenderMode == PopupRenderMode.Initial && JSRuntime != null)
{
await JSRuntime.InvokeVoidAsync("Radzen.createDatePicker", Element, PopupID, Reference, nameof(OnPopupClose));
}
}
private void ValidationStateChanged(object? sender, ValidationStateChangedEventArgs e)
{
StateHasChanged();
}
/// <inheritdoc />
public override void Dispose()
{
base.Dispose();
if (EditContext != null)
{
EditContext.OnValidationStateChanged -= ValidationStateChanged;
}
Form?.RemoveComponent(this);
if (IsJSRuntimeAvailable && JSRuntime != null)
{
JSRuntime.InvokeVoid("Radzen.destroyPopup", PopupID);
if (UniqueID != null)
{
JSRuntime.InvokeVoid("Radzen.destroyDatePicker", UniqueID, Element);
}
}
GC.SuppressFinalize(this);
}
/// <summary>
/// Gets the value.
/// </summary>
/// <returns>System.Object.</returns>
public object? GetValue()
{
return Value;
}
private string PopupID
{
get
{
return $"popup{GetId()}";
}
}
Popup? popup;
/// <summary>
/// Gets or sets the render mode.
/// </summary>
/// <value>The render mode.</value>
[Parameter]
public PopupRenderMode PopupRenderMode { get; set; } = PopupRenderMode.Initial;
async Task OnToggle()
{
if (PopupRenderMode == PopupRenderMode.OnDemand && !Disabled && !ReadOnly && !Inline)
{
if (popup != null)
{
await popup.ToggleAsync(Element);
}
await FocusAsync();
}
}
DateTime FocusedDate { get; set; } = DateTime.Now;
string GetDayCssClass(DateTime date, DateRenderEventArgs dateArgs, bool forCell = true)
{
var list = ClassList.Create()
.Add("rz-state-default", !forCell)
.Add("rz-calendar-other-month", GetCalendarMonth(CurrentDate) != GetCalendarMonth(date))
.Add("rz-state-active", !forCell && (Multiple ? selectedDates.Any(d => d.Date == date.Date) : (DateTimeValue.HasValue && DateTimeValue.Value.Date.CompareTo(date.Date) == 0)))
.Add("rz-calendar-today", !forCell && DateTime.Now.Date.CompareTo(date.Date) == 0)
.Add("rz-state-focused", !forCell && FocusedDate.Date.CompareTo(date.Date) == 0)
.Add("rz-state-disabled", !forCell && dateArgs.Disabled);
if (dateArgs.Attributes != null && dateArgs.Attributes.TryGetValue("class", out var @class) && !string.IsNullOrEmpty(Convert.ToString(@class, CultureInfo.InvariantCulture)))
{
list.Add($"{@class}", true);
}
return list.ToString();
}
async Task OnCalendarKeyPress(KeyboardEventArgs args)
{
var key = args.Code != null ? args.Code : args.Key;
if (key == "ArrowLeft" || key == "ArrowRight")
{
preventKeyPress = true;
stopKeydownPropagation = true;
FocusedDate = FocusedDate.AddDays(key == "ArrowLeft" ? -1 : 1);
CurrentDate = FocusedDate;
}
else if (key == "ArrowUp" || key == "ArrowDown")
{
preventKeyPress = true;
stopKeydownPropagation = true;
FocusedDate = FocusedDate.AddDays(key == "ArrowUp" ? -7 : 7);
CurrentDate = FocusedDate;
}
else if (key == "Enter" || (key == "Space" && Multiple))
{
preventKeyPress = true;
stopKeydownPropagation = true;
if (!DateAttributes(FocusedDate).Disabled && !ReadOnly)
{
await SetDay(FocusedDate);
if (!Multiple)
{
await ClosePopup();
await FocusAsync();
}
}
}
else if (key == "Escape")
{
preventKeyPress = false;
stopKeydownPropagation = false;
await ClosePopup();
await FocusAsync();
}
else if (key == "Tab")
{
preventKeyPress = false;
stopKeydownPropagation = false;
await ClosePopup();
await FocusAsync();
}
else
{
preventKeyPress = false;
stopKeydownPropagation = false;
}
}
async Task OnPopupKeyDown(KeyboardEventArgs args)
{
var key = args.Code != null ? args.Code : args.Key;
if(key == "Escape")
{
preventKeyPress = false;
await ClosePopup();
await FocusAsync();
}
}
async Task OnKeyPress(KeyboardEventArgs args)
{
var key = args.Code != null ? args.Code : args.Key;
if (args.AltKey && key == "ArrowDown")
{
preventKeyPress = true;
stopKeydownPropagation = true;
if (PopupRenderMode == PopupRenderMode.Initial && JSRuntime != null)
{
await JSRuntime.InvokeVoidAsync("Radzen.openPopup", Element, PopupID, false, null, null, null, Reference, nameof(OnPopupClose), true, true);
}
else if (popup != null)
{
await popup.CloseAsync(Element);
await popup.ToggleAsync(Element);
}
}
else if (key == "Enter")
{
preventKeyPress = true;
stopKeydownPropagation = true;
await TogglePopup();
}
else if (key == "Escape")
{
preventKeyPress = false;
stopKeydownPropagation = false;
await ClosePopup();
await FocusAsync();
}
else
{
preventKeyPress = false;
stopKeydownPropagation = false;
}
}
internal async Task TogglePopup()
{
if (Inline) return;
if (PopupRenderMode == PopupRenderMode.Initial && JSRuntime != null)
{
await JSRuntime.InvokeVoidAsync("Radzen.togglePopup", Element, PopupID, false, Reference, nameof(OnPopupClose), true, true);
}
else if (popup != null)
{
await popup.ToggleAsync(Element);
}
}
async Task ClosePopup()
{
if (Inline) return;
RevertUncommittedTimeChange();
if (PopupRenderMode == PopupRenderMode.Initial && JSRuntime != null)
{
await JSRuntime.InvokeVoidAsync("Radzen.closePopup", PopupID);
}
else if (popup != null)
{
await popup.CloseAsync(Element);
}
}
bool preventKeyPress;
bool stopKeydownPropagation;
/// <inheritdoc/>
public async ValueTask FocusAsync()
{
// If ShowButton is false, the input box is always shown
if (ShowInput || !ShowButton)
{
try
{
await input.FocusAsync();
}
catch
{ }
}
}
}
}