diff --git a/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml b/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml index fe97512b4c..cb5d5a5110 100644 --- a/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml +++ b/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml @@ -6682,12 +6682,14 @@ - + - Method called from JavaScript to get the current mouse ccordinates. + Method called from JavaScript to get the current mouse coordinates. - - + x-coordinate of point clicked on + y-coordinate of point clicked on + width of the screen + height of the screen diff --git a/examples/Demo/Shared/wwwroot/css/site.css b/examples/Demo/Shared/wwwroot/css/site.css index 2bd7a1b6b8..bad0befa31 100644 --- a/examples/Demo/Shared/wwwroot/css/site.css +++ b/examples/Demo/Shared/wwwroot/css/site.css @@ -1,4 +1,4 @@ -@import '_content/Microsoft.FluentUI.AspNetCore.Components/css/reboot.css'; +@import '/_content/Microsoft.FluentUI.AspNetCore.Components/css/reboot.css'; body { height: 100%; diff --git a/src/Core/Components/Menu/FluentMenu.razor.cs b/src/Core/Components/Menu/FluentMenu.razor.cs index b1289516e9..bc667dec02 100644 --- a/src/Core/Components/Menu/FluentMenu.razor.cs +++ b/src/Core/Components/Menu/FluentMenu.razor.cs @@ -1,5 +1,8 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + using System.Diagnostics.CodeAnalysis; -using System.Drawing; using Microsoft.AspNetCore.Components; using Microsoft.Extensions.DependencyInjection; using Microsoft.FluentUI.AspNetCore.Components.Extensions; @@ -14,12 +17,14 @@ public partial class FluentMenu : FluentComponentBase, IDisposable private bool _opened = false; private DotNetObjectReference? _dotNetHelper = null; - private Point _clickedPoint = default; + private bool _contextMenu = false; private readonly Dictionary items = []; private IMenuService? _menuService = null; private IJSObjectReference _jsModule = default!; + private (int top, int right, int bottom, int left) _stylePositions; + /// internal string? ClassValue => new CssBuilder(Class) .Build(); @@ -35,8 +40,11 @@ public partial class FluentMenu : FluentComponentBase, IDisposable .AddStyle("position", "fixed", () => !Anchored && !string.IsNullOrEmpty(Anchor)) .AddStyle("width", "unset", () => !Anchored) .AddStyle("height", "unset", () => !Anchored) - .AddStyle("left", $"{_clickedPoint.X}px", () => !Anchored && _clickedPoint.X != 0) - .AddStyle("top", $"{_clickedPoint.Y}px", () => !Anchored && _clickedPoint.Y != 0) + + .AddStyle("top", $"{_stylePositions.top}px", () => !Anchored && _stylePositions.top != 0) + .AddStyle("right", $"{_stylePositions.right}px", () => !Anchored && _stylePositions.right != 0) + .AddStyle("bottom", $"{_stylePositions.bottom}px", () => !Anchored && _stylePositions.bottom != 0) + .AddStyle("left", $"{_stylePositions.left}px", () => !Anchored && _stylePositions.left != 0) .Build(); /// @@ -153,7 +161,7 @@ public bool Open /// Gets or sets how short the space allocated to the default position has to be before the tallest area is selected for layout. /// [Parameter] - public int VerticalThreshold { get; set; } = 0; + public int VerticalThreshold { get; set; } = 200; /// /// Gets or sets how narrow the space allocated to the default position has to be before the widest area is selected for layout. @@ -252,23 +260,53 @@ public async Task CloseAsync() } /// - /// Method called from JavaScript to get the current mouse ccordinates. + /// Method called from JavaScript to get the current mouse coordinates. /// - /// - /// + /// x-coordinate of point clicked on + /// y-coordinate of point clicked on + /// width of the screen + /// height of the screen /// [JSInvokable] - public async Task OpenAsync(int x, int y) + public async Task OpenAsync(int screenWidth, int screenHeight, int x, int y) { - _clickedPoint = new Point(x, y); - Open = true; + // Calculate the position to display the context menu using the cursor position (x, y) + // together with the screen width and height. + // The menu may need to be displayed above or left of the cursor to fit in the screen. + var left = 0; + var right = 0; + var top = 0; + var bottom = 0; + + if (x + HorizontalThreshold > screenWidth) + { + right = screenWidth - x; + } + else + { + left = x; + } + + if (y + VerticalThreshold > screenHeight) + { + bottom = screenHeight - y; + } + else + { + top = y; + } + + _stylePositions = (top, right, bottom, left); + + Open = true; if (OpenChanged.HasDelegate) { await OpenChanged.InvokeAsync(Open); } StateHasChanged(); + } internal void Register(FluentMenuItem item) @@ -286,9 +324,7 @@ private bool DrawMenuWithoutService { get { - return MenuService is not null && UseMenuService == true && !string.IsNullOrEmpty(Id) && !string.IsNullOrEmpty(Anchor) && Anchored == true - ? false // Use the MenuService to draw the menu - : true; // Use the default way to draw the menu + return MenuService is null || UseMenuService != true || string.IsNullOrEmpty(Id) || string.IsNullOrEmpty(Anchor) || Anchored != true; // Use the default way to draw the menu } } @@ -328,7 +364,7 @@ internal async Task NotifyCheckedChangedAsync(FluentMenuItem fluentMenuItem) await OnCheckedChanged.InvokeAsync(fluentMenuItem); } - internal async Task IsCheckedAsync (FluentMenuItem item) + internal async Task IsCheckedAsync(FluentMenuItem item) { return await _jsModule.InvokeAsync("isChecked", item.Id); } diff --git a/src/Core/Components/Menu/FluentMenu.razor.js b/src/Core/Components/Menu/FluentMenu.razor.js index 8ad83197e6..e4a709d320 100644 --- a/src/Core/Components/Menu/FluentMenu.razor.js +++ b/src/Core/Components/Menu/FluentMenu.razor.js @@ -3,7 +3,7 @@ export function addEventLeftClick(id, dotNetHelper) { var item = document.getElementById(id); if (!!item) { item.addEventListener("click", function(e) { - dotNetHelper.invokeMethodAsync('OpenAsync', e.clientX, e.clientY); + dotNetHelper.invokeMethodAsync('OpenAsync', window.innerWidth, window.innerHeight, e.clientX, e.clientY); }); } } @@ -14,7 +14,7 @@ export function addEventRightClick(id, dotNetHelper) { if (!!item) { item.addEventListener('contextmenu', function (e) { e.preventDefault(); - dotNetHelper.invokeMethodAsync('OpenAsync', e.clientX, e.clientY); + dotNetHelper.invokeMethodAsync('OpenAsync', window.innerWidth, window.innerHeight, e.clientX, e.clientY); return false; }, false); }