using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading.Tasks; using AntDesign.core.Extensions; using AntDesign.Datepicker.Locale; using AntDesign.Internal; using AntDesign.JsInterop; using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; using OneOf; namespace AntDesign { public class DatePickerBase : AntInputComponentBase, IDatePicker { DateTime? IDatePicker.HoverDateTime { get; set; } public const int START_PICKER_INDEX = 0; public const int END_PICKER_INDEX = 0; [Parameter] public string PrefixCls { get; set; } = "ant-picker"; protected string _picker; protected bool _isSetPicker = false; private bool _isNullableEvaluated; private bool _isNullable; /// /// Stores information if TValue is a nullable type. /// protected bool IsNullable { get { if (!_isNullableEvaluated) { Type type = typeof(TValue); if (type.IsAssignableFrom(typeof(DateTime?)) || type.IsAssignableFrom(typeof(DateTime?[]))) { _isNullable = true; } else { _isNullable = false; } _isNullableEvaluated = true; } return _isNullable; } } [Parameter] public string Picker { get => _picker; set { _isSetPicker = true; _picker = value; InitPicker(value); } } [Parameter] public string PopupContainerSelector { get; set; } [Parameter] public OneOf Disabled { get; set; } = new bool[] { false, false }; [Parameter] public bool Bordered { get; set; } = true; [Parameter] public bool AutoFocus { get; set; } = false; [Parameter] public bool Open { get; set; } [Parameter] public bool InputReadOnly { get; set; } = false; [Parameter] public bool ShowToday { get; set; } = true; [Parameter] public DatePickerLocale Locale { get { return _locale; } set { _locale = value; _isLocaleSetOutside = true; } } [Parameter] public override CultureInfo CultureInfo { get { return base.CultureInfo; } set { if (!_isLocaleSetOutside && ( (base.CultureInfo != value && base.CultureInfo.Name != value.Name) || LocaleProvider.CurrentLocale.LocaleName != value.Name )) { _locale = LocaleProvider.GetLocale(value.Name).DatePicker; } base.CultureInfo = value; } } public bool IsShowTime { get; protected set; } public string ShowTimeFormat { get; protected set; } = "HH:mm:ss"; protected OneOf _showTime = null; private bool _timeFormatProvided; [Parameter] public OneOf ShowTime { get => _showTime; set { _showTime = value; value.Switch(booleanValue => { IsShowTime = booleanValue; }, strValue => { IsShowTime = true; _timeFormatProvided = true; ShowTimeFormat = strValue; }); } } [Parameter] public bool AllowClear { get; set; } = true; protected string[] _placeholders = new string[] { "", "" }; protected OneOf _placeholder; [Parameter] public OneOf Placeholder { get => _placeholder; set { _placeholder = value; value.Switch(single => { _placeholders[0] = single; }, arr => { _placeholders[0] = arr.Length > 0 ? arr[0] : _placeholders[0]; _placeholders[1] = arr.Length > 1 ? arr[1] : _placeholders[1]; }); } } [Parameter] public string PopupStyle { get; set; } [Parameter] public string ClassName { get; set; } [Parameter] public string DropdownClassName { get; set; } [Parameter] public string Format { get; set; } private TValue _defaultValue; [Parameter] public TValue DefaultValue { get => _defaultValue; set => _defaultValue = SortValue(value); } protected bool[] UseDefaultPickerValue { get; } = new bool[2]; private TValue _defaultPickerValue; [Parameter] public TValue DefaultPickerValue { get => _defaultPickerValue; set => _defaultPickerValue = SortValue(value); } [Parameter] public RenderFragment SuffixIcon { get; set; } [Parameter] public RenderFragment RenderExtraFooter { get; set; } /// /// Called when clear button clicked. /// [Parameter] public EventCallback OnClearClick { get; set; } [Parameter] public EventCallback OnOpenChange { get; set; } [Parameter] public EventCallback OnPanelChange { get; set; } [Parameter] public Func DisabledDate { get; set; } = null; [Parameter] public Func DisabledHours { get; set; } = null; [Parameter] public Func DisabledMinutes { get; set; } = null; [Parameter] public Func DisabledSeconds { get; set; } = null; [Parameter] public Func DisabledTime { get; set; } = null; [Parameter] public Func DateRender { get; set; } // TODO: need locale [Parameter] public Func MonthCellRender { get; set; } public DateTime CurrentDate { get; set; } = DateTime.Now; protected DateTime[] PickerValues { get; } = new DateTime[] { DateTime.Now, DateTime.Now }; public bool IsRange { get; set; } protected DatePickerInput _inputStart; protected DatePickerInput _inputEnd; protected OverlayTrigger _dropDown; protected bool _duringFocus; protected string _activeBarStyle = ""; protected string _rangeArrowStyle = ""; protected DatePickerStatus[] _pickerStatus = new DatePickerStatus[] { new DatePickerStatus(), new DatePickerStatus() }; protected Stack _prePickerStack = new Stack(); protected bool _isClose = true; protected bool _needRefresh; protected bool _duringManualInput; private bool _isLocaleSetOutside; private DatePickerLocale _locale = LocaleProvider.CurrentLocale.DatePicker; protected bool _openingOverlay; protected ClassMapper _panelClassMapper = new ClassMapper(); protected override void OnInitialized() { // set default picker type if (_isSetPicker == false) { Picker = DatePickerType.Date; } this.SetClass(); base.OnInitialized(); } protected bool _shouldRender = true; protected override bool ShouldRender() { if (!_shouldRender) { _shouldRender = true; return false; } return base.ShouldRender(); } public override Task SetParametersAsync(ParameterView parameters) { _needRefresh = true; return base.SetParametersAsync(parameters); } protected void SetClass() { this.ClassMapper.Clear() .Add(PrefixCls) .Get(() => $"{PrefixCls}-{Size}") .If($"{PrefixCls}-rtl", () => RTL) .If($"{PrefixCls}-borderless", () => Bordered == false) .If($"{PrefixCls}-disabled", () => IsDisabled() == true) .If($"{ClassName}", () => !string.IsNullOrEmpty(ClassName)) .If($"{PrefixCls}-range", () => IsRange == true) .If($"{PrefixCls}-focused", () => AutoFocus == true) //.If($"{PrefixCls}-normal", () => Image.IsT1 && Image.AsT1 == Empty.PRESENTED_IMAGE_SIMPLE) //.If($"{PrefixCls}-{Direction}", () => Direction.IsIn("ltr", "rlt")) ; _panelClassMapper .Add($"{PrefixCls}-panel") .If($"{PrefixCls}-panel-rtl", () => RTL); } protected async override Task OnAfterRenderAsync(bool firstRender) { await base.OnAfterRenderAsync(firstRender); if (firstRender) { await Js.InvokeVoidAsync(JSInteropConstants.AddPreventKeys, _inputStart.Ref, new[] { "ArrowUp", "ArrowDown" }); } if (_needRefresh && IsRange) { if (_inputStart.IsOnFocused) { HtmlElement element = await JsInvokeAsync(JSInteropConstants.GetDomInfo, _inputStart.Ref); _activeBarStyle = $"width: {element.ClientWidth - 10}px; position: absolute; transform: translate3d(0px, 0px, 0px);"; _rangeArrowStyle = $"left: 12px"; } else if (_inputEnd.IsOnFocused) { HtmlElement element = await JsInvokeAsync(JSInteropConstants.GetDomInfo, _inputEnd.Ref); int translateDistance = element.ClientWidth + 16; if (RTL) { translateDistance = -translateDistance; } _activeBarStyle = $"width: {element.ClientWidth - 10}px; position: absolute; transform: translate3d({translateDistance}px, 0px, 0px);"; _rangeArrowStyle = $"left: {element.ClientWidth + 30}px"; } else { _activeBarStyle = "display: none"; } StateHasChanged(); } _needRefresh = false; } protected override void Dispose(bool disposing) { base.Dispose(disposing); _ = InvokeAsync(async () => { await Js.InvokeVoidAsync(JSInteropConstants.RemovePreventKeys, _inputStart.Ref); }); } protected string GetInputValue(int index = 0) { DateTime? tryGetValue = GetIndexValue(index); if (tryGetValue == null) { return ""; } DateTime value = (DateTime)tryGetValue; return GetFormatValue(value, index); } protected void ChangeFocusTarget(bool inputStartFocus, bool inputEndFocus) { if (!IsRange) { return; } _duringManualInput = false; _needRefresh = true; _inputStart.IsOnFocused = inputStartFocus; _inputEnd.IsOnFocused = inputEndFocus; } protected virtual Task PickerClicked() { AutoFocus = true; return Task.CompletedTask; } protected virtual async Task OnSelect(DateTime date) { int index = GetOnFocusPickerIndex(); _duringManualInput = false; // InitPicker is the finally value if (_picker == _pickerStatus[index]._initPicker) { ChangeValue(date, index); // auto focus the other input if (IsRange && (!IsShowTime || Picker == DatePickerType.Time)) { await SwitchFocus(index); } else { await Focus(index); } } else { _picker = _prePickerStack.Pop(); } ChangePickerValue(date, index); } public async Task OnOkClick() { if (!IsRange) { Close(); return; } int index = GetOnFocusPickerIndex(); if (!(await SwitchFocus(index))) { Close(); } } private async Task SwitchFocus(int index) { if (index == 0 && !_pickerStatus[1]._currentShowHadSelectValue && !_inputEnd.IsOnFocused && !IsDisabled(1)) { await Blur(0); await Focus(1); } else if (index == 1 && !_pickerStatus[0]._currentShowHadSelectValue && !_inputStart.IsOnFocused && !IsDisabled(0)) { await Blur(1); await Focus(0); } else { await Focus(index); //keep focus on current input return false; } return true; } protected virtual async Task OnBlur(int index) { } protected void InitPicker(string picker) { if (string.IsNullOrEmpty(_pickerStatus[0]._initPicker)) { _pickerStatus[0]._initPicker = picker; } if (string.IsNullOrEmpty(_pickerStatus[1]._initPicker)) { _pickerStatus[1]._initPicker = picker; } if (IsRange) { (string first, string second) = DatePickerPlaceholder.GetRangePlaceHolderByType(picker, Locale); _placeholders[0] = first; _placeholders[1] = second; DateTime now = DateTime.Now; PickerValues[1] = picker switch { DatePickerType.Date => now.AddMonths(1), DatePickerType.Week => now.AddMonths(1), DatePickerType.Month => now.AddYears(1), DatePickerType.Decade => now.AddYears(1), DatePickerType.Quarter => now.AddYears(1), DatePickerType.Year => now.AddYears(10), _ => now, }; } else { string first = DatePickerPlaceholder.GetPlaceholderByType(picker, Locale); _placeholders[0] = first; _placeholders[1] = first; } } protected bool IsDisabled(int? index = null) { bool disabled = false; Disabled.Switch(single => { disabled = single; }, arr => { if (index == null || index > 1 || index < 0) { disabled = arr[0] && arr[1]; } else { disabled = arr[(int)index]; } }); return disabled; } public void Close() { _duringManualInput = false; _dropDown?.Hide(); } public async Task Focus(int index = 0) { DatePickerInput input = null; if (index == 0) { input = _inputStart; } else if (index == 1 && IsRange) { input = _inputEnd; } if (input != null) { input.IsOnFocused = true; await FocusAsync(input.Ref); _needRefresh = true; } } public async Task Blur(int index = 0) { DatePickerInput input = null; _duringManualInput = false; if (index == 0) { input = _inputStart; } else if (index == 1 && IsRange) { input = _inputEnd; } if (input != null) { input.IsOnFocused = false; await JsInvokeAsync(JSInteropConstants.Blur, input.Ref); _needRefresh = true; } } public int GetOnFocusPickerIndex() { if (_inputStart != null && _inputStart.IsOnFocused) { return 0; } if (_inputEnd != null && _inputEnd.IsOnFocused) { return 1; } return 0; } /// /// Get pickerValue by picker index. Note that index refers to a picker panel /// and not to input text. For RangePicker 2 inputs generate 2 panels. /// /// /// public DateTime GetIndexPickerValue(int index) { int tempIndex = GetOnFocusPickerIndex(); if (index == 0) { return PickerValues[tempIndex]; } else { //First picker panel will show the value, second panel shows next //expected value that depends on Picker type return Picker switch { DatePickerType.Date => (IsShowTime ? PickerValues[tempIndex] : PickerValues[tempIndex].AddMonths(1)), DatePickerType.Week => PickerValues[tempIndex].AddMonths(1), DatePickerType.Month => PickerValues[tempIndex].AddYears(1), DatePickerType.Decade => PickerValues[tempIndex].AddYears(1), DatePickerType.Quarter => PickerValues[tempIndex].AddYears(1), DatePickerType.Year => PickerValues[tempIndex].AddYears(10), _ => DateTime.Now, }; } } public void ChangePlaceholder(string placeholder, int index = 0) { _placeholders[index] = placeholder; StateHasChanged(); } private int _htmlInputSize; protected int HtmlInputSize { get { if (_htmlInputSize == 0) { _htmlInputSize = InternalFormat.Length + (int)(InternalFormat.Count(ch => ch > 127) * 1.34) + 2; if (_htmlInputSize < 12) { _htmlInputSize = 12; } } return _htmlInputSize; } } private string _internalFormat; private string InternalFormat { get { if (string.IsNullOrEmpty(_internalFormat)) { if (!string.IsNullOrEmpty(Format)) _internalFormat = Format; else _internalFormat = _pickerStatus[0]._initPicker switch { DatePickerType.Date => GetTimeFormat(), DatePickerType.Month => Locale.Lang.YearMonthFormat, DatePickerType.Year => Locale.Lang.YearFormat, DatePickerType.Time => Locale.Lang.TimeFormat, DatePickerType.Week => $"{Locale.Lang.YearFormat}-0{Locale.Lang.Week}", DatePickerType.Quarter => $"{Locale.Lang.YearFormat}-Q0", _ => Locale.Lang.DateFormat, }; } return _internalFormat; } } private string GetTimeFormat() { if (IsShowTime) { if (_timeFormatProvided) { return $"{Locale.Lang.DateFormat} {ShowTimeFormat}"; } return Locale.Lang.DateTimeFormat; } return Locale.Lang.DateFormat; } private FormatAnalyzer _formatAnalyzer; public FormatAnalyzer FormatAnalyzer => _formatAnalyzer ??= new(InternalFormat, Picker, Locale); public string GetFormatValue(DateTime value, int index) { string format; if (string.IsNullOrEmpty(Format)) format = _pickerStatus[index]._initPicker switch { DatePickerType.Week => $"{Locale.Lang.YearFormat}-{DateHelper.GetWeekOfYear(value, Locale.FirstDayOfWeek)}{Locale.Lang.Week}", DatePickerType.Quarter => $"{Locale.Lang.YearFormat}-{DateHelper.GetDayOfQuarter(value)}", _ => InternalFormat, }; else format = InternalFormat; return value.ToString(format, CultureInfo.InvariantCulture); } /// /// Changes what date(s) will be visible on the picker. /// /// New date to be saved. /// Index of the input box, where 0 = inputStart and 1 = inputEnd (only RangePicker) internal void ChangePickerValue(DateTime date, int? index = null) { if (index == null) index = GetOnFocusPickerIndex(); PickerValues[index.Value] = date; if (IsRange) { if (!UseDefaultPickerValue[1] && !_pickerStatus[1]._hadSelectValue && index == 0) { PickerValues[1] = date; } else if (!UseDefaultPickerValue[0] && !_pickerStatus[0]._hadSelectValue && index == 1) { PickerValues[0] = date; } } if (OnPanelChange.HasDelegate) { OnPanelChange.InvokeAsync(new DateTimeChangedEventArgs { Date = PickerValues[index.Value], DateString = _picker }); } StateHasChanged(); } public void ChangePickerType(string type) { ChangePickerType(type, 0); } public virtual void ChangePickerType(string type, int index) { _prePickerStack.Push(_picker); _picker = type; if (OnPanelChange.HasDelegate) { OnPanelChange.InvokeAsync(new DateTimeChangedEventArgs { Date = PickerValues[index], DateString = _picker }); } StateHasChanged(); } /// /// 修改值 /// /// /// public virtual void ChangeValue(DateTime value, int index = 0) { } public virtual void ClearValue(int index = 0, bool closeDropdown = true) { } public virtual DateTime? GetIndexValue(int index) { return null; } public void InvokeStateHasChanged() { StateHasChanged(); } protected TValue SortValue(TValue value) { if (value == null) { return value; } TValue orderedValue = value; if (IsRange) { if (IsNullable) { var tempValue = value as DateTime?[]; if (tempValue[0] == null || tempValue[1] == null) return orderedValue; if ((tempValue[0] ?? DateTime.Now).CompareTo((tempValue[1] ?? DateTime.Now)) > 0) orderedValue = DataConvertionExtensions.Convert(new DateTime?[] { tempValue[1], tempValue[0] }); } else { var tempValue = value as DateTime[]; if (tempValue[0].CompareTo(tempValue[1]) > 0) orderedValue = DataConvertionExtensions.Convert(new DateTime[] { tempValue[1], tempValue[0] }); } } return orderedValue; } } }