using System.Diagnostics.CodeAnalysis; using Connected.Annotations; using Connected.Services; using Connected.Utilities; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; namespace Connected.Components; public partial class Picker : FormComponent { protected IKeyInterceptor _keyInterceptor; public Picker() : base(new Converter()) { } protected Picker(Converter converter) : base(converter) { } [Inject] private IKeyInterceptorFactory _keyInterceptorFactory { get; set; } private string _elementId = "picker" + Guid.NewGuid().ToString().Substring(0, 8); [Inject] private IBrowserWindowSizeProvider WindowSizeListener { get; set; } protected string PickerClass => new CssBuilder("mud-picker") .AddClass($"mud-picker-inline", PickerVariant != PickerVariant.Static) .AddClass($"mud-picker-static", PickerVariant == PickerVariant.Static) .AddClass($"mud-rounded", PickerVariant == PickerVariant.Static && !_pickerSquare) .AddClass($"mud-elevation-{_pickerElevation}", PickerVariant == PickerVariant.Static) .AddClass($"mud-picker-input-button", !Editable && PickerVariant != PickerVariant.Static) .AddClass($"mud-picker-input-text", Editable && PickerVariant != PickerVariant.Static) .AddClass($"mud-disabled", Disabled && PickerVariant != PickerVariant.Static) .AddClass(Class) .Build(); protected string PickerPaperClass => new CssBuilder("mud-picker") .AddClass("mud-picker-paper") .AddClass("mud-picker-view", PickerVariant == PickerVariant.Inline) .AddClass("mud-picker-open", IsOpen && PickerVariant == PickerVariant.Inline) .AddClass("mud-picker-popover-paper", PickerVariant == PickerVariant.Inline) .AddClass("mud-dialog", PickerVariant == PickerVariant.Dialog) .Build(); protected string PickerInlineClass => new CssBuilder("mud-picker-inline-paper") .Build(); protected string PickerContainerClass => new CssBuilder("mud-picker-container") .AddClass("mud-paper-square", _pickerSquare) .AddClass("mud-picker-container-landscape", Orientation == Orientation.Landscape && PickerVariant == PickerVariant.Static) .Build(); protected string PickerInputClass => new CssBuilder("mud-input-input-control").AddClass(Class) .Build(); protected string ActionClass => new CssBuilder("mud-picker-actions") .AddClass(ClassActions) .Build(); /// /// Sets the icon of the input text field /// [ExcludeFromCodeCoverage] [Parameter] [Obsolete("Use AdornmentIcon instead.", true)] public string InputIcon { get { return AdornmentIcon; } set { AdornmentIcon = value; } } /// /// The color of the adornment if used. It supports the theme colors. /// [Parameter] [Category(CategoryTypes.FormComponent.Appearance)] public ThemeColor AdornmentColor { get; set; } = ThemeColor.Default; /// /// Sets the icon of the input text field /// [Parameter] [Category(CategoryTypes.FormComponent.Behavior)] public string AdornmentIcon { get; set; } = Icons.Material.Filled.Event; /// /// Sets the aria-label of the input text field icon /// [Parameter] [Category(CategoryTypes.FormComponent.Appearance)] public string AdornmentAriaLabel { get; set; } = string.Empty; /// /// The short hint displayed in the input before the user enters a value. /// [Parameter] [Category(CategoryTypes.FormComponent.Behavior)] public string Placeholder { get; set; } /// /// Fired when the dropdown / dialog opens /// [Parameter] public EventCallback PickerOpened { get; set; } /// /// Fired when the dropdown / dialog closes /// [Parameter] public EventCallback PickerClosed { get; set; } /// /// The higher the number, the heavier the drop-shadow. 0 for no shadow set to 8 by default in inline mode and 0 in static mode. /// [Parameter] [Category(CategoryTypes.FormComponent.PickerAppearance)] public int Elevation { set; get; } = 8; /// /// If true, border-radius is set to 0 this is set to true automatically in static mode but can be overridden with Rounded bool. /// [Parameter] [Category(CategoryTypes.FormComponent.PickerAppearance)] public bool Square { get; set; } /// /// If true, no date or time can be defined. /// [Parameter] [Category(CategoryTypes.FormComponent.Behavior)] public bool ReadOnly { get; set; } /// /// If true, border-radius is set to theme default when in Static Mode. /// [Parameter] [Category(CategoryTypes.FormComponent.PickerAppearance)] public bool Rounded { get; set; } /// /// If string has value, HelperText will be applied. /// [Parameter] [Category(CategoryTypes.FormComponent.Behavior)] public string HelperText { get; set; } /// /// If true, the helper text will only be visible on focus. /// [Parameter] [Category(CategoryTypes.FormComponent.Behavior)] public bool HelperTextOnFocus { get; set; } /// /// If string has value the label text will be displayed in the input, and scaled down at the top if the input has value. /// [Parameter] [Category(CategoryTypes.FormComponent.Behavior)] public string Label { get; set; } /// /// Show clear button. /// [Parameter] [Category(CategoryTypes.FormComponent.Behavior)] public bool Clearable { get; set; } = false; /// /// If true, the picker will be disabled. /// [Parameter] [Category(CategoryTypes.FormComponent.Behavior)] public bool Disabled { get; set; } /// /// If true, the picker will be editable. /// [Parameter] [Category(CategoryTypes.FormComponent.Behavior)] public bool Editable { get; set; } = false; /// /// Hide toolbar and show only date/time views. /// [Parameter] [Category(CategoryTypes.FormComponent.PickerAppearance)] public bool DisableToolbar { get; set; } /// /// User class names for picker's ToolBar, separated by space /// [Parameter] [Category(CategoryTypes.FormComponent.PickerAppearance)] public string ToolBarClass { get; set; } /// /// Picker container option /// [Parameter] [Category(CategoryTypes.FormComponent.Behavior)] public PickerVariant PickerVariant { get; set; } = PickerVariant.Inline; /// /// Variant of the text input /// [ExcludeFromCodeCoverage] [Parameter] [Obsolete("Use Variant instead.", true)] public Variant InputVariant { get { return Variant; } set { Variant = value; } } /// /// Variant of the text input /// [Parameter] [Category(CategoryTypes.FormComponent.Appearance)] public Variant Variant { get; set; } = Variant.Text; /// /// Sets if the icon will be att start or end, set to false to disable. /// [Parameter] [Category(CategoryTypes.FormComponent.Behavior)] public Adornment Adornment { get; set; } = Adornment.End; /// /// What orientation to render in when in PickerVariant Static Mode. /// [Parameter] [Category(CategoryTypes.FormComponent.PickerAppearance)] public Orientation Orientation { get; set; } = Orientation.Portrait; /// /// Sets the Icon Size. /// [Parameter] [Category(CategoryTypes.FormComponent.Appearance)] public Size IconSize { get; set; } = Size.Medium; /// /// The color of the toolbar, selected and active. It supports the theme colors. /// [Parameter] [Category(CategoryTypes.FormComponent.PickerAppearance)] public ThemeColor Color { get; set; } = ThemeColor.Primary; /// /// Changes the cursor appearance. /// [Obsolete("This is enabled now by default when you use Editable=true. You can remove the parameter.", false)] [Parameter] public bool AllowKeyboardInput { get; set; } /// /// Fired when the text changes. /// [Parameter] public EventCallback TextChanged { get; set; } /// /// The currently selected string value (two-way bindable) /// [Parameter] [Category(CategoryTypes.FormComponent.Data)] public string Text { get => _text; set => SetTextAsync(value, true).AndForget(); } private string _text; /// /// CSS class that will be applied to the action buttons container /// [Parameter] [Category(CategoryTypes.FormComponent.PickerAppearance)] public string ClassActions { get; set; } /// /// Define the action buttons here /// [Parameter] [Category(CategoryTypes.FormComponent.PickerBehavior)] public RenderFragment> PickerActions { get; set; } /// /// Will adjust vertical spacing. /// [Parameter] [Category(CategoryTypes.FormComponent.Appearance)] public Margin Margin { get; set; } = Margin.None; /// /// A mask for structured input of the date (requires Editable to be true). /// [Parameter] [Category(CategoryTypes.FormComponent.Behavior)] public IMask Mask { get => _mask; set => _mask = value; } /// /// Gets or sets the origin of the popover's anchor. Defaults to /// [Parameter] [Category(CategoryTypes.Popover.Appearance)] public Origin AnchorOrigin { get; set; } = Origin.TopLeft; /// /// Gets or sets the origin of the popover's transform. Defaults to /// [Parameter] [Category(CategoryTypes.Popover.Appearance)] public Origin TransformOrigin { get; set; } = Origin.TopLeft; protected IMask _mask = null; protected async Task SetTextAsync(string value, bool callback) { if (_text != value) { _text = value; if (callback) await StringValueChanged(_text); await TextChanged.InvokeAsync(_text); } } /// /// Value change hook for descendants. /// protected virtual Task StringValueChanged(string value) { return Task.CompletedTask; } protected bool IsOpen { get; set; } public void ToggleOpen() { if (IsOpen) Close(); else Open(); } public void Close(bool submit = true) { IsOpen = false; if (submit) { Submit(); } OnClosed(); StateHasChanged(); } public void Open() { IsOpen = true; StateHasChanged(); OnOpened(); } private void CloseOverlay() => Close(PickerActions == null); protected internal virtual void Submit() { } public virtual void Clear(bool close = true) { if (close && PickerVariant != PickerVariant.Static) { Close(false); } } protected override void ResetValue() { _inputReference?.Reset(); base.ResetValue(); } protected internal TextField _inputReference; public virtual ValueTask FocusAsync() => _inputReference?.FocusAsync() ?? ValueTask.CompletedTask; public virtual ValueTask BlurAsync() => _inputReference?.BlurAsync() ?? ValueTask.CompletedTask; public virtual ValueTask SelectAsync() => _inputReference?.SelectAsync() ?? ValueTask.CompletedTask; public virtual ValueTask SelectRangeAsync(int pos1, int pos2) => _inputReference?.SelectRangeAsync(pos1, pos2) ?? ValueTask.CompletedTask; private bool _pickerSquare; private int _pickerElevation; private ElementReference _pickerInlineRef; protected override void OnInitialized() { if (PickerVariant == PickerVariant.Static) { IsOpen = true; if (Elevation == 8) { _pickerElevation = 0; } else { _pickerElevation = Elevation; } if (!Rounded) { _pickerSquare = true; } } else { _pickerSquare = Square; _pickerElevation = Elevation; } if (Label == null && For != null) Label = For.GetLabelString(); } private async Task EnsureKeyInterceptor() { if (_keyInterceptor == null) { _keyInterceptor = _keyInterceptorFactory.Create(); await _keyInterceptor.Connect(_elementId, new KeyInterceptorOptions() { //EnableLogging = true, TargetClass = "mud-input-slot", Keys = { new KeyOptions { Key = " ", PreventDown = "key+none" }, new KeyOptions { Key = "ArrowUp", PreventDown = "key+none" }, new KeyOptions { Key = "ArrowDown", PreventDown = "key+none" }, new KeyOptions { Key = "Enter", PreventDown = "key+none" }, new KeyOptions { Key = "NumpadEnter", PreventDown = "key+none" }, new KeyOptions { Key = "/./", SubscribeDown = true, SubscribeUp = true }, // for our users }, }); _keyInterceptor.KeyDown += HandleKeyDown; } } protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender == true) { await EnsureKeyInterceptor(); } await base.OnAfterRenderAsync(firstRender); } protected internal void ToggleState() { if (Disabled) return; if (IsOpen) { IsOpen = false; OnClosed(); } else { IsOpen = true; OnOpened(); FocusAsync(); } } protected virtual async void OnOpened() { OnPickerOpened(); if (PickerVariant == PickerVariant.Inline) { await _pickerInlineRef.ChangeCssAsync(PickerInlineClass); } await EnsureKeyInterceptor(); await _keyInterceptor.UpdateKey(new() { Key = "Escape", StopDown = "key+none" }); } protected virtual async void OnClosed() { OnPickerClosed(); await EnsureKeyInterceptor(); await _keyInterceptor.UpdateKey(new() { Key = "Escape", StopDown = "none" }); } protected virtual void OnPickerOpened() { PickerOpened.InvokeAsync(this); } protected virtual void OnPickerClosed() { PickerClosed.InvokeAsync(this); } protected internal virtual void HandleKeyDown(KeyboardEventArgs obj) { if (Disabled || ReadOnly) return; switch (obj.Key) { case "Backspace": if (obj.CtrlKey == true && obj.ShiftKey == true) { Clear(); _value = default(T); Reset(); } break; case "Escape": case "Tab": Close(false); break; } } protected override void Dispose(bool disposing) { base.Dispose(disposing); if (disposing == true) { if (_keyInterceptor != null) { _keyInterceptor.KeyDown -= HandleKeyDown; _keyInterceptor.Dispose(); } } } }