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)]