diff --git a/src/BootstrapBlazor/Components/ContextMenu/ContextMenuTrigger.cs b/src/BootstrapBlazor/Components/ContextMenu/ContextMenuTrigger.cs index ca4233d9279..256731b3533 100644 --- a/src/BootstrapBlazor/Components/ContextMenu/ContextMenuTrigger.cs +++ b/src/BootstrapBlazor/Components/ContextMenu/ContextMenuTrigger.cs @@ -35,10 +35,30 @@ public class ContextMenuTrigger : BootstrapComponentBase [NotNull] private ContextMenuZone? ContextMenuZone { get; set; } + /// + /// The timeout duration for touch events to trigger the context menu (in milliseconds). + /// Default is milliseconds. Must be greater than 0. + /// + [Parameter] + public int? OnTouchDelay { get; set; } + + [Inject, NotNull] + private IOptionsMonitor? Options { get; set; } + private string? ClassString => CssBuilder.Default() .AddClassFromAttributes(AdditionalAttributes) .Build(); + /// + /// + /// + protected override void OnParametersSet() + { + base.OnParametersSet(); + + OnTouchDelay ??= Options.CurrentValue.ContextMenuOptions.OnTouchDelay; + } + /// /// /// @@ -65,7 +85,7 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) /// /// 是否触摸 /// - private bool TouchStart { get; set; } + public bool IsTouchStarted { get; private set; } /// /// 触摸定时器工作指示 @@ -77,11 +97,15 @@ private async Task OnTouchStart(TouchEventArgs e) if (!IsBusy) { IsBusy = true; - TouchStart = true; + IsTouchStarted = true; // 延时保持 TouchStart 状态 - await Task.Delay(200); - if (TouchStart) + // Delay to maintain TouchStart state + if (OnTouchDelay.HasValue) + { + await Task.Delay(OnTouchDelay.Value); + } + if (IsTouchStarted) { var args = new MouseEventArgs() { @@ -90,11 +114,15 @@ private async Task OnTouchStart(TouchEventArgs e) ScreenX = e.Touches[0].ScreenX, ScreenY = e.Touches[0].ScreenY, }; - // 弹出关联菜单 + await OnContextMenu(args); - //延时防止重复激活菜单功能 - await Task.Delay(200); + // 延时防止重复激活菜单功能 + // Delay to prevent repeated activation of menu functions + if (OnTouchDelay.HasValue) + { + await Task.Delay(OnTouchDelay.Value); + } } IsBusy = false; } @@ -102,6 +130,6 @@ private async Task OnTouchStart(TouchEventArgs e) private void OnTouchEnd() { - TouchStart = false; + IsTouchStarted = false; } } diff --git a/src/BootstrapBlazor/Options/ContextMenuOptions.cs b/src/BootstrapBlazor/Options/ContextMenuOptions.cs index 1477f178301..8174e61b2ae 100644 --- a/src/BootstrapBlazor/Options/ContextMenuOptions.cs +++ b/src/BootstrapBlazor/Options/ContextMenuOptions.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the Apache 2.0 License // See the LICENSE file in the project root for more information. // Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone diff --git a/test/UnitTest/Components/ContextMenuTest.cs b/test/UnitTest/Components/ContextMenuTest.cs index 5a37d1be896..998195cdf4b 100644 --- a/test/UnitTest/Components/ContextMenuTest.cs +++ b/test/UnitTest/Components/ContextMenuTest.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Components.Web; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Options; +using System.Diagnostics; namespace UnitTest.Components; @@ -106,6 +107,58 @@ public async Task ContextMenu_Ok() Assert.True(clicked); } + [Fact] + public async Task ContextMenu_TouchWithTimeout_Ok() + { + const int Delay = 300; // ms + + var localizer = Context.Services.GetRequiredService>(); + var foo = Foo.Generate(localizer); + + var cut = Context.Render(x => + { + x.AddChildContent(y => + { + y.Add(z => z.ContextItem, foo); + y.Add(z => z.OnTouchDelay, Delay); + y.AddChildContent(z => + { + z.OpenElement(0, "div"); + z.AddAttribute(1, "class", "context-trigger"); + z.AddContent(2, foo.Name); + z.CloseElement(); + }); + }); + x.AddChildContent(y => + { + y.AddChildContent(z => + { + z.Add(a => a.Icon, "fa fa-test"); + z.Add(a => a.Text, "Test"); + z.Add(a => a.OnClick, (_, _) => Task.CompletedTask); + }); + }); + }); + + var element = cut.Find(".context-trigger"); + var sw = Stopwatch.StartNew(); + await element.TouchStartAsync(new TouchEventArgs + { + Detail = 0, + Touches = [new TouchPoint { ClientX = 10, ClientY = 10, ScreenX = 10, ScreenY = 10 }] + }); + + var trigger = cut.FindComponent().Instance; + Assert.NotNull(trigger); + + Assert.True(trigger.IsTouchStarted); + await cut.InvokeAsync(() => element.TouchEndAsync()); + sw.Stop(); + + Assert.True(sw.ElapsedMilliseconds >= Delay * 2); + Assert.False(trigger.IsTouchStarted); + } + [Theory] [InlineData(TableRenderMode.Table)] [InlineData(TableRenderMode.CardView)]