Skip to content

Commit 81903b6

Browse files
peereflitsCopilotArgoZhang
authored
feat(ContextMenu): make the context menu appearance speed configurable when touched (#7521)
Signed-off-by: Marcel Peereboom <40403094+peereflits@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Argo Zhang <argo@live.ca>
1 parent e713590 commit 81903b6

3 files changed

Lines changed: 90 additions & 9 deletions

File tree

src/BootstrapBlazor/Components/ContextMenu/ContextMenuTrigger.cs

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,30 @@ public class ContextMenuTrigger : BootstrapComponentBase
3535
[NotNull]
3636
private ContextMenuZone? ContextMenuZone { get; set; }
3737

38+
/// <summary>
39+
/// The timeout duration for touch events to trigger the context menu (in milliseconds).
40+
/// Default is <see cref="ContextMenuOptions.OnTouchDelay"/> milliseconds. Must be greater than 0.
41+
/// </summary>
42+
[Parameter]
43+
public int? OnTouchDelay { get; set; }
44+
45+
[Inject, NotNull]
46+
private IOptionsMonitor<BootstrapBlazorOptions>? Options { get; set; }
47+
3848
private string? ClassString => CssBuilder.Default()
3949
.AddClassFromAttributes(AdditionalAttributes)
4050
.Build();
4151

52+
/// <summary>
53+
/// <inheritdoc/>
54+
/// </summary>
55+
protected override void OnParametersSet()
56+
{
57+
base.OnParametersSet();
58+
59+
OnTouchDelay ??= Options.CurrentValue.ContextMenuOptions.OnTouchDelay;
60+
}
61+
4262
/// <summary>
4363
/// <inheritdoc/>
4464
/// </summary>
@@ -65,7 +85,7 @@ protected override void BuildRenderTree(RenderTreeBuilder builder)
6585
/// <summary>
6686
/// 是否触摸
6787
/// </summary>
68-
private bool TouchStart { get; set; }
88+
public bool IsTouchStarted { get; private set; }
6989

7090
/// <summary>
7191
/// 触摸定时器工作指示
@@ -77,11 +97,15 @@ private async Task OnTouchStart(TouchEventArgs e)
7797
if (!IsBusy)
7898
{
7999
IsBusy = true;
80-
TouchStart = true;
100+
IsTouchStarted = true;
81101

82102
// 延时保持 TouchStart 状态
83-
await Task.Delay(200);
84-
if (TouchStart)
103+
// Delay to maintain TouchStart state
104+
if (OnTouchDelay.HasValue)
105+
{
106+
await Task.Delay(OnTouchDelay.Value);
107+
}
108+
if (IsTouchStarted)
85109
{
86110
var args = new MouseEventArgs()
87111
{
@@ -90,18 +114,22 @@ private async Task OnTouchStart(TouchEventArgs e)
90114
ScreenX = e.Touches[0].ScreenX,
91115
ScreenY = e.Touches[0].ScreenY,
92116
};
93-
// 弹出关联菜单
117+
94118
await OnContextMenu(args);
95119

96-
//延时防止重复激活菜单功能
97-
await Task.Delay(200);
120+
// 延时防止重复激活菜单功能
121+
// Delay to prevent repeated activation of menu functions
122+
if (OnTouchDelay.HasValue)
123+
{
124+
await Task.Delay(OnTouchDelay.Value);
125+
}
98126
}
99127
IsBusy = false;
100128
}
101129
}
102130

103131
private void OnTouchEnd()
104132
{
105-
TouchStart = false;
133+
IsTouchStarted = false;
106134
}
107135
}

src/BootstrapBlazor/Options/ContextMenuOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Licensed to the .NET Foundation under one or more agreements.
1+
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the Apache 2.0 License
33
// See the LICENSE file in the project root for more information.
44
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone

test/UnitTest/Components/ContextMenuTest.cs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Microsoft.AspNetCore.Components.Web;
88
using Microsoft.Extensions.Localization;
99
using Microsoft.Extensions.Options;
10+
using System.Diagnostics;
1011

1112
namespace UnitTest.Components;
1213

@@ -106,6 +107,58 @@ public async Task ContextMenu_Ok()
106107
Assert.True(clicked);
107108
}
108109

110+
[Fact]
111+
public async Task ContextMenu_TouchWithTimeout_Ok()
112+
{
113+
const int Delay = 300; // ms
114+
115+
var localizer = Context.Services.GetRequiredService<IStringLocalizer<Foo>>();
116+
var foo = Foo.Generate(localizer);
117+
118+
var cut = Context.Render<ContextMenuZone>(x =>
119+
{
120+
x.AddChildContent<ContextMenuTrigger>(y =>
121+
{
122+
y.Add(z => z.ContextItem, foo);
123+
y.Add(z => z.OnTouchDelay, Delay);
124+
y.AddChildContent(z =>
125+
{
126+
z.OpenElement(0, "div");
127+
z.AddAttribute(1, "class", "context-trigger");
128+
z.AddContent(2, foo.Name);
129+
z.CloseElement();
130+
});
131+
});
132+
x.AddChildContent<ContextMenu>(y =>
133+
{
134+
y.AddChildContent<ContextMenuItem>(z =>
135+
{
136+
z.Add(a => a.Icon, "fa fa-test");
137+
z.Add(a => a.Text, "Test");
138+
z.Add(a => a.OnClick, (_, _) => Task.CompletedTask);
139+
});
140+
});
141+
});
142+
143+
var element = cut.Find(".context-trigger");
144+
var sw = Stopwatch.StartNew();
145+
await element.TouchStartAsync(new TouchEventArgs
146+
{
147+
Detail = 0,
148+
Touches = [new TouchPoint { ClientX = 10, ClientY = 10, ScreenX = 10, ScreenY = 10 }]
149+
});
150+
151+
var trigger = cut.FindComponent<ContextMenuTrigger>().Instance;
152+
Assert.NotNull(trigger);
153+
154+
Assert.True(trigger.IsTouchStarted);
155+
await cut.InvokeAsync(() => element.TouchEndAsync());
156+
sw.Stop();
157+
158+
Assert.True(sw.ElapsedMilliseconds >= Delay * 2);
159+
Assert.False(trigger.IsTouchStarted);
160+
}
161+
109162
[Theory]
110163
[InlineData(TableRenderMode.Table)]
111164
[InlineData(TableRenderMode.CardView)]

0 commit comments

Comments
 (0)