Skip to content

Commit

Permalink
fix(module: datepicker): validate manually entered date against format (
Browse files Browse the repository at this point in the history
#1389)

* fix(module:datepicker): validate manually entered date against format

* fix(module:datepicker): keep frozen the panel until valid date entered

* fix(module:datepicker): switch to current culture format

* fix(module:datepicker): build fix

* fix(module:datepicker): use InvariantCulture when calling ToString on date

* fix(module:datepicker): FormatAnalyzer handles also year, week, month & quarter picker

* fix(module:datepicker): FormatAnalyzer broken tests

* fix: FormatAnalyzer handles prefixes in format.

Suffix is handled for picker type = "year"
Tests include validation of not changing state

* fix: all modes go through format analyzing (inluding week&quarter)

* fix(module:datepickerbase): InternalFormat initalized properly for all modes

* fix(module:rangepicker): reset opposing date when in conflict with current

* fix(module:rangePicker): handle null in second part of range

* fix(module:datepicker): switch from BindConverter to partials

* tests(module:datepicker): FormatAnalyzer new tests to cover switch from BindConverter

* tests(module:datepicker): missed change in tests

* fix: focus, key events, reset value to original if not confirmed

* fix: bug fix on range & clean-up

* Update DatePicker.razor

Co-authored-by: James Yeung <shunjiey@hotmail.com>
  • Loading branch information
anddrzejb and ElderJames committed Apr 28, 2021
1 parent 669cb32 commit 9deb7bc
Show file tree
Hide file tree
Showing 11 changed files with 1,166 additions and 180 deletions.
1 change: 1 addition & 0 deletions components/core/JsInterop/JSInteropConstants.cs
Expand Up @@ -103,6 +103,7 @@ public static class JSInteropConstants
public static string SetDomAttribute => $"{FUNC_PREFIX}setDomAttribute";

public static string SetSelectionStart => $"{FUNC_PREFIX}setSelectionStart";
public static string InvokeTabKey => $"{FUNC_PREFIX}invokeTabKey";

#region Draggable Modal

Expand Down
18 changes: 18 additions & 0 deletions components/core/JsInterop/interop.ts
Expand Up @@ -692,3 +692,21 @@ export function setSelectionStart(element, position) {
}
}
}

//copied from https://www.telerik.com/forums/trigger-tab-key-when-enter-key-is-pressed
export function invokeTabKey() {
var currInput = document.activeElement;
if (currInput.tagName.toLowerCase() == "input") {
var inputs = document.getElementsByTagName("input");
var currInput = document.activeElement;
for (var i = 0; i < inputs.length; i++) {
if (inputs[i] == currInput) {
var next = inputs[i + 1];
if (next && next.focus) {
next.focus();
}
break;
}
}
}
}
95 changes: 68 additions & 27 deletions components/date-picker/DatePicker.Razor.cs
@@ -1,8 +1,8 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.JSInterop;

namespace AntDesign
{
Expand Down Expand Up @@ -51,6 +51,11 @@ private void ProcessDefaults()

private async Task OnInputClick()
{
if (_duringManualInput)
{
return;
}
AutoFocus = true;
//Reset Picker to default in case it the picker value was changed
//but no value was selected (for example when a user clicks next
//month but does not select any value)
Expand All @@ -72,6 +77,8 @@ private async Task OnInputClick()
}
}

private TValue _cacheDuringInput;

protected void OnInput(ChangeEventArgs args, int index = 0)
{
if (index != 0)
Expand All @@ -82,55 +89,68 @@ protected void OnInput(ChangeEventArgs args, int index = 0)
{
return;
}

if (BindConverter.TryConvertTo(args.Value.ToString(), CultureInfo, out TValue changeValue))
if (!_duringManualInput)
{
if (Picker == DatePickerType.Date)
{
if (IsDateStringFullDate(args.Value.ToString()))
CurrentValue = changeValue;
}
else
CurrentValue = changeValue;

_duringManualInput = true;
_cacheDuringInput = Value;
}
if (FormatAnalyzer.TryPickerStringConvert(args.Value.ToString(), out TValue changeValue, IsNullable))
{
Value = changeValue;
GetIfNotNull(changeValue, (notNullValue) =>
{
PickerValues[0] = notNullValue;
});

if (OnChange.HasDelegate)
StateHasChanged();
}

UpdateCurrentValueAsString();
}

protected override Task OnBlur(int index)
{
if (_duringManualInput)
{
if (!Value.Equals(_cacheDuringInput))
{
OnChange.InvokeAsync(new DateTimeChangedEventArgs
//reset picker to Value
Value = _cacheDuringInput;
_pickerStatus[0]._hadSelectValue = !(Value is null && (DefaultValue is not null || DefaultPickerValue is not null));
GetIfNotNull(Value ?? DefaultValue ?? DefaultPickerValue, (notNullValue) =>
{
Date = Convert.ToDateTime(changeValue, this.CultureInfo),
DateString = GetInputValue(index)
PickerValues[0] = notNullValue;
});
}

StateHasChanged();
_duringManualInput = false;
}

UpdateCurrentValueAsString();
if (_dropDown.IsOverlayShow())
Close();
AutoFocus = false;
return Task.CompletedTask;
}

/// <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 OnKeyUp(KeyboardEventArgs e)
protected async Task OnKeyDown(KeyboardEventArgs e)
{
if (e == null) throw new ArgumentNullException(nameof(e));

var key = e.Key.ToUpperInvariant();
if (key == "ENTER")
if (key == "ENTER" || key == "TAB")
{
_duringManualInput = false;
if (string.IsNullOrWhiteSpace(_inputStart.Value))
ClearValue();
else
await TryApplyInputValue();

if (key == "ENTER")
{
if (BindConverter.TryConvertTo(_inputStart.Value, CultureInfo, out TValue changeValue))
Value = changeValue;
Close();
//needed only in wasm, details: https://github.com/dotnet/aspnetcore/issues/30070
await Task.Yield();
await Js.InvokeVoidAsync(JSInteropConstants.InvokeTabKey);
}
}

Expand All @@ -144,6 +164,26 @@ protected async Task OnKeyUp(KeyboardEventArgs e)
}
}

private async Task TryApplyInputValue()
{
if (FormatAnalyzer.TryPickerStringConvert(_inputStart.Value, out TValue changeValue, IsNullable))
{
CurrentValue = changeValue;
GetIfNotNull(changeValue, (notNullValue) =>
{
PickerValues[0] = notNullValue;
});
if (OnChange.HasDelegate)
{
await OnChange.InvokeAsync(new DateTimeChangedEventArgs
{
Date = Convert.ToDateTime(changeValue, this.CultureInfo),
DateString = GetInputValue(0)
});
}
}
}

/// <summary>
/// Get value of the picker
/// </summary>
Expand Down Expand Up @@ -219,15 +259,16 @@ protected override void OnValueChange(TValue value)
_pickerStatus[0]._hadSelectValue = true;
}

public override void ClearValue(int index = 0)
public override void ClearValue(int index = 0, bool closeDropdown = true)
{
_isSetPicker = false;

if (!IsNullable && DefaultValue != null)
CurrentValue = DefaultValue;
else
CurrentValue = default;
Close();
if (closeDropdown)
Close();
}

private void GetIfNotNull(TValue value, Action<DateTime> notNullAction)
Expand Down
6 changes: 4 additions & 2 deletions components/date-picker/DatePicker.razor
Expand Up @@ -9,7 +9,7 @@
IsButton="@true"
Disabled="Disabled"
PopupContainerSelector="@PopupContainerSelector"
OnVisibleChange="visible => { AutoFocus = visible; OnOpenChange.InvokeAsync(visible); }"
OnVisibleChange="visible => OnOpenChange.InvokeAsync(visible)"
OverlayEnterCls="ant-slide-up-enter ant-slide-up-enter-active ant-slide-up"
OverlayLeaveCls="ant-slide-up-leave ant-slide-up-leave-active ant-slide-up"
Trigger="new TriggerType[] { TriggerType.Click }">
Expand All @@ -34,8 +34,10 @@
ReadOnly="@InputReadOnly"
AutoFocus="@AutoFocus"
OnClick="async e => { await OnInputClick(); }"
OnKeyUp="@OnKeyUp"
OnKeyDown="@OnKeyDown"
OnInput="e => OnInput(e, 0)"
OnBlur="e => OnBlur(0)"
Onfocus="e => AutoFocus = true"
ShowTime="@(Picker == DatePickerType.Time)"
OnClickClear="e => ClearValue(0)"
AllowClear="@AllowClear" />
Expand Down
12 changes: 7 additions & 5 deletions components/date-picker/RangePicker.razor
Expand Up @@ -9,7 +9,7 @@
IsButton="@true"
Disabled="Disabled"
PopupContainerSelector="@PopupContainerSelector"
OnVisibleChange="visible => { AutoFocus = visible; OnOpenChange.InvokeAsync(visible); }"
OnVisibleChange="visible => OnOpenChange.InvokeAsync(visible)"
OverlayEnterCls="ant-slide-up-enter ant-slide-up-enter-active ant-slide-up ant-picker-dropdown-range"
OverlayLeaveCls="ant-slide-up-leave ant-slide-up-leave-active ant-slide-up ant-picker-dropdown-range"
Trigger="new TriggerType[] { TriggerType.Click }">
Expand Down Expand Up @@ -49,13 +49,14 @@
AutoFocus="@AutoFocus"
OnClick="async e => { await OnInputClick(0); }"
OnInput="e => OnInput(e, 0)"
OnKeyUp="e => OnKeyUp(e, 0)"
OnKeyDown="e => OnKeyDown(e, 0)"
Onfocus="() => OnFocus(0)"
OnBlur="e => OnBlur(0)"
ShowTime="@(Picker == DatePickerType.Time)"
ShowSuffixIcon="false"
IsRange="@IsRange"
AllowClear="@AllowClear"
OnClickClear="e => ClearValue(0)" />
OnClickClear="e => ClearValue(-1)" />

<div class="@(PrefixCls)-range-separator">
<span aria-label="to" class="@(PrefixCls)-separator">
Expand All @@ -73,12 +74,13 @@
AutoFocus="@AutoFocus"
OnClick="async e => { await OnInputClick(1); }"
OnInput="e => OnInput(e, 1)"
OnKeyUp="e => OnKeyUp(e, 1)"
OnKeyDown="e => OnKeyDown(e, 1)"
Onfocus="() => OnFocus(1)"
OnBlur="e => OnBlur(1)"
ShowTime="@(Picker == DatePickerType.Time)"
IsRange="@IsRange"
AllowClear="@AllowClear"
OnClickClear="e => ClearValue(0)" />
OnClickClear="e => ClearValue(-1)" />
<div class="@(PrefixCls)-active-bar" style="@_activeBarStyle"></div>
</div>
</Unbound>
Expand Down

0 comments on commit 9deb7bc

Please sign in to comment.