@(Node.Title ?? "Title")
@foreach (var port in Node.Ports)
{
@@ -11,4 +11,12 @@
[Parameter]
public NodeModel Node { get; set; } = null!;
+ private string GenerateStyle()
+ {
+ if (Node.Size is not null)
+ {
+ return $"width: {Node.Size.Width}px; height: {Node.Size.Height}px";
+ }
+ return string.Empty;
+ }
}
\ No newline at end of file
diff --git a/src/Blazor.Diagrams/wwwroot/default.styles.css b/src/Blazor.Diagrams/wwwroot/default.styles.css
index b095bbe4e..0e37330c2 100644
--- a/src/Blazor.Diagrams/wwwroot/default.styles.css
+++ b/src/Blazor.Diagrams/wwwroot/default.styles.css
@@ -127,4 +127,29 @@ g.diagram-group.default.selected > rect {
transform: translate(-50%, -50%);
}
+.default-node-resizer {
+ width: 5px;
+ height: 5px;
+ background-color: #f5f5f5;
+ border: 1px solid #6e9fd4;
+ position: absolute;
+ transform: translate(-2.5px, -2.5px);
+}
+
+.default-node-resizer.bottomright {
+ cursor: nwse-resize;
+}
+
+.default-node-resizer.topright {
+ cursor: nesw-resize;
+}
+
+.default-node-resizer.bottomleft {
+ cursor: nesw-resize;
+}
+
+.default-node-resizer.topleft {
+ cursor: nwse-resize;
+}
+
/*# sourceMappingURL=wwwroot\default.styles.css.map */
diff --git a/src/Blazor.Diagrams/wwwroot/default.styles.min.css b/src/Blazor.Diagrams/wwwroot/default.styles.min.css
index c44adedc3..781fb0aed 100644
--- a/src/Blazor.Diagrams/wwwroot/default.styles.min.css
+++ b/src/Blazor.Diagrams/wwwroot/default.styles.min.css
@@ -1 +1 @@
-.default-node{width:100px;height:80px;border-radius:10px;background-color:#f5f5f5;border:1px solid #e8e8e8;-webkit-box-shadow:0 2px 1px -1px rgba(0,0,0,.2),0 1px 1px 0 rgba(0,0,0,.14),0 1px 3px 0 rgba(0,0,0,.12);box-shadow:0 2px 1px -1px rgba(0,0,0,.2),0 1px 1px 0 rgba(0,0,0,.14),0 1px 3px 0 rgba(0,0,0,.12);position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;}.default-node.selected{border:1px solid #6e9fd4;}.default-node.selected .diagram-port{border:1px solid #6e9fd4;}.default-node .diagram-port,.default.diagram-group .diagram-port{width:20px;height:20px;margin:-10px;border-radius:50%;background-color:#f5f5f5;border:1px solid #d4d4d4;cursor:pointer;position:absolute;}.default-node .diagram-port:hover,.default-node .diagram-port.has-links,.default.diagram-group .diagram-port.has-links{background-color:#000;}.default-node .diagram-port.bottom,.default.diagram-group .diagram-port.bottom{bottom:0;left:50%;}.default-node .diagram-port.bottomleft,.default.diagram-group .diagram-port.bottomleft{bottom:0;left:0;}.default-node .diagram-port.bottomright,.default.diagram-group .diagram-port.bottomright{bottom:0;right:0;}.default-node .diagram-port.top,.default.diagram-group .diagram-port.top{top:0;left:50%;}.default-node .diagram-port.topleft,.default.diagram-group .diagram-port.topleft{top:0;left:0;}.default-node .diagram-port.topright,.default.diagram-group .diagram-port.topright{top:0;right:0;}.default-node .diagram-port.left,.default.diagram-group .diagram-port.left{left:0;top:50%;}.default-node .diagram-port.right,.default.diagram-group .diagram-port.right{right:0;top:50%;}.diagram-navigator.default{position:absolute;bottom:10px;right:10px;border:3px solid #9ba8b0;border-radius:15px;padding:20px;background-color:#fff;}div.diagram-group.default{outline:2px solid #000;background:#c6c6c6;}div.diagram-group.default.selected{outline:2px solid #6e9fd4;}g.diagram-group.default rect{outline:2px solid #000;fill:#c6c632;}g.diagram-group.default.selected>rect{outline:2px solid #008000;}.diagram-link div.default-link-label{display:inline-block;color:#fff;background-color:#6e9fd4;border-radius:.25rem;padding:.25rem;text-align:center;font-size:.875rem;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;min-width:3rem;-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%);}
\ No newline at end of file
+.default-node{width:100px;height:80px;border-radius:10px;background-color:#f5f5f5;border:1px solid #e8e8e8;-webkit-box-shadow:0 2px 1px -1px rgba(0,0,0,.2),0 1px 1px 0 rgba(0,0,0,.14),0 1px 3px 0 rgba(0,0,0,.12);box-shadow:0 2px 1px -1px rgba(0,0,0,.2),0 1px 1px 0 rgba(0,0,0,.14),0 1px 3px 0 rgba(0,0,0,.12);position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;}.default-node.selected{border:1px solid #6e9fd4;}.default-node.selected .diagram-port{border:1px solid #6e9fd4;}.default-node .diagram-port,.default.diagram-group .diagram-port{width:20px;height:20px;margin:-10px;border-radius:50%;background-color:#f5f5f5;border:1px solid #d4d4d4;cursor:pointer;position:absolute;}.default-node .diagram-port:hover,.default-node .diagram-port.has-links,.default.diagram-group .diagram-port.has-links{background-color:#000;}.default-node .diagram-port.bottom,.default.diagram-group .diagram-port.bottom{bottom:0;left:50%;}.default-node .diagram-port.bottomleft,.default.diagram-group .diagram-port.bottomleft{bottom:0;left:0;}.default-node .diagram-port.bottomright,.default.diagram-group .diagram-port.bottomright{bottom:0;right:0;}.default-node .diagram-port.top,.default.diagram-group .diagram-port.top{top:0;left:50%;}.default-node .diagram-port.topleft,.default.diagram-group .diagram-port.topleft{top:0;left:0;}.default-node .diagram-port.topright,.default.diagram-group .diagram-port.topright{top:0;right:0;}.default-node .diagram-port.left,.default.diagram-group .diagram-port.left{left:0;top:50%;}.default-node .diagram-port.right,.default.diagram-group .diagram-port.right{right:0;top:50%;}.diagram-navigator.default{position:absolute;bottom:10px;right:10px;border:3px solid #9ba8b0;border-radius:15px;padding:20px;background-color:#fff;}div.diagram-group.default{outline:2px solid #000;background:#c6c6c6;}div.diagram-group.default.selected{outline:2px solid #6e9fd4;}g.diagram-group.default rect{outline:2px solid #000;fill:#c6c632;}g.diagram-group.default.selected>rect{outline:2px solid #008000;}.diagram-link div.default-link-label{display:inline-block;color:#fff;background-color:#6e9fd4;border-radius:.25rem;padding:.25rem;text-align:center;font-size:.875rem;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;min-width:3rem;-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%);}.default-node-resizer{width:5px;height:5px;background-color:#f5f5f5;border:1px solid #6e9fd4;position:absolute;transform:translate(-2.5px,-2.5px);}.default-node-resizer.bottomright{cursor:nwse-resize;}.default-node-resizer.topright{cursor:nesw-resize;}.default-node-resizer.bottomleft{cursor:nesw-resize;}.default-node-resizer.topleft{cursor:nwse-resize;}
\ No newline at end of file
diff --git a/src/Blazor.Diagrams/wwwroot/default.styles.min.css.gz b/src/Blazor.Diagrams/wwwroot/default.styles.min.css.gz
index 905f20cd3..3c43a8a10 100644
Binary files a/src/Blazor.Diagrams/wwwroot/default.styles.min.css.gz and b/src/Blazor.Diagrams/wwwroot/default.styles.min.css.gz differ
diff --git a/tests/Blazor.Diagrams.Core.Tests/Controls/ResizeControlTests.cs b/tests/Blazor.Diagrams.Core.Tests/Controls/ResizeControlTests.cs
new file mode 100644
index 000000000..543ecb6f0
--- /dev/null
+++ b/tests/Blazor.Diagrams.Core.Tests/Controls/ResizeControlTests.cs
@@ -0,0 +1,74 @@
+using Blazor.Diagrams.Core.Controls.Default;
+using Blazor.Diagrams.Core.Events;
+using Blazor.Diagrams.Core.Models.Base;
+using Blazor.Diagrams.Core.Positions.Resizing;
+using Moq;
+using Xunit;
+
+namespace Blazor.Diagrams.Core.Tests.Controls;
+
+public class ResizeControlTests
+{
+ [Fact]
+ public void GetPosition_ShouldUseResizeProviderGetPosition()
+ {
+ var resizeProvider = new Mock
();
+ var control = new ResizeControl(resizeProvider.Object);
+ var model = new Mock();
+
+ control.GetPosition(model.Object);
+
+ resizeProvider.Verify(m => m.GetPosition(model.Object), Times.Once);
+ }
+
+ [Fact]
+ public void OnPointerDown_ShouldInvokeResizeStart()
+ {
+ var resizeProvider = new Mock();
+ var control = new ResizeControl(resizeProvider.Object);
+ var diagram = Mock.Of();
+ var model = Mock.Of();
+ var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true);
+
+ control.OnPointerDown(diagram, model, eventArgs);
+
+ resizeProvider.Verify(m => m.OnResizeStart(diagram, model, eventArgs), Times.Once);
+ }
+
+ [Fact]
+ public void OnPointerDown_ShouldAddEventHandlers()
+ {
+ var resizeProvider = new Mock();
+ var control = new ResizeControl(resizeProvider.Object);
+ var diagram = new TestDiagram();
+ var model = Mock.Of();
+ var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true);
+
+ control.OnPointerDown(diagram, model, eventArgs);
+
+ diagram.TriggerPointerMove(model, eventArgs);
+ resizeProvider.Verify(m => m.OnPointerMove(model, eventArgs), Times.Once);
+
+ diagram.TriggerPointerUp(model, eventArgs);
+ resizeProvider.Verify(m => m.OnResizeEnd(model, eventArgs), Times.Once);
+ }
+
+ [Fact]
+ public void OnPointerUp_ShouldRemoveEventHandlers()
+ {
+ var resizeProvider = new Mock();
+ var control = new ResizeControl(resizeProvider.Object);
+ var diagram = new TestDiagram();
+ var model = Mock.Of();
+ var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true);
+
+ control.OnPointerDown(diagram, model, eventArgs);
+ diagram.TriggerPointerUp(model, eventArgs);
+
+ diagram.TriggerPointerMove(model, eventArgs);
+ resizeProvider.Verify(m => m.OnPointerMove(model, eventArgs), Times.Never);
+
+ diagram.TriggerPointerUp(model, eventArgs);
+ resizeProvider.Verify(m => m.OnResizeEnd(model, eventArgs), Times.Once);
+ }
+}
\ No newline at end of file
diff --git a/tests/Blazor.Diagrams.Core.Tests/Models/NodeModelTest.cs b/tests/Blazor.Diagrams.Core.Tests/Models/NodeModelTest.cs
new file mode 100644
index 000000000..b27edc990
--- /dev/null
+++ b/tests/Blazor.Diagrams.Core.Tests/Models/NodeModelTest.cs
@@ -0,0 +1,53 @@
+using Blazor.Diagrams.Core.Geometry;
+using Blazor.Diagrams.Core.Models;
+using Moq;
+using Xunit;
+
+namespace Blazor.Diagrams.Core.Tests.Models
+{
+ public class NodeModelTest
+ {
+ [Fact]
+ public void UpdatePortOnSetPosition()
+ {
+ var node = new NodeModel(position: new Point(100, 100));
+ node.Size = new Size(100, 100);
+
+ var port = new PortModel(node, PortAlignment.BottomLeft, new Point(50, 50));
+ node.AddPort(port);
+
+ var newX = 200;
+ var newY = 300;
+
+ //Act
+ node.SetPosition(newX, newY);
+
+ //Assert
+ Assert.Equal(150, port.Position.X);
+ Assert.Equal(250, port.Position.Y);
+ }
+
+ [Fact]
+ public void SetPortPositionOnNodeSizeChangedIsCalledOnSetSize()
+ {
+ // Arrange
+ var oldWidth = 100.0;
+ var oldHeight = 100.0;
+ var newWidth = 500.0;
+ var newHeight = 700.0;
+ var deltaX = newWidth - oldWidth;
+ var deltaY = newHeight - oldHeight;
+
+ var node = new NodeModel(new Point(100, 100)) { Size = new Size(oldWidth, oldHeight) };
+ var portMock = new Mock(node, PortAlignment.BottomLeft, null, null);
+
+ node.AddPort(portMock.Object);
+
+ // Act
+ node.SetSize(newWidth, newHeight);
+
+ // Assert
+ portMock.Verify(m => m.SetPortPositionOnNodeSizeChanged(deltaX, deltaY), Times.Once);
+ }
+ }
+}
diff --git a/tests/Blazor.Diagrams.Core.Tests/Models/PortModelTest.cs b/tests/Blazor.Diagrams.Core.Tests/Models/PortModelTest.cs
new file mode 100644
index 000000000..d4dc57bf5
--- /dev/null
+++ b/tests/Blazor.Diagrams.Core.Tests/Models/PortModelTest.cs
@@ -0,0 +1,33 @@
+using Blazor.Diagrams.Core.Geometry;
+using Blazor.Diagrams.Core.Models;
+using Xunit;
+
+namespace Blazor.Diagrams.Core.Tests.Models
+{
+ public class PortModelTest
+ {
+ [Theory]
+ [InlineData(PortAlignment.Top, 50, 0)]
+ [InlineData(PortAlignment.TopLeft, 0, 0)]
+ [InlineData(PortAlignment.TopRight, 100, 0)]
+ [InlineData(PortAlignment.Bottom, 50, 100)]
+ [InlineData(PortAlignment.BottomLeft, 0, 100)]
+ [InlineData(PortAlignment.BottomRight, 100, 100)]
+ [InlineData(PortAlignment.Left, 0, 50)]
+ [InlineData(PortAlignment.Right, 100, 50)]
+ public void SetPortPositionOnNodeSizeChangedCalculatesCorrectPosition(PortAlignment alignment, double expectedXPosition, double expectedYPosition)
+ {
+ // Arrange
+ var node = new NodeModel();
+ var port = new PortModel(node, alignment, new Point(0, 0));
+ node.Size = new Size(100, 100);
+
+ // Act
+ port.SetPortPositionOnNodeSizeChanged(100, 100);
+
+ // Assert
+ Assert.Equal(expectedXPosition, port.Position.X);
+ Assert.Equal(expectedYPosition, port.Position.Y);
+ }
+ }
+}
diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs
new file mode 100644
index 000000000..1605cb14f
--- /dev/null
+++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs
@@ -0,0 +1,145 @@
+using Blazor.Diagrams.Core.Controls.Default;
+using Blazor.Diagrams.Core.Events;
+using Blazor.Diagrams.Core.Geometry;
+using Blazor.Diagrams.Core.Models;
+using Blazor.Diagrams.Core.Positions.Resizing;
+using FluentAssertions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Blazor.Diagrams.Core.Tests.Positions.Resizing;
+
+public class BottomLeftResizerProviderTests
+{
+ [Fact]
+ public void DragResizer_ShouldResizeNode()
+ {
+ // setup
+ var diagram = new TestDiagram();
+ diagram.SetContainer(new Rectangle(0, 0, 1000, 400));
+ var node = new NodeModel(position: new Point(0, 0));
+ node.Size = new Size(100, 200);
+ var control = new ResizeControl(new BottomLeftResizerProvider());
+ diagram.Controls.AddFor(node).Add(control);
+ diagram.SelectModel(node, false);
+
+ // before resize
+ node.Position.X.Should().Be(0);
+ node.Position.Y.Should().Be(0);
+ node.Size.Width.Should().Be(100);
+ node.Size.Height.Should().Be(200);
+
+ // resize
+ var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+ control.OnPointerDown(diagram, node, eventArgs);
+ eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+ diagram.TriggerPointerMove(null, eventArgs);
+
+ // after resize
+ node.Position.X.Should().Be(10);
+ node.Position.Y.Should().Be(0);
+ node.Size.Width.Should().Be(90);
+ node.Size.Height.Should().Be(215);
+ }
+
+ [Fact]
+ public void DragResizer_SmallerThanMinSize_SetsNodeToMinSize()
+ {
+ // setup
+ var diagram = new TestDiagram();
+ diagram.SetContainer(new Rectangle(0, 0, 1000, 400));
+ var node = new NodeModel(position: new Point(0, 0));
+ node.Size = new Size(100, 200);
+ var control = new ResizeControl(new BottomLeftResizerProvider());
+ diagram.Controls.AddFor(node).Add(control);
+ diagram.SelectModel(node, false);
+
+ // before resize
+ node.Position.X.Should().Be(0);
+ node.Position.Y.Should().Be(0);
+ node.Size.Width.Should().Be(100);
+ node.Size.Height.Should().Be(200);
+
+ // resize
+ var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+ control.OnPointerDown(diagram, node, eventArgs);
+ eventArgs = new PointerEventArgs(99, -199, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+ diagram.TriggerPointerMove(null, eventArgs);
+ eventArgs = new PointerEventArgs(300, -300, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+ diagram.TriggerPointerMove(null, eventArgs);
+
+ // after resize
+ node.Position.X.Should().Be(99);
+ node.Position.Y.Should().Be(0);
+ node.Size.Width.Should().Be(0);
+ node.Size.Height.Should().Be(0);
+ }
+
+ [Fact]
+ public void DragResizer_ShouldResizeNode_WhenDiagramZoomedOut()
+ {
+ // setup
+ var diagram = new TestDiagram();
+ diagram.SetContainer(new Rectangle(0, 0, 1000, 400));
+ var node = new NodeModel(position: new Point(0, 0));
+ node.Size = new Size(100, 200);
+ var control = new ResizeControl(new BottomLeftResizerProvider());
+ diagram.Controls.AddFor(node).Add(control);
+ diagram.SelectModel(node, false);
+ diagram.SetZoom(0.5);
+
+ // before resize
+ node.Position.X.Should().Be(0);
+ node.Position.Y.Should().Be(0);
+ node.Size.Width.Should().Be(100);
+ node.Size.Height.Should().Be(200);
+
+ // resize
+ var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+ control.OnPointerDown(diagram, node, eventArgs);
+ eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+ diagram.TriggerPointerMove(null, eventArgs);
+
+ // after resize
+ node.Position.X.Should().Be(20);
+ node.Position.Y.Should().Be(0);
+ node.Size.Width.Should().Be(80);
+ node.Size.Height.Should().Be(230);
+ }
+
+ [Fact]
+ public void DragResizer_ShouldResizeNode_WhenDiagramZoomedIn()
+ {
+ // setup
+ var diagram = new TestDiagram();
+ diagram.SetContainer(new Rectangle(0, 0, 1000, 400));
+ var node = new NodeModel(position: new Point(0, 0));
+ node.Size = new Size(100, 200);
+ var control = new ResizeControl(new BottomLeftResizerProvider());
+ diagram.Controls.AddFor(node).Add(control);
+ diagram.SelectModel(node, false);
+ diagram.SetZoom(2);
+
+ // before resize
+ node.Position.X.Should().Be(0);
+ node.Position.Y.Should().Be(0);
+ node.Size.Width.Should().Be(100);
+ node.Size.Height.Should().Be(200);
+
+ // resize
+ var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+ control.OnPointerDown(diagram, node, eventArgs);
+ eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+ diagram.TriggerPointerMove(null, eventArgs);
+
+ // after resize
+ node.Position.X.Should().Be(5);
+ node.Position.Y.Should().Be(0);
+ node.Size.Width.Should().Be(95);
+ node.Size.Height.Should().Be(207.5);
+ }
+}
diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs
new file mode 100644
index 000000000..959724e6d
--- /dev/null
+++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs
@@ -0,0 +1,143 @@
+using Blazor.Diagrams.Core.Controls.Default;
+using Blazor.Diagrams.Core.Events;
+using Blazor.Diagrams.Core.Geometry;
+using Blazor.Diagrams.Core.Models;
+using Blazor.Diagrams.Core.Positions.Resizing;
+using FluentAssertions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Blazor.Diagrams.Core.Tests.Positions.Resizing;
+
+public class BottomRightResizerProviderTests
+{
+ [Fact]
+ public void DragResizer_ShouldResizeNode()
+ {
+ // setup
+ var diagram = new TestDiagram();
+ diagram.SetContainer(new Rectangle(0, 0, 1000, 400));
+ var node = new NodeModel(position: new Point(0, 0));
+ node.Size = new Size(100, 200);
+ var control = new ResizeControl(new BottomRightResizerProvider());
+ diagram.Controls.AddFor(node).Add(control);
+ diagram.SelectModel(node, false);
+
+ // before resize
+ node.Position.X.Should().Be(0);
+ node.Position.Y.Should().Be(0);
+ node.Size.Width.Should().Be(100);
+ node.Size.Height.Should().Be(200);
+
+ // resize
+ var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+ control.OnPointerDown(diagram, node, eventArgs);
+ eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+ diagram.TriggerPointerMove(null, eventArgs);
+
+ // after resize
+ node.Position.X.Should().Be(0);
+ node.Position.Y.Should().Be(0);
+ node.Size.Width.Should().Be(110);
+ node.Size.Height.Should().Be(215);
+ }
+
+ [Fact]
+ public void DragResizer_SmallerThanMinSize_SetsNodeToMinSize()
+ {
+ // setup
+ var diagram = new TestDiagram();
+ diagram.SetContainer(new Rectangle(0, 0, 1000, 400));
+ var node = new NodeModel(position: new Point(0, 0));
+ node.Size = new Size(100, 200);
+ var control = new ResizeControl(new BottomRightResizerProvider());
+ diagram.Controls.AddFor(node).Add(control);
+ diagram.SelectModel(node, false);
+
+ // before resize
+ node.Position.X.Should().Be(0);
+ node.Position.Y.Should().Be(0);
+ node.Size.Width.Should().Be(100);
+ node.Size.Height.Should().Be(200);
+
+ // resize
+ var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+ control.OnPointerDown(diagram, node, eventArgs);
+ eventArgs = new PointerEventArgs(-300, -300, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+ diagram.TriggerPointerMove(null, eventArgs);
+
+ // after resize
+ node.Position.X.Should().Be(0);
+ node.Position.Y.Should().Be(0);
+ node.Size.Width.Should().Be(0);
+ node.Size.Height.Should().Be(0);
+ }
+
+ [Fact]
+ public void DragResizer_ShouldResizeNode_WhenDiagramZoomedOut()
+ {
+ // setup
+ var diagram = new TestDiagram();
+ diagram.SetContainer(new Rectangle(0, 0, 1000, 400));
+ var node = new NodeModel(position: new Point(0, 0));
+ node.Size = new Size(100, 200);
+ var control = new ResizeControl(new BottomRightResizerProvider());
+ diagram.Controls.AddFor(node).Add(control);
+ diagram.SelectModel(node, false);
+ diagram.SetZoom(0.5);
+
+ // before resize
+ node.Position.X.Should().Be(0);
+ node.Position.Y.Should().Be(0);
+ node.Size.Width.Should().Be(100);
+ node.Size.Height.Should().Be(200);
+
+ // resize
+ var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+ control.OnPointerDown(diagram, node, eventArgs);
+ eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+ diagram.TriggerPointerMove(null, eventArgs);
+
+ // after resize
+ node.Position.X.Should().Be(0);
+ node.Position.Y.Should().Be(0);
+ node.Size.Width.Should().Be(120);
+ node.Size.Height.Should().Be(230);
+ }
+
+ [Fact]
+ public void DragResizer_ShouldResizeNode_WhenDiagramZoomedIn()
+ {
+ // setup
+ var diagram = new TestDiagram();
+ diagram.SetContainer(new Rectangle(0, 0, 1000, 400));
+ var node = new NodeModel(position: new Point(0, 0));
+ node.Size = new Size(100, 200);
+ var control = new ResizeControl(new BottomRightResizerProvider());
+ diagram.Controls.AddFor(node).Add(control);
+ diagram.SelectModel(node, false);
+ diagram.SetZoom(2);
+
+ // before resize
+ node.Position.X.Should().Be(0);
+ node.Position.Y.Should().Be(0);
+ node.Size.Width.Should().Be(100);
+ node.Size.Height.Should().Be(200);
+
+ // resize
+ var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+ control.OnPointerDown(diagram, node, eventArgs);
+ eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+ diagram.TriggerPointerMove(null, eventArgs);
+
+ // after resize
+ node.Position.X.Should().Be(0);
+ node.Position.Y.Should().Be(0);
+ node.Size.Width.Should().Be(105);
+ node.Size.Height.Should().Be(207.5);
+ }
+}
diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs
new file mode 100644
index 000000000..13d241d08
--- /dev/null
+++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs
@@ -0,0 +1,145 @@
+using Blazor.Diagrams.Core.Controls.Default;
+using Blazor.Diagrams.Core.Events;
+using Blazor.Diagrams.Core.Geometry;
+using Blazor.Diagrams.Core.Models;
+using Blazor.Diagrams.Core.Positions.Resizing;
+using FluentAssertions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Blazor.Diagrams.Core.Tests.Positions.Resizing;
+
+public class TopLeftResizerProviderTests
+{
+ [Fact]
+ public void DragResizer_ShouldResizeNode()
+ {
+ // setup
+ var diagram = new TestDiagram();
+ diagram.SetContainer(new Rectangle(0, 0, 1000, 400));
+ var node = new NodeModel(position: new Point(0, 0));
+ node.Size = new Size(100, 200);
+ var control = new ResizeControl(new TopLeftResizerProvider());
+ diagram.Controls.AddFor(node).Add(control);
+ diagram.SelectModel(node, false);
+
+ // before resize
+ node.Position.X.Should().Be(0);
+ node.Position.Y.Should().Be(0);
+ node.Size.Width.Should().Be(100);
+ node.Size.Height.Should().Be(200);
+
+ // resize
+ var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+ control.OnPointerDown(diagram, node, eventArgs);
+ eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+ diagram.TriggerPointerMove(null, eventArgs);
+
+ // after resize
+ node.Position.X.Should().Be(10);
+ node.Position.Y.Should().Be(15);
+ node.Size.Width.Should().Be(90);
+ node.Size.Height.Should().Be(185);
+ }
+
+ [Fact]
+ public void DragResizer_SmallerThanMinSize_SetsNodeToMinSize()
+ {
+ // setup
+ var diagram = new TestDiagram();
+ diagram.SetContainer(new Rectangle(0, 0, 1000, 400));
+ var node = new NodeModel(position: new Point(0, 0));
+ node.Size = new Size(100, 200);
+ var control = new ResizeControl(new TopLeftResizerProvider());
+ diagram.Controls.AddFor(node).Add(control);
+ diagram.SelectModel(node, false);
+
+ // before resize
+ node.Position.X.Should().Be(0);
+ node.Position.Y.Should().Be(0);
+ node.Size.Width.Should().Be(100);
+ node.Size.Height.Should().Be(200);
+
+ // resize
+ var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+ control.OnPointerDown(diagram, node, eventArgs);
+ eventArgs = new PointerEventArgs(99, 199, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+ diagram.TriggerPointerMove(null, eventArgs);
+ eventArgs = new PointerEventArgs(300, 300, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+ diagram.TriggerPointerMove(null, eventArgs);
+
+ // after resize
+ node.Position.X.Should().Be(99);
+ node.Position.Y.Should().Be(199);
+ node.Size.Width.Should().Be(0);
+ node.Size.Height.Should().Be(0);
+ }
+
+ [Fact]
+ public void DragResizer_ShouldResizeNode_WhenDiagramZoomedOut()
+ {
+ // setup
+ var diagram = new TestDiagram();
+ diagram.SetContainer(new Rectangle(0, 0, 1000, 400));
+ var node = new NodeModel(position: new Point(0, 0));
+ node.Size = new Size(100, 200);
+ var control = new ResizeControl(new TopLeftResizerProvider());
+ diagram.Controls.AddFor(node).Add(control);
+ diagram.SelectModel(node, false);
+ diagram.SetZoom(0.5);
+
+ // before resize
+ node.Position.X.Should().Be(0);
+ node.Position.Y.Should().Be(0);
+ node.Size.Width.Should().Be(100);
+ node.Size.Height.Should().Be(200);
+
+ // resize
+ var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+ control.OnPointerDown(diagram, node, eventArgs);
+ eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+ diagram.TriggerPointerMove(null, eventArgs);
+
+ // after resize
+ node.Position.X.Should().Be(20);
+ node.Position.Y.Should().Be(30);
+ node.Size.Width.Should().Be(80);
+ node.Size.Height.Should().Be(170);
+ }
+
+ [Fact]
+ public void DragResizer_ShouldResizeNode_WhenDiagramZoomedIn()
+ {
+ // setup
+ var diagram = new TestDiagram();
+ diagram.SetContainer(new Rectangle(0, 0, 1000, 400));
+ var node = new NodeModel(position: new Point(0, 0));
+ node.Size = new Size(100, 200);
+ var control = new ResizeControl(new TopLeftResizerProvider());
+ diagram.Controls.AddFor(node).Add(control);
+ diagram.SelectModel(node, false);
+ diagram.SetZoom(2);
+
+ // before resize
+ node.Position.X.Should().Be(0);
+ node.Position.Y.Should().Be(0);
+ node.Size.Width.Should().Be(100);
+ node.Size.Height.Should().Be(200);
+
+ // resize
+ var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+ control.OnPointerDown(diagram, node, eventArgs);
+ eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+ diagram.TriggerPointerMove(null, eventArgs);
+
+ // after resize
+ node.Position.X.Should().Be(5);
+ node.Position.Y.Should().Be(7.5);
+ node.Size.Width.Should().Be(95);
+ node.Size.Height.Should().Be(192.5);
+ }
+}
diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs
new file mode 100644
index 000000000..5fcd3b821
--- /dev/null
+++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs
@@ -0,0 +1,145 @@
+using Blazor.Diagrams.Core.Controls.Default;
+using Blazor.Diagrams.Core.Events;
+using Blazor.Diagrams.Core.Geometry;
+using Blazor.Diagrams.Core.Models;
+using Blazor.Diagrams.Core.Positions.Resizing;
+using FluentAssertions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Blazor.Diagrams.Core.Tests.Positions.Resizing;
+
+public class TopRightResizerProviderTests
+{
+ [Fact]
+ public void DragResizer_ShouldResizeNode()
+ {
+ // setup
+ var diagram = new TestDiagram();
+ diagram.SetContainer(new Rectangle(0, 0, 1000, 400));
+ var node = new NodeModel(position: new Point(0, 0));
+ node.Size = new Size(100, 200);
+ var control = new ResizeControl(new TopRightResizerProvider());
+ diagram.Controls.AddFor(node).Add(control);
+ diagram.SelectModel(node, false);
+
+ // before resize
+ node.Position.X.Should().Be(0);
+ node.Position.Y.Should().Be(0);
+ node.Size.Width.Should().Be(100);
+ node.Size.Height.Should().Be(200);
+
+ // resize
+ var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+ control.OnPointerDown(diagram, node, eventArgs);
+ eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+ diagram.TriggerPointerMove(null, eventArgs);
+
+ // after resize
+ node.Position.X.Should().Be(0);
+ node.Position.Y.Should().Be(15);
+ node.Size.Width.Should().Be(110);
+ node.Size.Height.Should().Be(185);
+ }
+
+ [Fact]
+ public void DragResizer_SmallerThanMinSize_SetsNodeToMinSize()
+ {
+ // setup
+ var diagram = new TestDiagram();
+ diagram.SetContainer(new Rectangle(0, 0, 1000, 400));
+ var node = new NodeModel(position: new Point(0, 0));
+ node.Size = new Size(100, 200);
+ var control = new ResizeControl(new TopRightResizerProvider());
+ diagram.Controls.AddFor(node).Add(control);
+ diagram.SelectModel(node, false);
+
+ // before resize
+ node.Position.X.Should().Be(0);
+ node.Position.Y.Should().Be(0);
+ node.Size.Width.Should().Be(100);
+ node.Size.Height.Should().Be(200);
+
+ // resize
+ var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+ control.OnPointerDown(diagram, node, eventArgs);
+ eventArgs = new PointerEventArgs(-99, 199, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+ diagram.TriggerPointerMove(null, eventArgs);
+ eventArgs = new PointerEventArgs(-300, 300, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+ diagram.TriggerPointerMove(null, eventArgs);
+
+ // after resize
+ node.Position.X.Should().Be(0);
+ node.Position.Y.Should().Be(199);
+ node.Size.Width.Should().Be(0);
+ node.Size.Height.Should().Be(0);
+ }
+
+ [Fact]
+ public void DragResizer_ShouldResizeNode_WhenDiagramZoomedOut()
+ {
+ // setup
+ var diagram = new TestDiagram();
+ diagram.SetContainer(new Rectangle(0, 0, 1000, 400));
+ var node = new NodeModel(position: new Point(0, 0));
+ node.Size = new Size(100, 200);
+ var control = new ResizeControl(new TopRightResizerProvider());
+ diagram.Controls.AddFor(node).Add(control);
+ diagram.SelectModel(node, false);
+ diagram.SetZoom(0.5);
+
+ // before resize
+ node.Position.X.Should().Be(0);
+ node.Position.Y.Should().Be(0);
+ node.Size.Width.Should().Be(100);
+ node.Size.Height.Should().Be(200);
+
+ // resize
+ var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+ control.OnPointerDown(diagram, node, eventArgs);
+ eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+ diagram.TriggerPointerMove(null, eventArgs);
+
+ // after resize
+ node.Position.X.Should().Be(0);
+ node.Position.Y.Should().Be(30);
+ node.Size.Width.Should().Be(120);
+ node.Size.Height.Should().Be(170);
+ }
+
+ [Fact]
+ public void DragResizer_ShouldResizeNode_WhenDiagramZoomedIn()
+ {
+ // setup
+ var diagram = new TestDiagram();
+ diagram.SetContainer(new Rectangle(0, 0, 1000, 400));
+ var node = new NodeModel(position: new Point(0, 0));
+ node.Size = new Size(100, 200);
+ var control = new ResizeControl(new TopRightResizerProvider());
+ diagram.Controls.AddFor(node).Add(control);
+ diagram.SelectModel(node, false);
+ diagram.SetZoom(2);
+
+ // before resize
+ node.Position.X.Should().Be(0);
+ node.Position.Y.Should().Be(0);
+ node.Size.Width.Should().Be(100);
+ node.Size.Height.Should().Be(200);
+
+ // resize
+ var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+ control.OnPointerDown(diagram, node, eventArgs);
+ eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+ diagram.TriggerPointerMove(null, eventArgs);
+
+ // after resize
+ node.Position.X.Should().Be(0);
+ node.Position.Y.Should().Be(7.5);
+ node.Size.Width.Should().Be(105);
+ node.Size.Height.Should().Be(192.5);
+ }
+}
diff --git a/tests/Blazor.Diagrams.Tests/Components/Controls/ResizeControlWidgetTests.cs b/tests/Blazor.Diagrams.Tests/Components/Controls/ResizeControlWidgetTests.cs
new file mode 100644
index 000000000..ee4aa80a1
--- /dev/null
+++ b/tests/Blazor.Diagrams.Tests/Components/Controls/ResizeControlWidgetTests.cs
@@ -0,0 +1,24 @@
+using Blazor.Diagrams.Components.Controls;
+using Blazor.Diagrams.Core.Controls.Default;
+using Blazor.Diagrams.Core.Positions.Resizing;
+using Bunit;
+using Moq;
+using Xunit;
+
+namespace Blazor.Diagrams.Tests.Components.Controls;
+
+public class ResizeControlWidgetTests
+{
+ [Fact]
+ public void ShouldRenderDiv()
+ {
+ using var ctx = new TestContext();
+ var providerMock = Mock.Of();
+
+ var cut = ctx.RenderComponent(parameters =>
+ parameters.Add(w => w.Control, new ResizeControl(providerMock))
+ );
+
+ cut.MarkupMatches("");
+ }
+}