ant-design-blazor/components/input/TextArea.razor.cs

338 lines
11 KiB
C#
Raw Normal View History

using System.Diagnostics;
using System.Text.Json;
using System.Threading.Tasks;
using AntDesign.JsInterop;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace AntDesign
{
public partial class TextArea : Input<string>
{
private const uint DEFAULT_MIN_ROWS = 1;
2020-04-24 18:32:50 +08:00
protected override string InputType => "textarea";
private uint _minRows = DEFAULT_MIN_ROWS;
private uint _maxRows = uint.MaxValue;
private bool _hasMinOrMaxSet;
private bool _hasMinSet;
private DotNetObjectReference<TextArea> _reference;
/// <summary>
/// Will adjust (grow or shrink) the `TextArea` according to content.
/// Can work in connection with `MaxRows` and `MinRows`.
/// Sets resize attribute of the textarea HTML element to: none.
/// </summary>
[Parameter]
public bool AutoSize
{
get => _autoSize;
set
{
if (_hasMinOrMaxSet && !value)
{
Debug.WriteLine("AntBlazor.TextArea: AutoSize cannot be set to false when either MinRows or MaxRows has been set.AutoSize has been switched to true.");
_autoSize = true;
}
else
{
_autoSize = value;
}
}
}
/// <summary>
/// When `false`, value will be set to `null` when content is empty
/// or whitespace. When `true`, value will be set to empty string.
/// </summary>
[Parameter]
public bool DefaultToEmptyString { get; set; }
/// <summary>
/// `TextArea` will allow growing, but it will stop when visible
/// rows = MaxRows (will not grow further).
/// Default value = uint.MaxValue
/// </summary>
[Parameter]
public uint MaxRows
{
get
{
return _maxRows;
}
set
{
_hasMinOrMaxSet = true;
if (value >= MinRows)
{
_maxRows = value;
Debug.WriteLineIf(!AutoSize, "AntBlazor.TextArea: AutoSize cannot be set to false when either MinRows or MaxRows has been set.AutoSize has been switched to true.");
AutoSize = true;
}
else
{
_maxRows = uint.MaxValue;
Debug.WriteLine($"AntBlazor.TextArea: Value of {nameof(MaxRows)}({MaxRows}) has to be between {nameof(MinRows)}({MinRows}) and {uint.MaxValue}");
}
}
}
/// <summary>
/// `TextArea` will allow shrinking, but it will stop when visible
/// rows = MinRows (will not shrink further).
/// Default value = DEFAULT_MIN_ROWS = 1
/// </summary>
[Parameter]
public uint MinRows
{
get
{
return _minRows;
}
set
{
_hasMinOrMaxSet = true;
_hasMinSet = true;
if (value >= DEFAULT_MIN_ROWS && value <= MaxRows)
{
_minRows = value;
Debug.WriteLineIf(!AutoSize, "AntBlazor.TextArea: AutoSize cannot be set to false when either MinRows or MaxRows has been set.AutoSize has been switched to true.");
AutoSize = true;
}
else
{
_minRows = DEFAULT_MIN_ROWS;
Debug.WriteLine($"AntBlazor.TextArea: Value of {nameof(MinRows)}({MinRows}) has to be between {DEFAULT_MIN_ROWS} and {nameof(MaxRows)}({MaxRows})");
}
}
}
/// <summary>
/// Sets the height of the TextArea expressed in number of rows.
/// Default value is 2.
/// </summary>
[Parameter]
public uint Rows { get; set; } = 2;
/// <summary>
/// Callback when the size changes
/// </summary>
[Parameter]
public EventCallback<OnResizeEventArgs> OnResize { get; set; }
/// <inheritdoc/>
[Parameter]
public override string Value
{
get => base.Value;
set
{
if (base.Value != value)
{
_valueHasChanged = true;
_inputString = value;
}
base.Value = value;
}
}
private uint InnerMinRows => _hasMinSet ? MinRows : Rows;
private string Count => $"{_inputString?.Length ?? 0}{(MaxLength > 0 ? $" / {MaxLength}" : "")}";
private string _inputString;
private ClassMapper _warpperClassMapper = new();
private ClassMapper _textareaClassMapper = new();
private bool _afterFirstRender = false;
protected override void OnInitialized()
{
base.OnInitialized();
_warpperClassMapper
.Add($"{PrefixCls}-affix-wrapper")
.If($"{PrefixCls}-affix-wrapper-textarea-with-clear-btn", () => AllowClear)
.If($"{PrefixCls}-affix-wrapper-has-feedback", () => FormItem?.HasFeedback == true)
.GetIf(() => $"{PrefixCls}-affix-wrapper-status-{FormItem?.ValidateStatus.ToString().ToLowerInvariant()}", () => FormItem is { ValidateStatus: not FormValidateStatus.Default })
.If($"{PrefixCls}-affix-wrapper-rtl", () => RTL);
ClassMapper
.Add("ant-input-textarea ")
.If("ant-input-textarea-show-count", () => ShowCount)
.If("ant-input-textarea-in-form-item", () => FormItem != null)
.If("ant-input-textarea-has-feedback", () => FormItem?.HasFeedback == true)
.GetIf(() => $"ant-input-textarea-status-{FormItem?.ValidateStatus.ToString().ToLowerInvariant()}", () => FormItem is { ValidateStatus: not FormValidateStatus.Default })
;
_textareaClassMapper
.Add("ant-input")
.GetIf(() => $"ant-input-status-{FormItem?.ValidateStatus.ToString().ToLowerInvariant()}", () => FormItem is { ValidateStatus: not FormValidateStatus.Default })
;
}
protected override void SetClasses()
{
// override the classmapper setting
}
protected override void OnParametersSet()
{
base.OnParametersSet();
if (_oldStyle != Style)
{
_styleHasChanged = true;
_oldStyle = Style;
}
}
protected override async Task OnFirstAfterRenderAsync()
{
await base.OnFirstAfterRenderAsync();
if (AutoSize)
{
perf: avoid memory leak issue of event listener (#1857) * perf: avoid memory leak #1834 Avoid memory leak by remove the exclusive parameter and logic in the code block on AddEventListener method in DomEventService class. The following are the components affected: components/affix/Affix.razor.cs components/anchor/Anchor.razor.cs components/carousel/Carousel.razor.cs components/core/Component/Overlay/Overlay.razor.cs components/core/Component/Overlay/OverlayTrigger.razor.cs components/core/JsInterop/DomEventService.cs components/descriptions/Descriptions.razor.cs components/dropdown/DropdownButton.cs components/grid/Row.razor.cs components/input/Input.cs components/input/TextArea.razor.cs components/layout/Sider.razor.cs components/list/ListItem.razor.cs components/select/Select.razor.cs components/select/internal/SelectContent.razor.cs components/slider/Slider.razor.cs components/table/Table.razor.cs components/tabs/Tabs.razor.cs * fix override AddEventListener method in AntDesign.TestKit project * add register/remove event listerner for exclusive use in DomEventService class * move _dotNetObjects to DomEventListerner class/service, so that users not required to maintain it in each component. * * move share/reuse dom event listerner methods to DomEventListerner class * remove method 'AddEventListener' that no longer exists in DomEventService class in AntDesign.TestKit project * * change the component referring to an IDomEventListerner interface instead of a concrete class, so that the component can be tested via a mock TestDomEventListerner. * introduce DisposeShared and Dispose method in DomEventListerner to ease user remove callback from DomEventListerner * register IDomEventListerner into DI container instead of create manually * fix FormatKey * fix FormatKey * fix tests * fix test * fix test Co-authored-by: James Yeung <shunjiey@hotmail.com>
2021-09-09 12:56:11 +08:00
DomEventListener.AddShared<JsonElement>("window", "beforeunload", Reloading);
}
await RegisterResizeEvents();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
if (firstRender)
{
_afterFirstRender = true;
}
if (_afterFirstRender)
{
if (AutoSize && _valueHasChanged)
{
_valueHasChanged = false;
if (_isInputing)
{
_isInputing = false;
}
else if (_afterFirstRender)
{
await JsInvokeAsync(JSInteropConstants.InputComponentHelper.ResizeTextArea, Ref, InnerMinRows, MaxRows);
}
}
if (_styleHasChanged)
{
_styleHasChanged = false;
if (AutoSize && !string.IsNullOrWhiteSpace(Style) && _afterFirstRender)
{
await JsInvokeAsync(JSInteropConstants.StyleHelper.SetStyle, Ref, Style);
}
}
}
}
/// <inheritdoc/>
protected override async Task OnInputAsync(ChangeEventArgs args)
{
_isInputing = true;
_inputString = args.Value.ToString();
await base.OnInputAsync(args);
}
protected override void OnCurrentValueChange(string value)
{
base.OnCurrentValueChange(value);
_inputString = value;
}
protected override bool TryParseValueFromString(string value, out string result, out string validationErrorMessage)
{
validationErrorMessage = null;
if (string.IsNullOrWhiteSpace(value))
{
if (DefaultToEmptyString)
result = string.Empty;
else
result = default;
return true;
}
result = value;
return true;
}
protected override void Dispose(bool disposing)
{
if (AutoSize && !_isReloading)
{
_reference?.Dispose();
DomEventListener?.Dispose();
_ = InvokeAsync(async () =>
{
await JsInvokeAsync(JSInteropConstants.DisposeResizeTextArea, Ref);
});
}
base.Dispose(disposing);
}
/// <summary>
/// Indicates that a page is being refreshed
/// </summary>
private bool _isReloading;
private bool _autoSize;
private bool _valueHasChanged;
private bool _isInputing;
private string _oldStyle;
private bool _styleHasChanged;
private string _heightStyle;
private void Reloading(JsonElement jsonElement) => _isReloading = true;
[JSInvokable]
public void ChangeSizeAsyncJs(float width, float height)
{
if (OnResize.HasDelegate)
OnResize.InvokeAsync(new OnResizeEventArgs { Width = width, Height = height });
}
private async Task RegisterResizeEvents()
{
if (_reference == null)
{
_reference = DotNetObjectReference.Create<TextArea>(this);
}
if (AutoSize)
{
await JsInvokeAsync<TextAreaInfo>(
JSInteropConstants.InputComponentHelper.RegisterResizeTextArea, Ref, InnerMinRows, MaxRows, _reference);
}
else
{
var textAreaInfo = await JsInvokeAsync<TextAreaInfo>(
JSInteropConstants.InputComponentHelper.GetTextAreaInfo, Ref);
var rowHeight = textAreaInfo.LineHeight;
var offsetHeight = textAreaInfo.PaddingTop + textAreaInfo.PaddingBottom
+ textAreaInfo.BorderTop + textAreaInfo.BorderBottom;
_heightStyle = $"height: {Rows * rowHeight + offsetHeight}px;overflow-y: auto;overflow-x: hidden;";
StateHasChanged();
}
}
internal class TextAreaInfo
{
public double ScrollHeight { get; set; }
public double LineHeight { get; set; }
public double PaddingTop { get; set; }
public double PaddingBottom { get; set; }
public double BorderTop { get; set; }
public double BorderBottom { get; set; }
}
}
}