From 29143db2a80baf5995ca77af187904de62224762 Mon Sep 17 00:00:00 2001 From: Alex Kryvdyk Date: Tue, 1 Nov 2022 06:14:27 +0200 Subject: [PATCH] fix(module: datepicker): tab key does not confirm the value (#2847) * fix:(module: datepicker): tab key does not confirm the value * fix:(module: datepicker): bad value when click on panel after key input Co-authored-by: James Yeung --- components/date-picker/DatePicker.Razor.cs | 62 +++----- components/date-picker/RangePicker.razor.cs | 139 ++++++++---------- .../date-picker/internal/DatePickerBase.cs | 33 ++++- .../DatePickerKeyboardInputTests.razor | 22 ++- .../DatePicker/RangePickerTests.razor | 49 ++++++ 5 files changed, 177 insertions(+), 128 deletions(-) diff --git a/components/date-picker/DatePicker.Razor.cs b/components/date-picker/DatePicker.Razor.cs index 2b0c41e1..c20156b9 100644 --- a/components/date-picker/DatePicker.Razor.cs +++ b/components/date-picker/DatePicker.Razor.cs @@ -80,8 +80,6 @@ namespace AntDesign } } - private DateTime? _cacheDuringInput; - protected void OnInput(ChangeEventArgs args, int index = 0) { if (index != 0) @@ -95,7 +93,6 @@ namespace AntDesign if (!_duringManualInput) { _duringManualInput = true; - _cacheDuringInput = GetIndexValue(0); } if (FormatAnalyzer.TryPickerStringConvert(args.Value.ToString(), out TValue changeValue, IsNullable)) @@ -112,18 +109,9 @@ namespace AntDesign { if (_openingOverlay) return; - - if (_duringManualInput) - { - if (Value is null && _pickerStatus[0].SelectedValue is not null || - !Convert.ToDateTime(Value, CultureInfo).Equals(_pickerStatus[0].SelectedValue)) - { - _pickerStatus[0].SelectedValue = null; - ChangePickerValue(_cacheDuringInput ?? _pickerValuesAfterInit); - } - _duringManualInput = false; - } AutoFocus = false; + if (!_dropDown.IsOverlayShow()) + _pickerStatus[0].SelectedValue = null; await Task.Yield(); } @@ -136,23 +124,23 @@ namespace AntDesign if (e == null) throw new ArgumentNullException(nameof(e)); var key = e.Key.ToUpperInvariant(); - if (key == "ENTER" || key == "TAB" || key == "ESCAPE") + var isEnter = key == "ENTER"; + var isTab = key == "TAB"; + var isEscape = key == "ESCAPE"; + var isOverlayShown = _dropDown.IsOverlayShow(); + + if (isEnter || isTab || isEscape) { _duringManualInput = false; - if (key == "ESCAPE" && _dropDown.IsOverlayShow()) + if (isEscape && isOverlayShown) { Close(); await Js.FocusAsync(_inputStart.Ref); } - else if (key == "ENTER") + else if (isEnter || isTab) { - if (string.IsNullOrEmpty(_inputStart.Value)) - { - if (!_dropDown.IsOverlayShow()) - await _dropDown.Show(); - } - else if (HasTimeInput && _pickerStatus[0].SelectedValue is not null) + if (HasTimeInput && _pickerStatus[0].SelectedValue is not null) { await OnOkClick(); } @@ -160,33 +148,25 @@ namespace AntDesign { await OnSelect(_pickerStatus[0].SelectedValue.Value, 0); } - else + else if (isOverlayShown) { - if (!_dropDown.IsOverlayShow()) - await _dropDown.Show(); - else - Close(); + Close(); + } + else if (!isTab) + { + await _dropDown.Show(); } - } - else if (key == "TAB") - { - Close(); - AutoFocus = false; - } - } - else if (key == "ARROWDOWN") - { - if (!_dropDown.IsOverlayShow()) - await _dropDown.Show(); } else if (key == "ARROWUP") { - if (_dropDown.IsOverlayShow()) + if (isOverlayShown) Close(); } - else if (!_dropDown.IsOverlayShow()) + else if (!isOverlayShown) + { await _dropDown.Show(); + } } /// diff --git a/components/date-picker/RangePicker.razor.cs b/components/date-picker/RangePicker.razor.cs index 06a96fbc..909cece2 100644 --- a/components/date-picker/RangePicker.razor.cs +++ b/components/date-picker/RangePicker.razor.cs @@ -1,11 +1,9 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using AntDesign.Core.Extensions; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; -using Microsoft.JSInterop; namespace AntDesign { @@ -67,9 +65,6 @@ namespace AntDesign private bool ShowRanges => Ranges?.Count > 0; - private DateTime? _cacheDuringInput; - private DateTime _pickerValueCache; - public RangePicker() { IsRange = true; @@ -92,6 +87,11 @@ namespace AntDesign return false; } + if (_pickerStatus[index.Value].SelectedValue is null) + { + return false; + } + DateTime? value = GetIndexValue(index.Value); if (value is null) @@ -200,12 +200,9 @@ namespace AntDesign if (!_duringManualInput) { _duringManualInput = true; - _cacheDuringInput = GetIndexValue(index); - _pickerValueCache = PickerValues[index]; } - if (FormatAnalyzer.TryPickerStringConvert(args.Value.ToString(), out DateTime parsedValue, false) - && IsValidRange(parsedValue, index)) + if (FormatAnalyzer.TryPickerStringConvert(args.Value.ToString(), out DateTime parsedValue, false)) { _pickerStatus[index].SelectedValue = parsedValue; ChangePickerValue(parsedValue, index); @@ -222,7 +219,13 @@ namespace AntDesign if (e == null) throw new ArgumentNullException(nameof(e)); var key = e.Key.ToUpperInvariant(); - if (key == "ENTER" || key == "TAB" || key == "ESCAPE") + + var isEnter = key == "ENTER"; + var isTab = key == "TAB"; + var isEscape = key == "ESCAPE"; + var isOverlayShown = _dropDown.IsOverlayShow(); + + if (isEnter || isTab || isEscape) { if (_duringManualInput) { @@ -235,14 +238,9 @@ namespace AntDesign } var input = (index == 0 ? _inputStart : _inputEnd); - if (key == "ENTER") + if (isEnter || isTab) { - if (string.IsNullOrEmpty(input.Value)) - { - if (!_dropDown.IsOverlayShow()) - await _dropDown.Show(); - } - else if (HasTimeInput && _pickerStatus[index].SelectedValue is not null) + if (HasTimeInput && _pickerStatus[index].SelectedValue is not null) { await OnOkClick(); } @@ -250,47 +248,43 @@ namespace AntDesign { await OnSelect(_pickerStatus[index].SelectedValue.Value, index); } - else + else if (isOverlayShown) { - if (!_dropDown.IsOverlayShow()) - await _dropDown.Show(); - else + if (_pickerStatus[index].SelectedValue is null && _pickerStatus[index].IsValueSelected) { - if (_pickerStatus[index].SelectedValue is null && _pickerStatus[index].IsValueSelected) - { - _pickerStatus[index].SelectedValue = GetIndexValue(index); - } - if (!await SwitchFocus(index)) - Close(); + _pickerStatus[index].SelectedValue = GetIndexValue(index); } + if (isTab || !await SwitchFocus(index)) + { + Close(); + + if (isTab && index == 1) + { + AutoFocus = false; + } + } + + } + else if (!isTab) + { + await _dropDown.Show(); } } - else if (key == "TAB" && index == 1) - { - if (_dropDown.IsOverlayShow()) - Close(); - AutoFocus = false; - } - else if (key == "ESCAPE" && _dropDown.IsOverlayShow()) + else if (isEscape && isOverlayShown) { Close(); await Js.FocusAsync(input.Ref); } } - else if (key == "ARROWDOWN") - { - if (!_dropDown.IsOverlayShow()) - await _dropDown.Show(); - } else if (key == "ARROWUP") { - if (_dropDown.IsOverlayShow()) + if (isOverlayShown) { Close(); AutoFocus = true; } } - else if (!_dropDown.IsOverlayShow()) + else if (!isOverlayShown) await _dropDown.Show(); } @@ -333,20 +327,7 @@ namespace AntDesign { return; } - - if (_duringManualInput) - { - var array = Value as Array; - - if (array.GetValue(index) is null && _pickerStatus[index].SelectedValue is not null || - !Convert.ToDateTime(array.GetValue(index), CultureInfo).Equals(_pickerStatus[index].SelectedValue)) - { - _pickerStatus[index].SelectedValue = null; - ChangePickerValue(_cacheDuringInput ?? _pickerValuesAfterInit[index]); - } - _duringManualInput = false; - } - + _duringManualInput = false; AutoFocus = false; } @@ -390,6 +371,21 @@ namespace AntDesign /// public override DateTime? GetIndexValue(int index) { + if (_pickerStatus[index].SelectedValue is null) + { + var isFocused = index == 0 && _inputStart?.IsOnFocused == true || + index == 1 && _inputEnd?.IsOnFocused == true; + + DateTime? currentValue; + + if (isFocused && (currentValue = GetValue(index)) is not null + && _pickerStatus[Math.Abs(index - 1)].SelectedValue is not null + && !IsValidRange(currentValue.Value, index)) + { + return null; + } + } + if (_pickerStatus[index].SelectedValue is not null) { return _pickerStatus[index].SelectedValue; @@ -397,15 +393,7 @@ namespace AntDesign if (Value != null) { - var array = Value as Array; - var indexValue = array.GetValue(index); - - if (indexValue == null) - { - return null; - } - - return Convert.ToDateTime(indexValue, CultureInfo); + return GetValue(index); } else if (!IsTypedValueNull(DefaultValue, index, out var defaultValue)) { @@ -414,6 +402,19 @@ namespace AntDesign return null; } + private DateTime? GetValue(int index) + { + var array = Value as Array; + var indexValue = array.GetValue(index); + + if (indexValue == null) + { + return null; + } + + return Convert.ToDateTime(indexValue, CultureInfo); + } + private static bool IsTypedValueNull(TValue value, int index, out DateTime? outValue) { outValue = (DateTime?)(value as Array)?.GetValue(index); @@ -614,15 +615,5 @@ namespace AntDesign await Focus(); await OnInputClick(0); } - - private bool IsValidRange(DateTime newValue, int newValueIndex) - { - return newValueIndex switch - { - 0 when newValue > GetIndexValue(1) => false, - 1 when newValue < GetIndexValue(0) => false, - _ => true - }; - } } } diff --git a/components/date-picker/internal/DatePickerBase.cs b/components/date-picker/internal/DatePickerBase.cs index 6d9f9235..52a74af7 100644 --- a/components/date-picker/internal/DatePickerBase.cs +++ b/components/date-picker/internal/DatePickerBase.cs @@ -466,12 +466,12 @@ namespace AntDesign if (IsRange) { - var otherIndex = Math.Abs(index - 1); - if (!HasTimeInput) { - if (GetIndexValue(otherIndex) is not null) + if (IsValidRange(date, index)) { + var otherIndex = Math.Abs(index - 1); + if (_pickerStatus[otherIndex].SelectedValue is not null) { ChangeValue(_pickerStatus[otherIndex].SelectedValue.Value, otherIndex, closeDropdown); @@ -521,7 +521,8 @@ namespace AntDesign var otherIndex = Math.Abs(index - 1); var otherValue = GetIndexValue(otherIndex); - if (_pickerStatus[index].SelectedValue is not null && otherValue is not null) + if (_pickerStatus[index].SelectedValue is not null && otherValue is not null + && IsValidRange(_pickerStatus[index].SelectedValue.Value, index)) { if (_pickerStatus[otherIndex].SelectedValue is not null) { @@ -577,7 +578,6 @@ namespace AntDesign } else { - await Focus(index); //keep focus on current input return false; } @@ -932,13 +932,16 @@ namespace AntDesign if (IsRange) { - var otherIndex = Math.Abs(index - 1); - _pickerStatus[otherIndex].SelectedValue = null; + _pickerStatus[Math.Abs(index - 1)].SelectedValue = null; if (!visible) { ResetPlaceholder(); } + else + { + _pickerStatus[index].SelectedValue = GetIndexValue(index); + } } OverlayVisibleChanged?.Invoke(this, visible); @@ -975,6 +978,22 @@ namespace AntDesign } } + protected bool IsValidRange(DateTime newValue, int newValueIndex) + { + var otherValue = GetIndexValue(Math.Abs(newValueIndex - 1)); + + if (otherValue is null) + { + return false; + } + + return newValueIndex switch + { + 0 when newValue > otherValue => false, + 1 when newValue < otherValue => false, + _ => true + }; + } internal void OnNowClick() { ChangeValue(DateTime.Now, GetOnFocusPickerIndex()); diff --git a/tests/AntDesign.Tests/DatePicker/DatePickerKeyboardInputTests.razor b/tests/AntDesign.Tests/DatePicker/DatePickerKeyboardInputTests.razor index 15be38cc..caa73783 100644 --- a/tests/AntDesign.Tests/DatePicker/DatePickerKeyboardInputTests.razor +++ b/tests/AntDesign.Tests/DatePicker/DatePickerKeyboardInputTests.razor @@ -3,8 +3,13 @@ @inherits AntDesignTestBase @code { - [Fact] - public void Enter_applies_input_to_value() + + [Theory] + [InlineData("Enter", true)] + [InlineData("Tab", true)] + [InlineData("Enter", false)] + [InlineData("Tab", false)] + public void Key_applies_input_to_value(string key, bool showTime) { //Arrange JSInterop.SetupVoid(JSInteropConstants.AddPreventKeys, _ => true); @@ -13,14 +18,20 @@ DateTime value = DateTime.MinValue; DateTime defaultValue = new DateTime(2020,1,1); DateTime expectedValue = new DateTime(2020,1,2); - string expectedValueAsString = expectedValue.ToString("yyyy-MM-dd"); + + if (showTime) + { + expectedValue = expectedValue.AddHours(10).AddMinutes(30).AddSeconds(5); + } + + string expectedValueAsString = expectedValue.ToString(showTime ? "yyyy-MM-dd HH:mm:ss" : "yyyy-MM-dd"); var cut = Render>( - @ + @ ); //Act var input = cut.Find("input"); input.Input(expectedValueAsString); - input.KeyDown("ENTER"); + input.KeyDown(key); //Assert cut.Instance.Value.Should().Be(expectedValue); value.Should().Be(expectedValue); @@ -145,5 +156,4 @@ selectedCell.GetAttribute("title").Should().Be(expectedValueAsString); selectedCell.Children[0].TextContent.Trim().Should().Be(expectedValue.Day.ToString()); } - } \ No newline at end of file diff --git a/tests/AntDesign.Tests/DatePicker/RangePickerTests.razor b/tests/AntDesign.Tests/DatePicker/RangePickerTests.razor index 53fb549f..834c30c6 100644 --- a/tests/AntDesign.Tests/DatePicker/RangePickerTests.razor +++ b/tests/AntDesign.Tests/DatePicker/RangePickerTests.razor @@ -200,4 +200,53 @@ cut.Instance.Value[1].Should().Be(expectedEnd); input[1].GetAttribute("value").Should().Be(endValueAsString); } + + [Theory] + [InlineData("Enter", true)] + [InlineData("Tab", true)] + [InlineData("Enter", false)] + [InlineData("Tab", false)] + public async Task Key_applies_input_to_value(string key, bool showTime) + { + //Arrange + JSInterop.SetupVoid(JSInteropConstants.AddPreventKeys, _ => true); + JSInterop.SetupVoid(JSInteropConstants.InvokeTabKey, _ => true); + + var range = new DateTime[] + { + new DateTime(2022, 1, 1, 0, 0, 0), + new DateTime(2022, 1, 1, 1, 0, 0) + }; + + var expectedStart = new DateTime(2022, 6, 8, 0, 0, 0); + var expectedEnd = new DateTime(2022, 6, 9, 0, 0, 0); + + if (showTime) + { + expectedStart = expectedStart.AddHours(20).AddMinutes(30).AddSeconds(5); + expectedEnd = expectedEnd.AddHours(1).AddMinutes(50).AddSeconds(5); + } + + var format = showTime ? "yyyy-MM-dd HH:mm:ss" : "yyyy-MM-dd"; + string startValueAsString = expectedStart.ToString(format); + string endValueAsString = expectedEnd.ToString(format); + + //Act + var cut = Render> + (@ ); + + //Act + var input = cut.FindAll("input"); + await input[0].InputAsync(new ChangeEventArgs() { Value = startValueAsString }); + await input[0].KeyDownAsync(new KeyboardEventArgs() { Key = key }); + await input[1].InputAsync(new ChangeEventArgs() { Value = endValueAsString }); + await input[1].KeyDownAsync(new KeyboardEventArgs() { Key = key }); + + //Assert + input = cut.FindAll("input"); + range[0].Should().Be(expectedStart); + input[0].GetAttribute("value").Should().Be(startValueAsString); + range[1].Should().Be(expectedEnd); + input[1].GetAttribute("value").Should().Be(endValueAsString); + } }