Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 36 additions & 8 deletions src/BootstrapBlazor/Components/ContextMenu/ContextMenuTrigger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,30 @@ public class ContextMenuTrigger : BootstrapComponentBase
[NotNull]
private ContextMenuZone? ContextMenuZone { get; set; }

/// <summary>
/// The timeout duration for touch events to trigger the context menu (in milliseconds).
/// Default is <see cref="ContextMenuOptions.OnTouchDelay"/> milliseconds. Must be greater than 0.
/// </summary>
[Parameter]
public int? OnTouchDelay { get; set; }

[Inject, NotNull]
private IOptionsMonitor<BootstrapBlazorOptions>? Options { get; set; }

private string? ClassString => CssBuilder.Default()
.AddClassFromAttributes(AdditionalAttributes)
.Build();

/// <summary>
/// <inheritdoc/>
/// </summary>
protected override void OnParametersSet()
{
base.OnParametersSet();

OnTouchDelay ??= Options.CurrentValue.ContextMenuOptions.OnTouchDelay;
}

/// <summary>
/// <inheritdoc/>
/// </summary>
Expand All @@ -65,7 +85,7 @@ protected override void BuildRenderTree(RenderTreeBuilder builder)
/// <summary>
/// 是否触摸
/// </summary>
private bool TouchStart { get; set; }
public bool IsTouchStarted { get; private set; }

/// <summary>
/// 触摸定时器工作指示
Expand All @@ -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()
{
Expand All @@ -90,18 +114,22 @@ 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;
}
}

private void OnTouchEnd()
{
TouchStart = false;
IsTouchStarted = false;
}
}
2 changes: 1 addition & 1 deletion src/BootstrapBlazor/Options/ContextMenuOptions.cs
Original file line number Diff line number Diff line change
@@ -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
Expand Down
53 changes: 53 additions & 0 deletions test/UnitTest/Components/ContextMenuTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Microsoft.AspNetCore.Components.Web;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
using System.Diagnostics;

namespace UnitTest.Components;

Expand Down Expand Up @@ -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<IStringLocalizer<Foo>>();
var foo = Foo.Generate(localizer);

var cut = Context.Render<ContextMenuZone>(x =>
{
x.AddChildContent<ContextMenuTrigger>(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<ContextMenu>(y =>
{
y.AddChildContent<ContextMenuItem>(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<ContextMenuTrigger>().Instance;
Assert.NotNull(trigger);

Assert.True(trigger.IsTouchStarted);
await cut.InvokeAsync(() => element.TouchEndAsync());
sw.Stop();

Assert.True(sw.ElapsedMilliseconds >= Delay * 2);
Comment thread
peereflits marked this conversation as resolved.
Assert.False(trigger.IsTouchStarted);
}

[Theory]
[InlineData(TableRenderMode.Table)]
[InlineData(TableRenderMode.CardView)]
Expand Down