diff --git a/src/BootstrapBlazor/BootstrapBlazor.csproj b/src/BootstrapBlazor/BootstrapBlazor.csproj index 10fe672570e..81d8957de88 100644 --- a/src/BootstrapBlazor/BootstrapBlazor.csproj +++ b/src/BootstrapBlazor/BootstrapBlazor.csproj @@ -1,7 +1,7 @@  - 10.3.1-beta06 + 10.3.1 diff --git a/src/BootstrapBlazor/Components/Dialog/Dialog.razor b/src/BootstrapBlazor/Components/Dialog/Dialog.razor index a5753872ba6..8d5ab2cb92b 100644 --- a/src/BootstrapBlazor/Components/Dialog/Dialog.razor +++ b/src/BootstrapBlazor/Components/Dialog/Dialog.razor @@ -1,8 +1,9 @@ -@namespace BootstrapBlazor.Components +@namespace BootstrapBlazor.Components @inherits BootstrapComponentBase - + @for (var index = 0; index < DialogParameters.Keys.Count; index++) { if (index != 0 && index == DialogParameters.Keys.Count - 1) diff --git a/src/BootstrapBlazor/Components/Dialog/Dialog.razor.cs b/src/BootstrapBlazor/Components/Dialog/Dialog.razor.cs index 0f42f073f57..9a89b66abca 100644 --- a/src/BootstrapBlazor/Components/Dialog/Dialog.razor.cs +++ b/src/BootstrapBlazor/Components/Dialog/Dialog.razor.cs @@ -17,11 +17,7 @@ public partial class Dialog : IDisposable [NotNull] private Modal? _modal = null; - - [NotNull] private Func? _onShownAsync = null; - - [NotNull] private Func? _onCloseAsync = null; private readonly Dictionary, (bool IsKeyboard, bool IsBackdrop, Func? OnCloseCallback)> DialogParameters = []; diff --git a/src/BootstrapBlazor/Components/Modal/Modal.razor.cs b/src/BootstrapBlazor/Components/Modal/Modal.razor.cs index 3d378027b2a..0d4cf2782c0 100644 --- a/src/BootstrapBlazor/Components/Modal/Modal.razor.cs +++ b/src/BootstrapBlazor/Components/Modal/Modal.razor.cs @@ -83,6 +83,13 @@ public partial class Modal [Parameter] public Func? OnCloseAsync { get; set; } + /// + /// 关闭之前回调方法 返回 true 时关闭弹窗 返回 false 时阻止关闭弹窗 + /// Callback Method Before Closing. Return true to close, false to prevent closing + /// + [Parameter] + public Func>? OnClosingAsync { get; set; } + /// /// 获得后台关闭弹出窗口的设置 /// Gets the background close popup setting @@ -190,6 +197,21 @@ public async Task CloseCallback() } } + /// + /// 弹出窗口关闭前回调方法,由 JSInvoke 调用 + /// Callback method when the popup before close, called by JSInvoke + /// + [JSInvokable] + public async Task BeforeCloseCallback() + { + var result = true; + if (OnClosingAsync != null) + { + result = await OnClosingAsync(); + } + return result; + } + /// /// 切换弹出窗口状态的方法 /// Method to toggle the popup state @@ -214,7 +236,14 @@ public async Task Show() /// 关闭当前弹出窗口的方法 /// Method to close the current popup /// - public Task Close() => InvokeVoidAsync("execute", Id, "hide"); + public async Task Close() + { + var result = await BeforeCloseCallback(); + if (result) + { + await InvokeVoidAsync("execute", Id, "hide"); + } + } /// /// 设置标题文本的方法 @@ -247,4 +276,33 @@ public void UnRegisterShownCallback(IComponent component) { _shownCallbackCache.TryRemove(component, out _); } + + /// + /// 注册弹出窗口关闭前调用的回调方法,允许自定义逻辑来决定是否继续关闭操作 + /// Registers a callback that is invoked asynchronously when a closing event is triggered, allowing custom logic to determine whether the closing operation should proceed. + /// + /// + /// 返回包含布尔值的任务的函数。当关闭事件发生时执行该回调,返回 允许继续关闭操作,返回 取消关闭操作 + /// A function that returns a task containing a Boolean value. The callback is executed when the closing event + /// occurs, and should return to allow the closing operation to continue, or to cancel it. + /// + public void RegisterOnClosingCallback(Func> onClosingCallback) + { + OnClosingAsync += onClosingCallback; + } + + /// + /// 注销弹出窗口关闭前调用的回调方法 + /// Unregisters a previously registered callback that is invoked when a closing event occurs. + /// + /// + /// 要从关闭事件中移除的回调函数。该函数应返回一个布尔值的任务,指示是否继续关闭操作 + /// The callback function to remove from the closing event. The function should return a task that evaluates to a + /// Boolean value indicating whether the closing operation should proceed. + /// + public void UnRegisterOnClosingCallback(Func> onClosingCallback) + { + OnClosingAsync -= onClosingCallback; + } } diff --git a/src/BootstrapBlazor/Components/Modal/Modal.razor.js b/src/BootstrapBlazor/Components/Modal/Modal.razor.js index cba81a4d8c1..dc2fc33de19 100644 --- a/src/BootstrapBlazor/Components/Modal/Modal.razor.js +++ b/src/BootstrapBlazor/Components/Modal/Modal.razor.js @@ -1,4 +1,4 @@ -import Data from "../../modules/data.js" +import Data from "../../modules/data.js" import EventHandler from "../../modules/event-handler.js" export function init(id, invoke, shownCallback, closeCallback) { @@ -73,15 +73,22 @@ export function init(id, invoke, shownCallback, closeCallback) { } } + modal.close = async () => { + const close = await invoke.invokeMethodAsync("BeforeCloseCallback"); + if (close) { + modal.hide(); + } + } + modal.handlerKeyboardAndBackdrop = () => { if (!modal.hook_keyboard_backdrop) { modal.hook_keyboard_backdrop = true; - modal.handlerEscape = e => { + modal.handlerEscape = async e => { if (e.key === 'Escape') { const keyboard = el.getAttribute('data-bs-keyboard') if (keyboard === 'true') { - modal.hide() + modal.close(); } } } @@ -91,7 +98,7 @@ export function init(id, invoke, shownCallback, closeCallback) { if (e.target.closest('.modal-dialog') === null) { const backdrop = el.getAttribute('data-bs-backdrop') if (backdrop !== 'static') { - modal.hide() + modal.close(); } } }) diff --git a/src/BootstrapBlazor/Components/Modal/ModalDialog.razor b/src/BootstrapBlazor/Components/Modal/ModalDialog.razor index 2b57e974b47..8407aa0b193 100644 --- a/src/BootstrapBlazor/Components/Modal/ModalDialog.razor +++ b/src/BootstrapBlazor/Components/Modal/ModalDialog.razor @@ -22,7 +22,8 @@ } @if (ShowPrintButtonInHeader) { - + } @if (ShowExportPdfButtonInHeader) { @@ -32,11 +33,13 @@ } @if (ShowMaximizeButton) { - + } @if (ShowHeaderCloseButton) { - + } @@ -58,11 +61,13 @@ } @if (ShowCloseButton) { - + } @if (ShowPrintButton) { - + } @if (ShowExportPdfButton) { @@ -72,7 +77,8 @@ } @if (ShowSaveButton) { - + } @if (FooterTemplate != null) { diff --git a/test/UnitTest/Components/ModalTest.cs b/test/UnitTest/Components/ModalTest.cs index 6d42e4e8ca2..ec5c4cdef53 100644 --- a/test/UnitTest/Components/ModalTest.cs +++ b/test/UnitTest/Components/ModalTest.cs @@ -172,6 +172,51 @@ public async Task RegisterShownCallback_Ok() Assert.True(component.Instance.Pass); } + [Fact] + public async Task OnClosingAsync_Ok() + { + var closing = false; + var cut = Context.Render(builder => + { + builder.Add(a => a.OnClosingAsync, async () => + { + closing = true; + await Task.Yield(); + return true; + }); + builder.AddChildContent(pb => + { + + }); + }); + await cut.InvokeAsync(() => cut.Instance.Close()); + Assert.True(closing); + } + + [Fact] + public async Task RegisterOnClosingAsync_Ok() + { + var closing = false; + var cut = Context.Render(builder => + { + builder.AddChildContent(pb => + { + + }); + }); + + var closingHandler = async () => + { + closing = true; + await Task.Yield(); + return true; + }; + cut.Instance.RegisterOnClosingCallback(closingHandler); + await cut.InvokeAsync(() => cut.Instance.Close()); + cut.Instance.UnRegisterOnClosingCallback(closingHandler); + Assert.True(closing); + } + private class MockComponent : ComponentBase { public bool Value { get; set; }