diff --git a/src/Connected.Components/Components/Dialog/Dialog.razor.cs b/src/Connected.Components/Components/Dialog/Dialog.razor.cs index fd7f3ec..a3c5574 100644 --- a/src/Connected.Components/Components/Dialog/Dialog.razor.cs +++ b/src/Connected.Components/Components/Dialog/Dialog.razor.cs @@ -10,176 +10,127 @@ namespace Connected.Components; public partial class Dialog : UIComponent { - protected string ContentClass => new CssBuilder("dialog-content") - .AddClass($"dialog-no-side-padding", DisableSidePadding) - .AddClass(ClassContent) - .Build(); - - protected string ActionClass => new CssBuilder("dialog-actions") - .AddClass(ClassActions) - .Build(); - - [CascadingParameter] private DialogInstance DialogInstance { get; set; } - - [Inject] public IDialogService DialogService { get; set; } - - /// - /// Define the dialog title as a renderfragment (overrides GlyphTitle) - /// - [Parameter] - [Category(CategoryTypes.Dialog.Behavior)] - public RenderFragment TitleContent { get; set; } - - /// - /// Define the dialog body here - /// - [Parameter] - [Category(CategoryTypes.Dialog.Behavior)] - public RenderFragment DialogContent { get; set; } - - /// - /// Define the action buttons here - /// - [Parameter] - [Category(CategoryTypes.Dialog.Behavior)] - public RenderFragment DialogActions { get; set; } - - /// - /// Default options to pass to Show(), if none are explicitly provided. - /// Typically useful on inline dialogs. - /// - [Parameter] - [Category(CategoryTypes.Dialog.Misc)] // Behavior and Appearance - public DialogOptions Options { get; set; } - - [Parameter] - [Category(CategoryTypes.Dialog.Behavior)] - public Action OnBackdropClick { get; set; } - - /// - /// No padding at the sides - /// - [Parameter] - [Category(CategoryTypes.Dialog.Appearance)] - public bool DisableSidePadding { get; set; } - - /// - /// CSS class that will be applied to the dialog content - /// - [Parameter] - [Category(CategoryTypes.Dialog.Appearance)] - public string ClassContent { get; set; } - - /// - /// CSS class that will be applied to the action buttons container - /// - [Parameter] - [Category(CategoryTypes.Dialog.Appearance)] - public string ClassActions { get; set; } - - /// - /// CSS styles to be applied to the dialog content - /// - [Parameter] - [Category(CategoryTypes.Dialog.Appearance)] - public string ContentStyle { get; set; } - - /// - /// Bind this two-way to show and close an inlined dialog. Has no effect on opened dialogs - /// - [Parameter] - [Category(CategoryTypes.Dialog.Behavior)] - public bool IsVisible - { - get => _isVisible; - set - { - if (_isVisible == value) - return; - _isVisible = value; - IsVisibleChanged.InvokeAsync(value); - } - } - private bool _isVisible; - - /// - /// Raised when the inline dialog's display status changes. - /// - [Parameter] public EventCallback IsVisibleChanged { get; set; } - - - /// - /// Define the dialog title as a renderfragment (overrides GlyphTitle) - /// - [Parameter] - [Category(CategoryTypes.Dialog.Behavior)] - public DefaultFocus DefaultFocus { get; set; } - - private bool IsInline => DialogInstance == null; - - private IDialogReference _reference; - - /// - /// Show this inlined dialog - /// - /// - /// - /// - public IDialogReference Show(string title = null, DialogOptions options = null) - { - if (!IsInline) - throw new InvalidOperationException("You can only show an inlined dialog."); - if (_reference != null) - Close(); - var parameters = new DialogParameters() - { - [nameof(AdditionalClassList)] = AdditionalClassList, - [nameof(Tag)] = Tag, - [nameof(TitleContent)] = TitleContent, - [nameof(DialogContent)] = DialogContent, - [nameof(DialogActions)] = DialogActions, - [nameof(DisableSidePadding)] = DisableSidePadding, - [nameof(ClassContent)] = ClassContent, - [nameof(ClassActions)] = ClassActions, - [nameof(ContentStyle)] = ContentStyle, - }; - _reference = DialogService.Show(title, parameters, options ?? Options); - _reference.Result.ContinueWith(t => - { - _isVisible = false; - InvokeAsync(() => IsVisibleChanged.InvokeAsync(false)); - }); - return _reference; - } - - protected override void OnAfterRender(bool firstRender) - { - if (IsInline) - { - if (_isVisible && _reference == null) - { - Show(); // if isVisible and we don't have any reference we need to call Show - } - else if (_reference != null) - { - if (IsVisible) - (_reference.Dialog as Dialog)?.ForceUpdate(); // forward render update to instance - else - Close(); // if we still have reference but it's not visible call Close - } - } - base.OnAfterRender(firstRender); - } - - /// - /// Used for forwarding state changes from inlined dialog to its instance - /// - internal void ForceUpdate() - { - StateHasChanged(); - } - - /// + #region Variables + [CascadingParameter] private DialogInstance DialogInstance { get; set; } + [Inject] public IDialogService DialogService { get; set; } + private IDialogReference _reference; + #endregion + + #region Events + [Parameter] + [Category(CategoryTypes.Dialog.Behavior)] + public Action OnBackdropClick { get; set; } + + /// + /// Raised when the inline dialog's display status changes. + /// + [Parameter] public EventCallback IsVisibleChanged { get; set; } + #endregion + + #region Content + + /// + /// Define the dialog title as a renderfragment (overrides GlyphTitle) + /// + [Parameter] + [Category(CategoryTypes.Dialog.Behavior)] + public DefaultFocus DefaultFocus { get; set; } + + /// + /// Define the dialog title as a renderfragment (overrides GlyphTitle) + /// + [Parameter] + [Category(CategoryTypes.Dialog.Behavior)] + public RenderFragment TitleContent { get; set; } + + /// + /// Define the dialog body here + /// + [Parameter] + [Category(CategoryTypes.Dialog.Behavior)] + public RenderFragment DialogContent { get; set; } + + /// + /// Define the action buttons here + /// + [Parameter] + [Category(CategoryTypes.Dialog.Behavior)] + public RenderFragment DialogActions { get; set; } + #endregion + + #region Styling + /// + /// No padding at the sides + /// + [Parameter] + [Category(CategoryTypes.Dialog.Appearance)] + public bool DisableSidePadding { get; set; } + /// + /// CSS class that will be applied to the dialog content + /// + [Parameter] + [Category(CategoryTypes.Dialog.Appearance)] + public string ClassContent { get; set; } + + /// + /// CSS class that will be applied to the action buttons container + /// + [Parameter] + [Category(CategoryTypes.Dialog.Appearance)] + public string ClassActions { get; set; } + + /// + /// CSS styles to be applied to the dialog content + /// + [Parameter] + [Category(CategoryTypes.Dialog.Appearance)] + public string ContentStyle { get; set; } + + /// + /// Bind this two-way to show and close an inlined dialog. Has no effect on opened dialogs + /// + [Parameter] + [Category(CategoryTypes.Dialog.Behavior)] + public bool IsVisible + { + get => _isVisible; + set + { + if (_isVisible == value) + return; + _isVisible = value; + IsVisibleChanged.InvokeAsync(value); + } + } + private bool _isVisible; + protected string ContentClass => new CssBuilder("dialog-content") + .AddClass($"dialog-no-side-padding", DisableSidePadding) + .AddClass(ClassContent) + .Build(); + + protected string ActionClass => new CssBuilder("dialog-actions") + .AddClass(ClassActions) + .Build(); + #endregion + + #region Behavior + /// + /// Used for forwarding state changes from inlined dialog to its instance + /// + internal void ForceUpdate() + { + StateHasChanged(); + } + + /// + /// Default options to pass to Show(), if none are explicitly provided. + /// Typically useful on inline dialogs. + /// + [Parameter] + [Category(CategoryTypes.Dialog.Misc)] // Behavior and Appearance + public DialogOptions Options { get; set; } + + /// /// Close the currently open inlined dialog /// /// @@ -191,9 +142,69 @@ public partial class Dialog : UIComponent _reference = null; } - protected override void OnInitialized() - { - base.OnInitialized(); - DialogInstance?.Register(this); - } + private bool IsInline => DialogInstance == null; + + #endregion + + #region Lifecycle + + protected override void OnInitialized() + { + base.OnInitialized(); + DialogInstance?.Register(this); + } + + /// + /// Show this inlined dialog + /// + /// + /// + /// + public IDialogReference Show(string title = null, DialogOptions options = null) + { + if (!IsInline) + throw new InvalidOperationException("You can only show an inlined dialog."); + if (_reference != null) + Close(); + var parameters = new DialogParameters() + { + [nameof(AdditionalClassList)] = AdditionalClassList, + [nameof(Tag)] = Tag, + [nameof(TitleContent)] = TitleContent, + [nameof(DialogContent)] = DialogContent, + [nameof(DialogActions)] = DialogActions, + [nameof(DisableSidePadding)] = DisableSidePadding, + [nameof(ClassContent)] = ClassContent, + [nameof(ClassActions)] = ClassActions, + [nameof(ContentStyle)] = ContentStyle, + }; + _reference = DialogService.Show(title, parameters, options ?? Options); + _reference.Result.ContinueWith(t => + { + _isVisible = false; + InvokeAsync(() => IsVisibleChanged.InvokeAsync(false)); + }); + return _reference; + } + + protected override void OnAfterRender(bool firstRender) + { + if (IsInline) + { + if (_isVisible && _reference == null) + { + Show(); // if isVisible and we don't have any reference we need to call Show + } + else if (_reference != null) + { + if (IsVisible) + (_reference.Dialog as Dialog)?.ForceUpdate(); // forward render update to instance + else + Close(); // if we still have reference but it's not visible call Close + } + } + base.OnAfterRender(firstRender); + } + #endregion + } diff --git a/src/Connected.Components/Components/Dialog/DialogInstance.razor.cs b/src/Connected.Components/Components/Dialog/DialogInstance.razor.cs index cccaf94..90e215b 100644 --- a/src/Connected.Components/Components/Dialog/DialogInstance.razor.cs +++ b/src/Connected.Components/Components/Dialog/DialogInstance.razor.cs @@ -12,29 +12,52 @@ namespace Connected.Components; public partial class DialogInstance : UIComponent, IDisposable { + #region Variables private DialogOptions _options = new(); private string _elementId = "dialog_" + Guid.NewGuid().ToString().Substring(0, 8); private IKeyInterceptor _keyInterceptor; - [Inject] private IKeyInterceptorFactory _keyInterceptorFactory { get; set; } - - [CascadingParameter(Name = "RightToLeft")] public bool RightToLeft { get; set; } [CascadingParameter] private DialogProvider Parent { get; set; } [CascadingParameter] private DialogOptions GlobalDialogOptions { get; set; } = new DialogOptions(); + private Dialog _dialog; + private bool _disposedValue; + #endregion - [Parameter] - [Category(CategoryTypes.Dialog.Misc)] // Behavior and Appearance - public DialogOptions Options + #region Events + private void HandleBackgroundClick() { - get + if (DisableBackdropClick) + return; + + if (_dialog?.OnBackdropClick == null) { - if (_options == null) - _options = new DialogOptions(); - return _options; + Cancel(); + return; } - set => _options = value; + + _dialog?.OnBackdropClick.Invoke(); } + /// + /// Cancels all dialogs in dialog provider collection. + /// + public void CancelAll() + { + Parent?.DismissAll(); + } + + public void Register(Dialog dialog) + { + if (dialog == null) + return; + _dialog = dialog; + //AdditionalClassList = dialog.AdditionalClassList; + TitleContent = dialog.TitleContent; + StateHasChanged(); + } + #endregion + + #region Content [Parameter] [Category(CategoryTypes.Dialog.Behavior)] public string Title { get; set; } @@ -46,10 +69,129 @@ public partial class DialogInstance : UIComponent, IDisposable [Parameter] [Category(CategoryTypes.Dialog.Behavior)] public RenderFragment Content { get; set; } - [Parameter] [Category(CategoryTypes.Dialog.Behavior)] public Guid Id { get; set; } + #endregion + + #region Styling + public void ForceRender() + { + StateHasChanged(); + } + private string SetPosition() + { + DialogPosition position; + + if (Options.Position.HasValue) + { + position = Options.Position.Value; + } + else if (GlobalDialogOptions.Position.HasValue) + { + position = GlobalDialogOptions.Position.Value; + } + else + { + position = DialogPosition.Center; + } + return $"dialog-{position.ToDescription()}"; + } + + + + private bool SetHideHeader() + { + if (Options.NoHeader.HasValue) + return Options.NoHeader.Value; + + if (GlobalDialogOptions.NoHeader.HasValue) + return GlobalDialogOptions.NoHeader.Value; + + return false; + } + + private bool SetCloseButton() + { + if (Options.CloseButton.HasValue) + return Options.CloseButton.Value; + + if (GlobalDialogOptions.CloseButton.HasValue) + return GlobalDialogOptions.CloseButton.Value; + + return false; + } + + private bool SetDisableBackdropClick() + { + if (Options.DisableBackdropClick.HasValue) + return Options.DisableBackdropClick.Value; + + if (GlobalDialogOptions.DisableBackdropClick.HasValue) + return GlobalDialogOptions.DisableBackdropClick.Value; + + return false; + } + + private bool SetCloseOnEscapeKey() + { + if (Options.CloseOnEscapeKey.HasValue) + return Options.CloseOnEscapeKey.Value; + + if (GlobalDialogOptions.CloseOnEscapeKey.HasValue) + return GlobalDialogOptions.CloseOnEscapeKey.Value; + + return false; + } + private string SetMaxWidth() + { + MaxWidth maxWidth; + + if (Options.MaxWidth.HasValue) + { + maxWidth = Options.MaxWidth.Value; + } + else if (GlobalDialogOptions.MaxWidth.HasValue) + { + maxWidth = GlobalDialogOptions.MaxWidth.Value; + } + else + { + maxWidth = MaxWidth.Small; + } + return $"dialog-width-{maxWidth.ToDescription()}"; + } + + private bool SetFullWidth() + { + if (Options.FullWidth.HasValue) + return Options.FullWidth.Value; + + if (GlobalDialogOptions.FullWidth.HasValue) + return GlobalDialogOptions.FullWidth.Value; + + return false; + } + + private bool SetFulScreen() + { + if (Options.FullScreen.HasValue) + return Options.FullScreen.Value; + + if (GlobalDialogOptions.FullScreen.HasValue) + return GlobalDialogOptions.FullScreen.Value; + + return false; + } + + protected string Classname => + new CssBuilder("dialog") + .AddClass(DialogMaxWidth, !FullScreen) + .AddClass("dialog-width-full", FullWidth && !FullScreen) + .AddClass("dialog-fullscreen", FullScreen) + .AddClass("dialog-rtl", RightToLeft) + .AddClass(_dialog?.AdditionalClassList) + .Build(); /// /// Custom close icon. @@ -66,35 +208,22 @@ public partial class DialogInstance : UIComponent, IDisposable private bool CloseButton { get; set; } private bool FullScreen { get; set; } private bool FullWidth { get; set; } - - - protected override void OnInitialized() - { - ConfigureInstance(); - } - - protected override async Task OnAfterRenderAsync(bool firstRender) + [CascadingParameter(Name = "RightToLeft")] public bool RightToLeft { get; set; } + [Parameter] + [Category(CategoryTypes.Dialog.Misc)] // Behavior and Appearance + public DialogOptions Options { - if (firstRender) + get { - //Since CloseOnEscapeKey is the only thing to be handled, turn interceptor off - if (CloseOnEscapeKey) - { - _keyInterceptor = _keyInterceptorFactory.Create(); - - await _keyInterceptor.Connect(_elementId, new KeyInterceptorOptions() - { - TargetClass = "dialog", - Keys = { - new KeyOptions { Key="Escape", SubscribeDown = true }, - }, - }); - _keyInterceptor.KeyDown += HandleKeyDown; - } + if (_options == null) + _options = new DialogOptions(); + return _options; } - await base.OnAfterRenderAsync(firstRender); + set => _options = value; } + #endregion + #region Behavior internal void HandleKeyDown(KeyboardEventArgs args) { switch (args.Key) @@ -175,157 +304,34 @@ public partial class DialogInstance : UIComponent, IDisposable //AdditionalClassList = Classname; } - private string SetPosition() - { - DialogPosition position; - - if (Options.Position.HasValue) - { - position = Options.Position.Value; - } - else if (GlobalDialogOptions.Position.HasValue) - { - position = GlobalDialogOptions.Position.Value; - } - else - { - position = DialogPosition.Center; - } - return $"dialog-{position.ToDescription()}"; - } - - private string SetMaxWidth() - { - MaxWidth maxWidth; - - if (Options.MaxWidth.HasValue) - { - maxWidth = Options.MaxWidth.Value; - } - else if (GlobalDialogOptions.MaxWidth.HasValue) - { - maxWidth = GlobalDialogOptions.MaxWidth.Value; - } - else - { - maxWidth = MaxWidth.Small; - } - return $"dialog-width-{maxWidth.ToDescription()}"; - } - - private bool SetFullWidth() - { - if (Options.FullWidth.HasValue) - return Options.FullWidth.Value; - - if (GlobalDialogOptions.FullWidth.HasValue) - return GlobalDialogOptions.FullWidth.Value; - - return false; - } - - private bool SetFulScreen() - { - if (Options.FullScreen.HasValue) - return Options.FullScreen.Value; - - if (GlobalDialogOptions.FullScreen.HasValue) - return GlobalDialogOptions.FullScreen.Value; - - return false; - } - - protected string Classname => - new CssBuilder("dialog") - .AddClass(DialogMaxWidth, !FullScreen) - .AddClass("dialog-width-full", FullWidth && !FullScreen) - .AddClass("dialog-fullscreen", FullScreen) - .AddClass("dialog-rtl", RightToLeft) - .AddClass(_dialog?.AdditionalClassList) - .Build(); - - private bool SetHideHeader() - { - if (Options.NoHeader.HasValue) - return Options.NoHeader.Value; - - if (GlobalDialogOptions.NoHeader.HasValue) - return GlobalDialogOptions.NoHeader.Value; - - return false; - } - - private bool SetCloseButton() - { - if (Options.CloseButton.HasValue) - return Options.CloseButton.Value; - - if (GlobalDialogOptions.CloseButton.HasValue) - return GlobalDialogOptions.CloseButton.Value; - - return false; - } - - private bool SetDisableBackdropClick() - { - if (Options.DisableBackdropClick.HasValue) - return Options.DisableBackdropClick.Value; - - if (GlobalDialogOptions.DisableBackdropClick.HasValue) - return GlobalDialogOptions.DisableBackdropClick.Value; + #endregion - return false; - } - - private bool SetCloseOnEscapeKey() + #region Lifecycle + protected override void OnInitialized() { - if (Options.CloseOnEscapeKey.HasValue) - return Options.CloseOnEscapeKey.Value; - - if (GlobalDialogOptions.CloseOnEscapeKey.HasValue) - return GlobalDialogOptions.CloseOnEscapeKey.Value; - - return false; + ConfigureInstance(); } - private void HandleBackgroundClick() + protected override async Task OnAfterRenderAsync(bool firstRender) { - if (DisableBackdropClick) - return; - - if (_dialog?.OnBackdropClick == null) + if (firstRender) { - Cancel(); - return; - } - - _dialog?.OnBackdropClick.Invoke(); - } - - private Dialog _dialog; - private bool _disposedValue; - - public void Register(Dialog dialog) - { - if (dialog == null) - return; - _dialog = dialog; - //AdditionalClassList = dialog.AdditionalClassList; - TitleContent = dialog.TitleContent; - StateHasChanged(); - } - - public void ForceRender() - { - StateHasChanged(); - } + //Since CloseOnEscapeKey is the only thing to be handled, turn interceptor off + if (CloseOnEscapeKey) + { + _keyInterceptor = _keyInterceptorFactory.Create(); - /// - /// Cancels all dialogs in dialog provider collection. - /// - public void CancelAll() - { - Parent?.DismissAll(); + await _keyInterceptor.Connect(_elementId, new KeyInterceptorOptions() + { + TargetClass = "dialog", + Keys = { + new KeyOptions { Key="Escape", SubscribeDown = true }, + }, + }); + _keyInterceptor.KeyDown += HandleKeyDown; + } + } + await base.OnAfterRenderAsync(firstRender); } protected virtual void Dispose(bool disposing) @@ -350,4 +356,6 @@ public partial class DialogInstance : UIComponent, IDisposable Dispose(disposing: true); GC.SuppressFinalize(this); } + #endregion + } diff --git a/src/Connected.Components/Components/Dialog/DialogProvider.razor.cs b/src/Connected.Components/Components/Dialog/DialogProvider.razor.cs index b44d2fd..c72afc6 100644 --- a/src/Connected.Components/Components/Dialog/DialogProvider.razor.cs +++ b/src/Connected.Components/Components/Dialog/DialogProvider.razor.cs @@ -15,94 +15,101 @@ namespace Connected.Components; public partial class DialogProvider : IDisposable { - [Inject] private IDialogService DialogService { get; set; } - [Inject] private NavigationManager NavigationManager { get; set; } - - [Parameter][Category(CategoryTypes.Dialog.Behavior)] public bool? NoHeader { get; set; } - [Parameter][Category(CategoryTypes.Dialog.Behavior)] public bool? CloseButton { get; set; } - [Parameter][Category(CategoryTypes.Dialog.Behavior)] public bool? DisableBackdropClick { get; set; } - [Parameter][Category(CategoryTypes.Dialog.Behavior)] public bool? CloseOnEscapeKey { get; set; } - [Parameter][Category(CategoryTypes.Dialog.Appearance)] public bool? FullWidth { get; set; } - [Parameter][Category(CategoryTypes.Dialog.Appearance)] public DialogPosition? Position { get; set; } - [Parameter][Category(CategoryTypes.Dialog.Appearance)] public MaxWidth? MaxWidth { get; set; } - - private readonly Collection _dialogs = new(); - private readonly DialogOptions _globalDialogOptions = new(); - - protected override void OnInitialized() - { - DialogService.OnDialogInstanceAdded += AddInstance; - DialogService.OnDialogCloseRequested += DismissInstance; - NavigationManager.LocationChanged += LocationChanged; - - _globalDialogOptions.DisableBackdropClick = DisableBackdropClick; - _globalDialogOptions.CloseOnEscapeKey = CloseOnEscapeKey; - _globalDialogOptions.CloseButton = CloseButton; - _globalDialogOptions.NoHeader = NoHeader; - _globalDialogOptions.Position = Position; - _globalDialogOptions.FullWidth = FullWidth; - _globalDialogOptions.MaxWidth = MaxWidth; - } - - protected override Task OnAfterRenderAsync(bool firstRender) - { - if (!firstRender) - { - foreach (var dialogReference in _dialogs.Where(x => !x.Result.IsCompleted)) - { - dialogReference.RenderCompleteTaskCompletionSource.TrySetResult(true); - } - } - - return base.OnAfterRenderAsync(firstRender); - } - - internal void DismissInstance(Guid id, DialogResult result) - { - var reference = GetDialogReference(id); - if (reference != null) - DismissInstance(reference, result); - } - - private void AddInstance(IDialogReference dialog) - { - _dialogs.Add(dialog); - StateHasChanged(); - } - - public void DismissAll() - { - _dialogs.ToList().ForEach(r => DismissInstance(r, DialogResult.Cancel())); - StateHasChanged(); - } - - private void DismissInstance(IDialogReference dialog, DialogResult result) - { - if (!dialog.Dismiss(result)) return; - - _dialogs.Remove(dialog); - StateHasChanged(); - } - - private IDialogReference GetDialogReference(Guid id) - { - return _dialogs.SingleOrDefault(x => x.Id == id); - } - - private void LocationChanged(object sender, LocationChangedEventArgs args) - { - DismissAll(); - } - - public void Dispose() - { - if (NavigationManager != null) - NavigationManager.LocationChanged -= LocationChanged; - - if (DialogService != null) - { - DialogService.OnDialogInstanceAdded -= AddInstance; - DialogService.OnDialogCloseRequested -= DismissInstance; - } - } + + #region Variables + [Inject] private IDialogService DialogService { get; set; } + [Inject] private NavigationManager NavigationManager { get; set; } + [Parameter][Category(CategoryTypes.Dialog.Behavior)] public bool? NoHeader { get; set; } + [Parameter][Category(CategoryTypes.Dialog.Behavior)] public bool? CloseButton { get; set; } + [Parameter][Category(CategoryTypes.Dialog.Behavior)] public bool? DisableBackdropClick { get; set; } + [Parameter][Category(CategoryTypes.Dialog.Behavior)] public bool? CloseOnEscapeKey { get; set; } + [Parameter][Category(CategoryTypes.Dialog.Appearance)] public bool? FullWidth { get; set; } + [Parameter][Category(CategoryTypes.Dialog.Appearance)] public DialogPosition? Position { get; set; } + [Parameter][Category(CategoryTypes.Dialog.Appearance)] public MaxWidth? MaxWidth { get; set; } + + private readonly Collection _dialogs = new(); + private readonly DialogOptions _globalDialogOptions = new(); + #endregion + + #region Events + private void AddInstance(IDialogReference dialog) + { + _dialogs.Add(dialog); + StateHasChanged(); + } + internal void DismissInstance(Guid id, DialogResult result) + { + var reference = GetDialogReference(id); + if (reference != null) + DismissInstance(reference, result); + } + + public void DismissAll() + { + _dialogs.ToList().ForEach(r => DismissInstance(r, DialogResult.Cancel())); + StateHasChanged(); + } + + private void DismissInstance(IDialogReference dialog, DialogResult result) + { + if (!dialog.Dismiss(result)) return; + + _dialogs.Remove(dialog); + StateHasChanged(); + } + + private IDialogReference GetDialogReference(Guid id) + { + return _dialogs.SingleOrDefault(x => x.Id == id); + } + + private void LocationChanged(object sender, LocationChangedEventArgs args) + { + DismissAll(); + } + #endregion + + #region Lifecycle + protected override void OnInitialized() + { + DialogService.OnDialogInstanceAdded += AddInstance; + DialogService.OnDialogCloseRequested += DismissInstance; + NavigationManager.LocationChanged += LocationChanged; + + _globalDialogOptions.DisableBackdropClick = DisableBackdropClick; + _globalDialogOptions.CloseOnEscapeKey = CloseOnEscapeKey; + _globalDialogOptions.CloseButton = CloseButton; + _globalDialogOptions.NoHeader = NoHeader; + _globalDialogOptions.Position = Position; + _globalDialogOptions.FullWidth = FullWidth; + _globalDialogOptions.MaxWidth = MaxWidth; + } + + protected override Task OnAfterRenderAsync(bool firstRender) + { + if (!firstRender) + { + foreach (var dialogReference in _dialogs.Where(x => !x.Result.IsCompleted)) + { + dialogReference.RenderCompleteTaskCompletionSource.TrySetResult(true); + } + } + + return base.OnAfterRenderAsync(firstRender); + } + + public void Dispose() + { + if (NavigationManager != null) + NavigationManager.LocationChanged -= LocationChanged; + + if (DialogService != null) + { + DialogService.OnDialogInstanceAdded -= AddInstance; + DialogService.OnDialogCloseRequested -= DismissInstance; + } + } + + #endregion + } diff --git a/src/Connected.Components/Components/Divider/Divider.razor.cs b/src/Connected.Components/Components/Divider/Divider.razor.cs index d83927b..15a8007 100644 --- a/src/Connected.Components/Components/Divider/Divider.razor.cs +++ b/src/Connected.Components/Components/Divider/Divider.razor.cs @@ -6,7 +6,8 @@ using Microsoft.AspNetCore.Components; namespace Connected.Components; public partial class Divider : UIComponent { - protected string Classname => + #region Styling + protected string Classname => new CssBuilder("divider") .AddClass($"divider-absolute", Absolute) .AddClass($"divider-flexitem", FlexItem) @@ -50,4 +51,6 @@ public partial class Divider : UIComponent [Parameter] [Category(CategoryTypes.Divider.Appearance)] public DividerType DividerType { get; set; } = DividerType.FullWidth; + + #endregion } diff --git a/src/Connected.Components/Components/Drawer/Drawer.razor.cs b/src/Connected.Components/Components/Drawer/Drawer.razor.cs index 48514dc..6d4faea 100644 --- a/src/Connected.Components/Components/Drawer/Drawer.razor.cs +++ b/src/Connected.Components/Components/Drawer/Drawer.razor.cs @@ -7,6 +7,7 @@ namespace Connected.Components; public partial class Drawer : UIComponent, IDisposable, INavigationEventReceiver { + #region Variables private double _height; private ElementReference _contentRef; private DrawerClipMode _clipMode; @@ -21,8 +22,16 @@ public partial class Drawer : UIComponent, IDisposable, INavigationEventReceiver private Breakpoint _screenBreakpoint = Breakpoint.None; private Guid _breakpointListenerSubscriptionId; - #region EventCallbacks + #endregion + #region Events + private void CloseDrawer() + { + if (Open) + { + OpenChanged.InvokeAsync(false); + } + } [Parameter] public EventCallback OpenChanged { get; set; } @@ -351,13 +360,7 @@ public partial class Drawer : UIComponent, IDisposable, INavigationEventReceiver OpenChanged.InvokeAsync(_open); } } - private void CloseDrawer() - { - if (Open) - { - OpenChanged.InvokeAsync(false); - } - } + /// /// Width of left/right drawer. Only for non-fixed drawers. diff --git a/src/Connected.Components/Components/Drawer/DrawerContainer.razor.cs b/src/Connected.Components/Components/Drawer/DrawerContainer.razor.cs index 7bf5c7a..2ccb8cd 100644 --- a/src/Connected.Components/Components/Drawer/DrawerContainer.razor.cs +++ b/src/Connected.Components/Components/Drawer/DrawerContainer.razor.cs @@ -6,16 +6,33 @@ namespace Connected.Components; public partial class DrawerContainer : UIComponent { + #region Variables protected bool Fixed { get; set; } = false; private List _drawers = new(); + #endregion - #region Event callbacks + #region Events internal void FireDrawersChanged() => StateHasChanged(); + internal void Add(Drawer drawer) + { + if (Fixed && !drawer.Fixed) + return; + + _drawers.Add(drawer); + StateHasChanged(); + } + + internal void Remove(Drawer drawer) + { + _drawers.Remove(drawer); + StateHasChanged(); + } + #endregion - #region Content placeholders + #region Content [CascadingParameter(Name = "RightToLeft")] public bool RightToLeft { get; set; } @@ -83,7 +100,7 @@ public partial class DrawerContainer : UIComponent #endregion - #region Styling properties + #region Styling protected virtual CssBuilder CompiledClassList { @@ -142,18 +159,5 @@ public partial class DrawerContainer : UIComponent #endregion - internal void Add(Drawer drawer) - { - if (Fixed && !drawer.Fixed) - return; - - _drawers.Add(drawer); - StateHasChanged(); - } - - internal void Remove(Drawer drawer) - { - _drawers.Remove(drawer); - StateHasChanged(); - } + } diff --git a/src/Connected.Components/Components/Drawer/DrawerHeader.razor.cs b/src/Connected.Components/Components/Drawer/DrawerHeader.razor.cs index 32ae6a3..74dcfb2 100644 --- a/src/Connected.Components/Components/Drawer/DrawerHeader.razor.cs +++ b/src/Connected.Components/Components/Drawer/DrawerHeader.razor.cs @@ -4,10 +4,8 @@ using Microsoft.AspNetCore.Components; namespace Connected.Components; public partial class DrawerHeader : UIComponent { - #region Event callbacks - #endregion - #region Content placeholders + #region Content /// /// If true, compact padding will be used, same as the Appbar. @@ -29,7 +27,7 @@ public partial class DrawerHeader : UIComponent #endregion - #region Styling properties + #region Styling /// /// A space separated list of class names, added on top of the default class list. @@ -37,11 +35,13 @@ public partial class DrawerHeader : UIComponent [Parameter] public string? ClassList { get; set; } + /* /// /// A space separated list of class names, added on top of the default class list. /// [Parameter] public string? StyleList { get; set; } + */ protected virtual CssBuilder CompiledClassList { @@ -56,5 +56,4 @@ public partial class DrawerHeader : UIComponent #endregion - } diff --git a/src/Connected.Components/Components/DropZone/DropContainer.razor.cs b/src/Connected.Components/Components/DropZone/DropContainer.razor.cs index f02be05..f2b9e97 100644 --- a/src/Connected.Components/Components/DropZone/DropContainer.razor.cs +++ b/src/Connected.Components/Components/DropZone/DropContainer.razor.cs @@ -10,6 +10,13 @@ namespace Connected.Components; public class DragAndDropIndexChangedEventArgs : EventArgs { + #region Variables + public string ZoneIdentifier { get; } + public int Index { get; } + public string OldZoneIdentifier { get; } + #endregion + + #region Events public DragAndDropIndexChangedEventArgs(string zoneIdentifier, string oldZoneIdentifier, int index) { ZoneIdentifier = zoneIdentifier; @@ -17,41 +24,17 @@ public class DragAndDropIndexChangedEventArgs : EventArgs OldZoneIdentifier = oldZoneIdentifier; } - public string ZoneIdentifier { get; } - public int Index { get; } - public string OldZoneIdentifier { get; } + #endregion } -/// -/// Used to encapsulate data for a drag and drop transaction -/// -/// public class DragAndDropItemTransaction { + #region Variables private Func _commitCallback; private Func _cancelCallback; + #endregion - /// - /// The Item that is dragged during the transaction - /// - public T Item { get; init; } - - /// - /// The index of the item in the current drop zone - /// - public int Index { get; private set; } - - /// - /// The index of the item when the transaction started - /// - public int SourceIndex { get; private set; } - - /// - /// Identifier for drop zone where the transaction started - /// - public string SourceZoneIdentifier { get; init; } - - public string CurrentZone { get; private set; } + #region Events /// /// create a new instance of a drag and drop transaction encapsulating the item and source @@ -101,248 +84,293 @@ public class DragAndDropItemTransaction Index = -1; return true; } -} -/// -/// Record encaplusalting data regaring a completed transaction -/// -/// Type of dragged item -/// The dragged item during the transaction -/// Identifier of the zone where the transaction started -/// The index of the item within in the dropzone -public record ItemDropInfo(T Item, string DropzoneIdentifier, int IndexInZone); - -public class DragAndDropTransactionFinishedEventArgs : EventArgs -{ - public DragAndDropTransactionFinishedEventArgs(DragAndDropItemTransaction transaction) : - this(string.Empty, false, transaction) - { - - } + #endregion - public DragAndDropTransactionFinishedEventArgs(string destinationDropzoneIdentifier, bool success, DragAndDropItemTransaction transaction) - { - Item = transaction.Item; - Success = success; - OriginatedDropzoneIdentifier = transaction.SourceZoneIdentifier; - DestinationDropzoneIdentifier = destinationDropzoneIdentifier; - OriginIndex = transaction.SourceIndex; - DestinationIndex = transaction.Index; - } - - public T Item { get; } - public bool Success { get; } - public string OriginatedDropzoneIdentifier { get; } - public string DestinationDropzoneIdentifier { get; } - public int OriginIndex { get; } - public int DestinationIndex { get; } -} - - -/// -/// The container of a drag and drop zones -/// -/// Datetype of items -public partial class DropContainer : UIComponent -{ - private DragAndDropItemTransaction _transaction; - - protected string Classname => - new CssBuilder("drop-container") - .AddClass(AdditionalClassList) - .Build(); - - /// - /// Child content of component. This should include the drop zones - /// - [Parameter] - [Category(CategoryTypes.DropZone.Appearance)] - public RenderFragment ChildContent { get; set; } + #region Content /// - /// The items that can be drag and dropped within the container - /// - [Parameter] - [Category(CategoryTypes.DropZone.Items)] - public IEnumerable Items { get; set; } - - /// - /// The render fragment (template) that should be used to render the items within a drop zone - /// - [Parameter] - [Category(CategoryTypes.DropZone.Items)] - public RenderFragment ItemRenderer { get; set; } - - /// - /// The method is used to determinate if an item can be dropped within a drop zone - /// - [Parameter] - [Category(CategoryTypes.DropZone.Items)] - public Func ItemsSelector { get; set; } - - /// - /// Callback that indicates that an item has been dropped on a drop zone. Should be used to update the "status" of the data item + /// The Item that is dragged during the transaction /// - [Parameter] - [Category(CategoryTypes.DropZone.Items)] - public EventCallback> ItemDropped { get; set; } + public T Item { get; init; } /// - /// The method is used to determinate if an item can be dropped within a drop zone + /// The index of the item in the current drop zone /// - [Parameter] - [Category(CategoryTypes.DropZone.DropRules)] - public Func CanDrop { get; set; } + public int Index { get; private set; } /// - /// The CSS class(es), that is applied to drop zones that are a valid target for drag and drop transaction + /// The index of the item when the transaction started /// - [Parameter] - [Category(CategoryTypes.DropZone.DropRules)] - public string CanDropClass { get; set; } + public int SourceIndex { get; private set; } /// - /// The CSS class(es), that is applied to drop zones that are NOT valid target for drag and drop transaction + /// Identifier for drop zone where the transaction started /// - [Parameter] - [Category(CategoryTypes.DropZone.DropRules)] - public string NoDropClass { get; set; } + public string SourceZoneIdentifier { get; init; } - /// - /// If true, drop classes CanDropClass or NoDropClass or applied as soon, as a transaction has started - /// - [Parameter] - [Category(CategoryTypes.DropZone.DropRules)] - public bool ApplyDropClassesOnDragStarted { get; set; } = false; + public string CurrentZone { get; private set; } - /// - /// The method is used to determinate if an item should be disabled for dragging. Defaults to allow all items - /// - [Parameter] - [Category(CategoryTypes.DropZone.Disabled)] - public Func ItemIsDisabled { get; set; } + #endregion - /// - /// If a drop item is disabled (determinate by ). This class is applied to the element - /// - [Parameter] - [Category(CategoryTypes.DropZone.Disabled)] - public string DisabledClass { get; set; } = "disabled"; + #region Styling + #endregion - /// - /// An additional class that is applied to the drop zone where a drag operation started - /// - [Parameter] - [Category(CategoryTypes.DropZone.DraggingClass)] - public string DraggingClass { get; set; } + #region Behavior + #endregion - /// - /// An additional class that is applied to an drop item, when it is dragged - /// - [Parameter] - [Category(CategoryTypes.DropZone.DraggingClass)] - public string ItemDraggingClass { get; set; } + #region Lifecycle + #endregion - public event EventHandler> TransactionStarted; - public event EventHandler TransactionIndexChanged; - public event EventHandler> TransactionEnded; - public event EventHandler RefreshRequested; +} - public void StartTransaction(T item, string identifier, int index, Func commitCallback, Func cancelCallback) - { - _transaction = new DragAndDropItemTransaction(item, identifier, index, commitCallback, cancelCallback); - TransactionStarted?.Invoke(this, _transaction); - } +/// +/// Used to encapsulate data for a drag and drop transaction +/// +/// - public T GetTransactionItem() => _transaction.Item; - public bool TransactionInProgress() => _transaction != null; - public string GetTransactionOrignZoneIdentiifer() => _transaction?.SourceZoneIdentifier ?? string.Empty; - public string GetTransactionCurrentZoneIdentiifer() => _transaction?.CurrentZone ?? string.Empty; - public bool IsTransactionOriginatedFromInside(string identifier) => _transaction.SourceZoneIdentifier == identifier; +/// +/// Record encaplusalting data regaring a completed transaction +/// +/// Type of dragged item +/// The dragged item during the transaction +/// Identifier of the zone where the transaction started +/// The index of the item within in the dropzone +public record ItemDropInfo(T Item, string DropzoneIdentifier, int IndexInZone); - public int GetTransactionIndex() => _transaction?.Index ?? -1; - public bool IsItemMovedDownwards() => _transaction.Index > _transaction.SourceIndex; - public bool HasTransactionIndexChanged() + public class DragAndDropTransactionFinishedEventArgs : EventArgs { - if (_transaction == null) + public DragAndDropTransactionFinishedEventArgs(DragAndDropItemTransaction transaction) : + this(string.Empty, false, transaction) { - return false; + } - if (_transaction.CurrentZone != _transaction.SourceZoneIdentifier) + public DragAndDropTransactionFinishedEventArgs(string destinationDropzoneIdentifier, bool success, DragAndDropItemTransaction transaction) { - return true; + Item = transaction.Item; + Success = success; + OriginatedDropzoneIdentifier = transaction.SourceZoneIdentifier; + DestinationDropzoneIdentifier = destinationDropzoneIdentifier; + OriginIndex = transaction.SourceIndex; + DestinationIndex = transaction.Index; } - return _transaction.Index != _transaction.SourceIndex; + public T Item { get; } + public bool Success { get; } + public string OriginatedDropzoneIdentifier { get; } + public string DestinationDropzoneIdentifier { get; } + public int OriginIndex { get; } + public int DestinationIndex { get; } } - public bool IsOrign(int index, string identifier) + + /// + /// The container of a drag and drop zones + /// + /// Datetype of items + public partial class DropContainer : UIComponent { - if (_transaction == null) + private DragAndDropItemTransaction _transaction; + + protected string Classname => + new CssBuilder("drop-container") + .AddClass(AdditionalClassList) + .Build(); + + /// + /// Child content of component. This should include the drop zones + /// + [Parameter] + [Category(CategoryTypes.DropZone.Appearance)] + public RenderFragment ChildContent { get; set; } + + /// + /// The items that can be drag and dropped within the container + /// + [Parameter] + [Category(CategoryTypes.DropZone.Items)] + public IEnumerable Items { get; set; } + + /// + /// The render fragment (template) that should be used to render the items within a drop zone + /// + [Parameter] + [Category(CategoryTypes.DropZone.Items)] + public RenderFragment ItemRenderer { get; set; } + + /// + /// The method is used to determinate if an item can be dropped within a drop zone + /// + [Parameter] + [Category(CategoryTypes.DropZone.Items)] + public Func ItemsSelector { get; set; } + + /// + /// Callback that indicates that an item has been dropped on a drop zone. Should be used to update the "status" of the data item + /// + [Parameter] + [Category(CategoryTypes.DropZone.Items)] + public EventCallback> ItemDropped { get; set; } + + /// + /// The method is used to determinate if an item can be dropped within a drop zone + /// + [Parameter] + [Category(CategoryTypes.DropZone.DropRules)] + public Func CanDrop { get; set; } + + /// + /// The CSS class(es), that is applied to drop zones that are a valid target for drag and drop transaction + /// + [Parameter] + [Category(CategoryTypes.DropZone.DropRules)] + public string CanDropClass { get; set; } + + /// + /// The CSS class(es), that is applied to drop zones that are NOT valid target for drag and drop transaction + /// + [Parameter] + [Category(CategoryTypes.DropZone.DropRules)] + public string NoDropClass { get; set; } + + /// + /// If true, drop classes CanDropClass or NoDropClass or applied as soon, as a transaction has started + /// + [Parameter] + [Category(CategoryTypes.DropZone.DropRules)] + public bool ApplyDropClassesOnDragStarted { get; set; } = false; + + /// + /// The method is used to determinate if an item should be disabled for dragging. Defaults to allow all items + /// + [Parameter] + [Category(CategoryTypes.DropZone.Disabled)] + public Func ItemIsDisabled { get; set; } + + /// + /// If a drop item is disabled (determinate by ). This class is applied to the element + /// + [Parameter] + [Category(CategoryTypes.DropZone.Disabled)] + public string DisabledClass { get; set; } = "disabled"; + + /// + /// An additional class that is applied to the drop zone where a drag operation started + /// + [Parameter] + [Category(CategoryTypes.DropZone.DraggingClass)] + public string DraggingClass { get; set; } + + /// + /// An additional class that is applied to an drop item, when it is dragged + /// + [Parameter] + [Category(CategoryTypes.DropZone.DraggingClass)] + public string ItemDraggingClass { get; set; } + + public event EventHandler> TransactionStarted; + public event EventHandler TransactionIndexChanged; + + public event EventHandler> TransactionEnded; + public event EventHandler RefreshRequested; + + public void StartTransaction(T item, string identifier, int index, Func commitCallback, Func cancelCallback) { - return false; + _transaction = new DragAndDropItemTransaction(item, identifier, index, commitCallback, cancelCallback); + TransactionStarted?.Invoke(this, _transaction); } - if (identifier != _transaction.SourceZoneIdentifier) + public T GetTransactionItem() => _transaction.Item; + + public bool TransactionInProgress() => _transaction != null; + public string GetTransactionOrignZoneIdentiifer() => _transaction?.SourceZoneIdentifier ?? string.Empty; + public string GetTransactionCurrentZoneIdentiifer() => _transaction?.CurrentZone ?? string.Empty; + public bool IsTransactionOriginatedFromInside(string identifier) => _transaction.SourceZoneIdentifier == identifier; + + public int GetTransactionIndex() => _transaction?.Index ?? -1; + public bool IsItemMovedDownwards() => _transaction.Index > _transaction.SourceIndex; + public bool HasTransactionIndexChanged() { - return false; - } + if (_transaction == null) + { + return false; + } - return _transaction.SourceIndex == index || _transaction.SourceIndex - 1 == index; - } + if (_transaction.CurrentZone != _transaction.SourceZoneIdentifier) + { + return true; + } - public async Task CommitTransaction(string dropzoneIdentifier, bool reorderIsAllowed) - { - await _transaction.Commit(); - var index = -1; - if (reorderIsAllowed == true) + return _transaction.Index != _transaction.SourceIndex; + } + + public bool IsOrign(int index, string identifier) { - index = GetTransactionIndex() + 1; - if (_transaction.SourceZoneIdentifier == _transaction.CurrentZone && IsItemMovedDownwards() == true) + if (_transaction == null) + { + return false; + } + + if (identifier != _transaction.SourceZoneIdentifier) { - index -= 1; + return false; } + + return _transaction.SourceIndex == index || _transaction.SourceIndex - 1 == index; } - await ItemDropped.InvokeAsync(new ItemDropInfo(_transaction.Item, dropzoneIdentifier, index)); - var transactionFinishedEventArgs = new DragAndDropTransactionFinishedEventArgs(dropzoneIdentifier, true, _transaction); - _transaction = null; - TransactionEnded?.Invoke(this, transactionFinishedEventArgs); - } + public async Task CommitTransaction(string dropzoneIdentifier, bool reorderIsAllowed) + { + await _transaction.Commit(); + var index = -1; + if (reorderIsAllowed == true) + { + index = GetTransactionIndex() + 1; + if (_transaction.SourceZoneIdentifier == _transaction.CurrentZone && IsItemMovedDownwards() == true) + { + index -= 1; + } + } - public async Task CancelTransaction() - { - await _transaction.Cancel(); - var transactionFinishedEventArgs = new DragAndDropTransactionFinishedEventArgs(_transaction); - _transaction = null; - TransactionEnded?.Invoke(this, transactionFinishedEventArgs); - } + await ItemDropped.InvokeAsync(new ItemDropInfo(_transaction.Item, dropzoneIdentifier, index)); + var transactionFinishedEventArgs = new DragAndDropTransactionFinishedEventArgs(dropzoneIdentifier, true, _transaction); + _transaction = null; + TransactionEnded?.Invoke(this, transactionFinishedEventArgs); + } - public void UpdateTransactionIndex(int index) - { - var changed = _transaction.UpdateIndex(index); - if (changed == false) { return; } + public async Task CancelTransaction() + { + await _transaction.Cancel(); + var transactionFinishedEventArgs = new DragAndDropTransactionFinishedEventArgs(_transaction); + _transaction = null; + TransactionEnded?.Invoke(this, transactionFinishedEventArgs); + } - TransactionIndexChanged?.Invoke(this, new DragAndDropIndexChangedEventArgs(_transaction.CurrentZone, _transaction.CurrentZone, _transaction.Index)); - } + public void UpdateTransactionIndex(int index) + { + var changed = _transaction.UpdateIndex(index); + if (changed == false) { return; } - internal void UpdateTransactionZone(string identifier) - { - var oldValue = _transaction.CurrentZone; - var changed = _transaction.UpdateZone(identifier); - if (changed == false) { return; } + TransactionIndexChanged?.Invoke(this, new DragAndDropIndexChangedEventArgs(_transaction.CurrentZone, _transaction.CurrentZone, _transaction.Index)); + } - TransactionIndexChanged?.Invoke(this, new DragAndDropIndexChangedEventArgs(_transaction.CurrentZone, oldValue, _transaction.Index)); - } + internal void UpdateTransactionZone(string identifier) + { + var oldValue = _transaction.CurrentZone; + var changed = _transaction.UpdateZone(identifier); + if (changed == false) { return; } + TransactionIndexChanged?.Invoke(this, new DragAndDropIndexChangedEventArgs(_transaction.CurrentZone, oldValue, _transaction.Index)); + } - /// - /// Refreshes the dropzone and all items within. This is neded in case of adding items to the collection or changed values of items - /// - public void Refresh() => RefreshRequested?.Invoke(this, EventArgs.Empty); + + /// + /// Refreshes the dropzone and all items within. This is neded in case of adding items to the collection or changed values of items + /// + public void Refresh() => RefreshRequested?.Invoke(this, EventArgs.Empty); }