From 6768fa498799bddeb7dad54beea77bce81a4fe5d Mon Sep 17 00:00:00 2001 From: dvolper Date: Thu, 14 Dec 2023 11:48:13 +0100 Subject: [PATCH] Rework component events (#43) * refactor IComponentEvent.cs and add OnFocus & OnBlur event * e2e test * code format * fixes --- .../FocusComponentBase.cs | 32 ++++++++++++++----- packages/Ignis.Components/ComponentEvent.cs | 2 +- packages/Ignis.Components/IComponentEvent.cs | 2 ++ .../DialogPanel.cs | 2 +- .../DisclosureButton.cs | 2 +- .../Ignis.Components.HeadlessUI/Listbox.cs | 2 +- .../ListboxButton.cs | 2 +- .../ListboxOption.cs | 2 +- .../Ignis.Components.HeadlessUI/Menu.cs | 2 +- .../Ignis.Components.HeadlessUI/MenuButton.cs | 2 +- .../Ignis.Components.HeadlessUI/MenuItem.cs | 2 +- .../Ignis.Components.HeadlessUI/Popover.cs | 2 +- .../PopoverButton.cs | 2 +- .../RadioGroupOption.cs | 6 ++-- .../Ignis.Components.HeadlessUI/Switch.cs | 2 +- .../Ignis.Components.HeadlessUI/Tab.cs | 2 +- .../Dialog/Test_Dialog_PreventOnBlur.razor | 10 ++++++ .../Shared/CustomDialog.razor | 21 ++++++------ tests/e2e/Ignis.Tests.E2E/DialogTests.cs | 23 +++++++++++++ 19 files changed, 85 insertions(+), 35 deletions(-) create mode 100644 tests/e2e/Ignis.Tests.E2E.Website/Pages/Tests/Dialog/Test_Dialog_PreventOnBlur.razor diff --git a/packages/Ignis.Components.Web/FocusComponentBase.cs b/packages/Ignis.Components.Web/FocusComponentBase.cs index a9e714b0..b3014ce8 100644 --- a/packages/Ignis.Components.Web/FocusComponentBase.cs +++ b/packages/Ignis.Components.Web/FocusComponentBase.cs @@ -16,6 +16,10 @@ public abstract class FocusComponentBase : IgnisComponentBase, IFocus, IHandleAf protected virtual bool FocusOnRender => false; + [Parameter] public EventCallback OnFocus { get; set; } + + [Parameter] public EventCallback OnBlur { get; set; } + // ReSharper disable once InconsistentNaming [Inject] public IJSRuntime JSRuntime { get; set; } = null!; @@ -32,14 +36,20 @@ public async Task InvokeFocusAsync() { if (_isFocused) return; + var @event = new ComponentEvent(); + + await OnFocus.InvokeAsync(@event); + + if (@event.DefaultPrevented) return; + _isFocused = true; #pragma warning disable MA0042 // ReSharper disable once MethodHasAsyncOverload - OnFocus(); + OnTargetFocus(); #pragma warning restore MA0042 - await OnFocusAsync(); + await OnTargetFocusAsync(); } /// @@ -50,14 +60,20 @@ public async Task InvokeBlurAsync() { if (!_isFocused) return; + var @event = new ComponentEvent(); + + await OnBlur.InvokeAsync(@event); + + if (@event.DefaultPrevented) return; + _isFocused = false; #pragma warning disable MA0042 // ReSharper disable once MethodHasAsyncOverload - OnBlur(); + OnTargetBlur(); #pragma warning restore MA0042 - await OnBlurAsync(); + await OnTargetBlurAsync(); } /// @@ -117,20 +133,20 @@ private IEnumerable GetElementReferences() } } - protected virtual void OnFocus() + protected virtual void OnTargetFocus() { } - protected virtual Task OnFocusAsync() + protected virtual Task OnTargetFocusAsync() { return Task.CompletedTask; } - protected virtual void OnBlur() + protected virtual void OnTargetBlur() { } - protected virtual Task OnBlurAsync() + protected virtual Task OnTargetBlurAsync() { return Task.CompletedTask; } diff --git a/packages/Ignis.Components/ComponentEvent.cs b/packages/Ignis.Components/ComponentEvent.cs index 04e4fb06..0a40711f 100644 --- a/packages/Ignis.Components/ComponentEvent.cs +++ b/packages/Ignis.Components/ComponentEvent.cs @@ -4,7 +4,7 @@ public class ComponentEvent : IComponentEvent, IDisposable { private readonly CancellationTokenSource _cancellationTokenSource = new(); - public CancellationToken CancellationToken => this._cancellationTokenSource.Token; + public bool DefaultPrevented => this._cancellationTokenSource.IsCancellationRequested; public void PreventDefault() { diff --git a/packages/Ignis.Components/IComponentEvent.cs b/packages/Ignis.Components/IComponentEvent.cs index b96af7d8..e882cdd4 100644 --- a/packages/Ignis.Components/IComponentEvent.cs +++ b/packages/Ignis.Components/IComponentEvent.cs @@ -2,5 +2,7 @@ public interface IComponentEvent { + bool DefaultPrevented { get; } + void PreventDefault(); } diff --git a/packages/Tailwind/Ignis.Components.HeadlessUI/DialogPanel.cs b/packages/Tailwind/Ignis.Components.HeadlessUI/DialogPanel.cs index 3168933a..169940af 100644 --- a/packages/Tailwind/Ignis.Components.HeadlessUI/DialogPanel.cs +++ b/packages/Tailwind/Ignis.Components.HeadlessUI/DialogPanel.cs @@ -98,7 +98,7 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) } /// - protected override void OnBlur() + protected override void OnTargetBlur() { Dialog.Close(); } diff --git a/packages/Tailwind/Ignis.Components.HeadlessUI/DisclosureButton.cs b/packages/Tailwind/Ignis.Components.HeadlessUI/DisclosureButton.cs index 9099758b..e885adc9 100644 --- a/packages/Tailwind/Ignis.Components.HeadlessUI/DisclosureButton.cs +++ b/packages/Tailwind/Ignis.Components.HeadlessUI/DisclosureButton.cs @@ -57,7 +57,7 @@ private void Click() var __ = OnClick.InvokeAsync(@event); - if (@event.CancellationToken.IsCancellationRequested) return; + if (@event.DefaultPrevented) return; Toggle(); } diff --git a/packages/Tailwind/Ignis.Components.HeadlessUI/Listbox.cs b/packages/Tailwind/Ignis.Components.HeadlessUI/Listbox.cs index 51b2d329..efc947db 100644 --- a/packages/Tailwind/Ignis.Components.HeadlessUI/Listbox.cs +++ b/packages/Tailwind/Ignis.Components.HeadlessUI/Listbox.cs @@ -219,7 +219,7 @@ protected override IEnumerable Targets new[] { "Escape", "Space", "Enter", "ArrowUp", "ArrowDown" }; /// - protected override void OnBlur() + protected override void OnTargetBlur() { Close(); } diff --git a/packages/Tailwind/Ignis.Components.HeadlessUI/ListboxButton.cs b/packages/Tailwind/Ignis.Components.HeadlessUI/ListboxButton.cs index a9e3ceb5..b10bec79 100644 --- a/packages/Tailwind/Ignis.Components.HeadlessUI/ListboxButton.cs +++ b/packages/Tailwind/Ignis.Components.HeadlessUI/ListboxButton.cs @@ -67,7 +67,7 @@ private void Click() var __ = OnClick.InvokeAsync(@event); - if (@event.CancellationToken.IsCancellationRequested) return; + if (@event.DefaultPrevented) return; if (Listbox.IsOpen) Listbox.Close(); else Listbox.Open(); diff --git a/packages/Tailwind/Ignis.Components.HeadlessUI/ListboxOption.cs b/packages/Tailwind/Ignis.Components.HeadlessUI/ListboxOption.cs index a4bbb162..6396ad98 100644 --- a/packages/Tailwind/Ignis.Components.HeadlessUI/ListboxOption.cs +++ b/packages/Tailwind/Ignis.Components.HeadlessUI/ListboxOption.cs @@ -73,7 +73,7 @@ public void Click() var __ = OnClick.InvokeAsync(@event); - if (@event.CancellationToken.IsCancellationRequested) return; + if (@event.DefaultPrevented) return; Listbox.SelectValue(Value); diff --git a/packages/Tailwind/Ignis.Components.HeadlessUI/Menu.cs b/packages/Tailwind/Ignis.Components.HeadlessUI/Menu.cs index c010640a..fb05b32f 100644 --- a/packages/Tailwind/Ignis.Components.HeadlessUI/Menu.cs +++ b/packages/Tailwind/Ignis.Components.HeadlessUI/Menu.cs @@ -195,7 +195,7 @@ protected override IEnumerable Targets new[] { "Escape", "Space", "Enter", "ArrowUp", "ArrowDown" }; /// - protected override void OnBlur() + protected override void OnTargetBlur() { Close(); } diff --git a/packages/Tailwind/Ignis.Components.HeadlessUI/MenuButton.cs b/packages/Tailwind/Ignis.Components.HeadlessUI/MenuButton.cs index e7bc6229..9a6da182 100644 --- a/packages/Tailwind/Ignis.Components.HeadlessUI/MenuButton.cs +++ b/packages/Tailwind/Ignis.Components.HeadlessUI/MenuButton.cs @@ -62,7 +62,7 @@ private void Click() var __ = OnClick.InvokeAsync(@event); - if (@event.CancellationToken.IsCancellationRequested) return; + if (@event.DefaultPrevented) return; if (Menu.IsOpen) Menu.Close(); else Menu.Open(); diff --git a/packages/Tailwind/Ignis.Components.HeadlessUI/MenuItem.cs b/packages/Tailwind/Ignis.Components.HeadlessUI/MenuItem.cs index f34cb877..7bf467f8 100644 --- a/packages/Tailwind/Ignis.Components.HeadlessUI/MenuItem.cs +++ b/packages/Tailwind/Ignis.Components.HeadlessUI/MenuItem.cs @@ -65,7 +65,7 @@ public void Click() var __ = OnClick.InvokeAsync(@event); - if (@event.CancellationToken.IsCancellationRequested) return; + if (@event.DefaultPrevented) return; Menu.Close(); } diff --git a/packages/Tailwind/Ignis.Components.HeadlessUI/Popover.cs b/packages/Tailwind/Ignis.Components.HeadlessUI/Popover.cs index ec521047..acc7aade 100644 --- a/packages/Tailwind/Ignis.Components.HeadlessUI/Popover.cs +++ b/packages/Tailwind/Ignis.Components.HeadlessUI/Popover.cs @@ -101,7 +101,7 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) } /// - protected override void OnBlur() + protected override void OnTargetBlur() { Close(); } diff --git a/packages/Tailwind/Ignis.Components.HeadlessUI/PopoverButton.cs b/packages/Tailwind/Ignis.Components.HeadlessUI/PopoverButton.cs index 529a9962..3bf84ae3 100644 --- a/packages/Tailwind/Ignis.Components.HeadlessUI/PopoverButton.cs +++ b/packages/Tailwind/Ignis.Components.HeadlessUI/PopoverButton.cs @@ -62,7 +62,7 @@ private void Click() var __ = OnClick.InvokeAsync(@event); - if (@event.CancellationToken.IsCancellationRequested) return; + if (@event.DefaultPrevented) return; if (Popover.IsOpen) Popover.Close(); else Popover.Open(); diff --git a/packages/Tailwind/Ignis.Components.HeadlessUI/RadioGroupOption.cs b/packages/Tailwind/Ignis.Components.HeadlessUI/RadioGroupOption.cs index 49247ed9..dcf6aa0e 100644 --- a/packages/Tailwind/Ignis.Components.HeadlessUI/RadioGroupOption.cs +++ b/packages/Tailwind/Ignis.Components.HeadlessUI/RadioGroupOption.cs @@ -193,19 +193,19 @@ private void Click() var __ = OnClick.InvokeAsync(@event); - if (@event.CancellationToken.IsCancellationRequested) return; + if (@event.DefaultPrevented) return; Check(); } /// - protected override void OnFocus() + protected override void OnTargetFocus() { RadioGroup.ActiveOption = this; } /// - protected override void OnBlur() + protected override void OnTargetBlur() { if (RadioGroup.ActiveOption == this) RadioGroup.ActiveOption = null; } diff --git a/packages/Tailwind/Ignis.Components.HeadlessUI/Switch.cs b/packages/Tailwind/Ignis.Components.HeadlessUI/Switch.cs index 30efc339..fe9b885d 100644 --- a/packages/Tailwind/Ignis.Components.HeadlessUI/Switch.cs +++ b/packages/Tailwind/Ignis.Components.HeadlessUI/Switch.cs @@ -66,7 +66,7 @@ private void Click() var __ = OnClick.InvokeAsync(@event); - if (@event.CancellationToken.IsCancellationRequested) return; + if (@event.DefaultPrevented) return; Toggle(); } diff --git a/packages/Tailwind/Ignis.Components.HeadlessUI/Tab.cs b/packages/Tailwind/Ignis.Components.HeadlessUI/Tab.cs index d1af1b49..3b71d38c 100644 --- a/packages/Tailwind/Ignis.Components.HeadlessUI/Tab.cs +++ b/packages/Tailwind/Ignis.Components.HeadlessUI/Tab.cs @@ -148,7 +148,7 @@ public void Click() var __ = OnClick.InvokeAsync(@event); - if (@event.CancellationToken.IsCancellationRequested) return; + if (@event.DefaultPrevented) return; TabGroup.SelectTab(this); } diff --git a/tests/e2e/Ignis.Tests.E2E.Website/Pages/Tests/Dialog/Test_Dialog_PreventOnBlur.razor b/tests/e2e/Ignis.Tests.E2E.Website/Pages/Tests/Dialog/Test_Dialog_PreventOnBlur.razor new file mode 100644 index 00000000..0c745a85 --- /dev/null +++ b/tests/e2e/Ignis.Tests.E2E.Website/Pages/Tests/Dialog/Test_Dialog_PreventOnBlur.razor @@ -0,0 +1,10 @@ +@page "/tests/dialog/preventOnBlur" +@inherits IgnisComponentBase + +Test: Prevent OnBlur + + +

This dialog cannot be closed.

+
+ + \ No newline at end of file diff --git a/tests/e2e/Ignis.Tests.E2E.Website/Shared/CustomDialog.razor b/tests/e2e/Ignis.Tests.E2E.Website/Shared/CustomDialog.razor index c9e64821..8a25774f 100644 --- a/tests/e2e/Ignis.Tests.E2E.Website/Shared/CustomDialog.razor +++ b/tests/e2e/Ignis.Tests.E2E.Website/Shared/CustomDialog.razor @@ -1,6 +1,6 @@ @inherits IgnisComponentBase - + - +
@ChildContent
@@ -39,18 +40,16 @@ @code { - [Parameter] - public RenderFragment? ChildContent { get; set; } + [Parameter] public RenderFragment? ChildContent { get; set; } - [Parameter] - public RenderFragment? FooterContent { get; set; } + [Parameter] public RenderFragment? FooterContent { get; set; } - [Parameter] - public bool Open { get; set; } + [Parameter] public bool Open { get; set; } - [Parameter] - public EventCallback OpenChanged { get; set; } + [Parameter] public EventCallback OpenChanged { get; set; } + + [Parameter] public EventCallback OnBlur { get; set; } [Parameter(CaptureUnmatchedValues = true)] public IEnumerable>? AdditionalAttributes { get; set; } -} +} \ No newline at end of file diff --git a/tests/e2e/Ignis.Tests.E2E/DialogTests.cs b/tests/e2e/Ignis.Tests.E2E/DialogTests.cs index be5cb398..deeb6a8b 100644 --- a/tests/e2e/Ignis.Tests.E2E/DialogTests.cs +++ b/tests/e2e/Ignis.Tests.E2E/DialogTests.cs @@ -65,4 +65,27 @@ await Page.ClickAsync(".fixed.inset-0.bg-gray-500", await Expect(dialog).ToBeHiddenAsync(); } } + + [Test] + public async Task Test_Dialog_PreventOnBlur() + { + await Page.GotoAsync("https://e2e.ignis.dvolper.dev/tests/dialog/preventOnBlur"); + + var dialog = Page.GetByTestId("dialog"); + + await Expect(dialog).ToBeInViewportAsync(); + + var content = Page.GetByTestId("content"); + + await Expect(content).ToBeInViewportAsync(); + + await Expect(content).ToContainTextAsync("This dialog cannot be closed."); + + await Page.ClickAsync(".fixed.inset-0.bg-gray-500", + new PageClickOptions { Position = new Position { X = 0, Y = 0 } }); + + await Task.Delay(300); + + await Expect(dialog).ToBeInViewportAsync(); + } }