Setter { get; }
+ }
+}
diff --git a/site/Site/Models/Landing/SvgAndHtml/BatteryNodeModel.cs b/site/Site/Models/Landing/SvgAndHtml/BatteryNodeModel.cs
new file mode 100644
index 000000000..b1aa29bf3
--- /dev/null
+++ b/site/Site/Models/Landing/SvgAndHtml/BatteryNodeModel.cs
@@ -0,0 +1,37 @@
+using Blazor.Diagrams.Core.Geometry;
+using Blazor.Diagrams.Models;
+
+namespace Site.Models.Landing.SvgAndHtml
+{
+ public class BatteryNodeModel : SvgNodeModel
+ {
+ private int _firstCharge = 5;
+ private int _secondCharge = 15;
+
+ public BatteryNodeModel(Point position) : base(position)
+ {
+ }
+
+ public int FirstCharge
+ {
+ get => _firstCharge;
+ set
+ {
+ _firstCharge = value;
+ Refresh();
+ }
+ }
+
+ public int SecondCharge
+ {
+ get => _secondCharge;
+ set
+ {
+ _secondCharge = value;
+ Refresh();
+ }
+ }
+
+ public int Percentage => FirstCharge + SecondCharge;
+ }
+}
diff --git a/site/Site/Pages/Documentation/Diagram/Api.razor b/site/Site/Pages/Documentation/Diagram/Api.razor
new file mode 100644
index 000000000..af548a11f
--- /dev/null
+++ b/site/Site/Pages/Documentation/Diagram/Api.razor
@@ -0,0 +1,362 @@
+@page "/documentation/diagram-api"
+@layout DocumentationLayout
+@inherits DocumentationPage
+
+@{
+ RenderFragment readonlySubtitle = @Read only ;
+}
+
+Diagram API - Documentation - Blazor Diagrams
+
+Diagram API
+
+Properties
+
+
+
+
+ Name
+ Type
+ Default
+ Description
+
+
+
+
+ Options @readonlySubtitle
+ BlazorDiagramOptions
+ -
+
+
+
+ Nodes @readonlySubtitle
+ NodeLayer
+ -
+ Acts as a list for the nodes
+
+
+ Links @readonlySubtitle
+ LinkLayer
+ -
+ Acts as a list for the links
+
+
+ Groups @readonlySubtitle
+ GroupLayer
+ -
+ Acts as a list for the groups
+
+
+ Controls @readonlySubtitle
+ ControlsLayer
+ -
+ Only way to Get/Add/Remove controls for a model
+
+
+ Container @readonlySubtitle
+ Rectangle?
+ null
+ Boundaries representing the canvas, retrieved with JS
+
+
+ Pan @readonlySubtitle
+ Point?
+ Zero
+
+
+
+ Zoom @readonlySubtitle
+ Double
+ 1
+
+
+
+ SuspendRefresh
+ Boolean
+ false
+ Suspends the refreshes related to the diagram (canvas)
+
+
+ OrderedSelectables @readonlySubtitle
+ IReadOnlyList<SelectableModel>
+ -
+ Ordered list of models using the Order property
+
+
+
+
+Events
+
+
+
+
+ Name
+ Type
+ Description
+
+
+
+
+ PointerDown
+ Action<Model?, PointerEventArgs>?
+
+
+
+ PointerMove
+ Action<Model?, PointerEventArgs>?
+
+
+
+ PointerUp
+ Action<Model?, PointerEventArgs>?
+
+
+
+ PointerEnter
+ Action<Model?, PointerEventArgs>?
+
+
+
+ PointerLeave
+ Action<Model?, PointerEventArgs>?
+
+
+
+ KeyDown
+ Action<KeyboardEventArgs>?
+
+
+
+ Wheel
+ Action<WheelEventArgs>?
+
+
+
+ PointerClick
+ Action<Model?, PointerEventArgs>?
+
+
+
+ PointerDoubleClick
+ Action<Model?, PointerEventArgs>?
+
+
+
+ SelectionChanged
+ Action<SelectableModel>?
+
+
+
+ PanChanged
+ Action?
+
+
+
+ ZoomChanged
+ Action?
+
+
+
+ ContainerChanged
+ Action?
+
+
+
+ Changed
+ Action?
+
+
+
+
+
+Methods
+
+
+
+
+ Name
+ Return Type
+ Description
+
+
+
+
+ RegisterComponent<TModel, TComponent>(bool replace = false)
+ void
+ Register a Blazor component to render for every instance of TModel
+
+
+ RegisterComponent(Type modelType, Type componentType, bool replace = false)
+ void
+ Register a Blazor component to render for every instance of modelType
+
+
+ GetComponent(Type modelType, bool checkSubclasses = true)
+ Type?
+ Returns the component Type registered for the modelType
+
+
+ GetComponent<TModel>(bool checkSubclasses = true)
+ Type?
+ Returns the component Type registered for TModel
+
+
+ GetComponent(Model model, bool checkSubclasses = true)
+ Type?
+ Returns the component Type registered for typeof(Model)
+
+
+ Refresh()
+ void
+
+
+
+ Batch(Action action)
+ void
+
+
+
+ GetSelectedModels()
+ IEnumerable<SelectableModel>
+
+
+
+ SelectModel(SelectableModel model, bool unselectOthers)
+ void
+
+
+
+ UnselectModel(SelectableModel model)
+ void
+
+
+
+ UnselectAll()
+ void
+
+
+
+ RegisterBehavior(Behavior behavior)
+ void
+
+
+
+ GetBehavior<T>() where T : Behavior
+ T?
+
+
+
+ UnregisterBehavior<T>() where T : Behavior
+ void
+
+
+
+ ZoomToFit(double margin
+ void
+
+
+
+ SetPan(double x, double y)
+ void
+
+
+
+ UpdatePan(double deltaX, double deltaY)
+ void
+
+
+
+ SetZoom(double newZoom)
+ void
+
+
+
+ SetContainer(Rectangle newRect)
+ void
+
+
+
+ GetRelativeMousePoint(double clientX, double clientY)
+ Point
+
+
+
+ GetRelativePoint(double clientX, double clientY)
+ Point
+
+
+
+ GetScreenPoint(double clientX, double clientY)
+ Point
+
+
+
+ SendToBack(SelectableModel model)
+ void
+
+
+
+ SendToFront(SelectableModel model)
+ void
+
+
+
+ GetMinOrder()
+ int
+
+
+
+ GetMaxOrder()
+ int
+
+
+
+ TriggerPointerDown(Model? model, PointerEventArgs e)
+ void
+
+
+
+ TriggerPointerMove(Model? model, PointerEventArgs e)
+ void
+
+
+
+ TriggerPointerUp(Model? model, PointerEventArgs e)
+ void
+
+
+
+ TriggerPointerEnter(Model? model, PointerEventArgs e)
+ void
+
+
+
+ TriggerPointerLeave(Model? model, PointerEventArgs e)
+ void
+
+
+
+ TriggerKeyDown(KeyboardEventArgs e)
+ void
+
+
+
+ TriggerWheel(WheelEventArgs e)
+ void
+
+
+
+ TriggerPointerClick(Model? model, PointerEventArgs e)
+ void
+
+
+
+ TriggerPointerDoubleClick(Model? model, PointerEventArgs e)
+ void
+
+
+
+
+
+
\ No newline at end of file
diff --git a/site/Site/Pages/Documentation/Diagram/Api.razor.css b/site/Site/Pages/Documentation/Diagram/Api.razor.css
new file mode 100644
index 000000000..50dbcec91
--- /dev/null
+++ b/site/Site/Pages/Documentation/Diagram/Api.razor.css
@@ -0,0 +1,3 @@
+tr td:first-child {
+ font-weight: 600;
+}
diff --git a/site/Site/Pages/Documentation/Diagram/Behaviors.razor b/site/Site/Pages/Documentation/Diagram/Behaviors.razor
new file mode 100644
index 000000000..7a0c20e93
--- /dev/null
+++ b/site/Site/Pages/Documentation/Diagram/Behaviors.razor
@@ -0,0 +1,120 @@
+@page "/documentation/diagram-behaviors"
+@layout DocumentationLayout
+@inherits DocumentationPage
+
+Diagram Behaviors - Documentation - Blazor Diagrams
+
+Diagram Behaviors
+
+
+ Behaviors are a way to encapsulate a functionality.
+ They are separated into classes mainly for readability, separation of concerns, and for the library users to be able to remove/replace them.
+ The behaviors inherit from the base class Behavior
and use the available events to do what they need.
+
+
+Example
+
+
+using Blazor.Diagrams.Core.Models.Base;
+using Blazor.Diagrams.Core.Events;
+
+namespace Blazor.Diagrams.Core.Behaviors
+{
+ public class SelectionBehavior : Behavior
+ {
+ public SelectionBehavior(Diagram diagram) : base(diagram)
+ {
+ Diagram.PointerDown += OnPointerDown;
+ }
+
+ private void OnPointerDown(Model? model, PointerEventArgs e)
+ {
+ var ctrlKey = e.CtrlKey;
+
+ switch (model)
+ {
+ case null:
+ Diagram.UnselectAll();
+ break;
+ case SelectableModel sm when ctrlKey && sm.Selected:
+ Diagram.UnselectModel(sm);
+ break;
+ case SelectableModel sm:
+ {
+ if (!sm.Selected)
+ {
+ Diagram.SelectModel(sm, !ctrlKey || !Diagram.Options.AllowMultiSelection);
+ }
+
+ break;
+ }
+ }
+ }
+
+ public override void Dispose()
+ {
+ Diagram.PointerDown -= OnPointerDown;
+ }
+ }
+}
+
+
+
+ As you can see, the behavior simply uses the PointerDown
event in order to (un)select stuff.
+
+
+Replacing a behavior
+
+Let's say you didn't like how the SelectionBehavior
works, let's replace it by a simpler one.
+We just want to keep selecting models without CTRL and only unselect everything once the canvas is clicked.
+
+
+using Blazor.Diagrams.Core.Models.Base;
+using Blazor.Diagrams.Core.Events;
+
+namespace Blazor.Diagrams.Core.Behaviors
+{
+ public class MySelectionBehavior : Behavior
+ {
+ public SelectionBehavior(Diagram diagram) : base(diagram)
+ {
+ Diagram.PointerDown += OnPointerDown;
+ }
+
+ private void OnPointerDown(Model? model, PointerEventArgs e)
+ {
+ if (model == null) // Canvas
+ {
+ Diagram.UnselectAll();
+ }
+ else if (model is SelectableModel sm)
+ {
+ Diagram.SelectModel(sm);
+ }
+ }
+
+ public override void Dispose()
+ {
+ Diagram.PointerDown -= OnPointerDown;
+ }
+ }
+}
+
+
+
+ A very simple class indeed. Inherting from Behavior
automatically forces you to have the constructor, in order to have the Diagram
instance,
+ but also the Dispose
method in order to remove any event handlers in case the behavior is unregistered.
+ Let's now replace the old behavior with ours:
+
+
+
+// In case you want to keep the old behavior intance
+var oldSelectionBehavior = Diagram.GetBehavior<SelectionBehavior>()!;
+Diagram.UnregisterBehavior<SelectionBehavior>();
+Diagram.RegisterBehavior(new MySelectionBehavior(Diagram));
+
+
+
\ No newline at end of file
diff --git a/site/Site/Pages/Documentation/Diagram/KeyboardShortcuts.razor b/site/Site/Pages/Documentation/Diagram/KeyboardShortcuts.razor
new file mode 100644
index 000000000..89d1b39f2
--- /dev/null
+++ b/site/Site/Pages/Documentation/Diagram/KeyboardShortcuts.razor
@@ -0,0 +1,59 @@
+@page "/documentation/keyboard-shortcuts"
+@layout DocumentationLayout
+@inherits DocumentationPage
+
+Keyboard Shortcuts - Documentation - Blazor Diagrams
+
+Keyboard Shortcuts
+
+
+ Blazor Diagrams provides a utility behavior to handle keyboard shortcuts: KeyboardShortcutsBehavior
.
+
+
+Default shortcuts
+
+
+
+
+ Keys
+ Implementation
+ Description
+
+
+
+
+ Delete
+ KeyboardShortcutsDefaults.DeleteSelection
+ Deletes the selected models (nodes, groups and links)
+
+
+ Ctrl+Alt+g
+ KeyboardShortcutsDefaults.Grouping
+ Groups the selected models
+
+
+
+
+Adding a shortcut
+
+
+var ksb = _blazorDiagram.GetBehavior<KeyboardShortcutsBehavior>();
+ksb.SetShortcut("s", ctrl: true, shift: true, alt: true, SaveToMyServer);
+
+private async ValueTask SaveToMyServer()
+{
+ await FakeHttpCall();
+}
+
+
+Removing a shortcut
+
+
+var ksb = _blazorDiagram.GetBehavior<KeyboardShortcutsBehavior>();
+ksb.RemoveShortcut("s", ctrl: true, shift: true, alt: true);
+
+
+
\ No newline at end of file
diff --git a/site/Site/Pages/Documentation/Diagram/Options.razor b/site/Site/Pages/Documentation/Diagram/Options.razor
new file mode 100644
index 000000000..f74f01c67
--- /dev/null
+++ b/site/Site/Pages/Documentation/Diagram/Options.razor
@@ -0,0 +1,284 @@
+@page "/documentation/diagram-options"
+@layout DocumentationLayout
+@inherits DocumentationPage
+
+Diagram Options - Documentation - Blazor Diagrams
+
+Diagram Options
+
+
+ Here are all the available options in BlazorDiagramOptions
:
+
+
+
+
+
+ Name
+ Type
+ Default
+ Description
+
+
+
+
+ LinksLayerOrder
+ Integer
+ 0
+ The order (z-index) of the SVG layer
+
+
+ NodesLayerOrder
+ Integer
+ 0
+ The order (z-index) of the HTML layer
+
+
+ Zoom
+ BlazorDiagramZoomOptions
+
+
+
+
+ Links
+ BlazorDiagramLinkOptions
+
+
+
+
+ Groups
+ BlazorDiagramGroupOptions
+
+
+
+
+ Constraints
+ BlazorDiagramConstraintsOptions
+
+
+
+
+ Virtualization
+ BlazorDiagramVirtualizationOptions
+
+
+
+
+
+
+Zoom
+
+
+
+
+ Name
+ Type
+ Default
+ Description
+
+
+
+
+ Enabled
+ Boolean
+ true
+
+
+
+ Inverse
+ Boolean
+ false
+
+
+
+ Minimum
+ Double
+ 0.1
+
+
+
+ Maximum
+ Double
+ 2
+
+
+
+ ScaleFactor
+ Double
+ 1.05
+
+
+
+
+
+Links
+
+
+
+
+ Name
+ Type
+ Default
+ Description
+
+
+
+
+ DefaultColor
+ String
+ black
+
+
+
+ DefaultSelectedColor
+ String
+ rgb(110, 159, 212)
+
+
+
+ DefaultRouter
+ Router
+ NormalRouter
+
+
+
+ DefaultPathGenerator
+ PathGenerator
+ SmoothPathGenerator
+
+
+
+ EnableSnapping
+ Boolean
+ false
+ If true, dragging new links will snap them to the closest ports
+
+
+ SnappingRadius
+ Double
+ 50
+ Used to calculate the distance and decide whether to snap the link
+
+
+ RequireTarget
+ Boolean
+ true
+ If false, links can be free (without target)
+
+
+ Factory
+ LinkFactory
+ -
+ Delegate to control how a link is instanciated
+
+
+ TargetAnchorFactory
+ AnchorFactory
+ -
+ Delegate to control how the target anchor is instanciated
+
+
+
+
+Groups
+
+
+
+
+ Name
+ Type
+ Default
+ Description
+
+
+
+
+ Enabled
+ Boolean
+ false
+
+
+
+ Factory
+ GroupFactory
+ -
+ Delegate to control how a group is instanciated
+
+
+
+
+Constraints
+
+
+
+
+ Name
+ Type
+ Default
+ Description
+
+
+
+
+ ShouldDeleteNode
+ Func<NodeModel, ValueTask<bool>>
+ true
+
+
+
+ ShouldDeleteLink
+ Func<BaseLinkModel, ValueTask<bool>>
+ true
+
+
+
+ ShouldDeleteGroup
+ Func<GroupModel, ValueTask<bool>>
+ true
+
+
+
+
+
+Virtualization
+
+
+
+
+ Name
+ Type
+ Default
+ Description
+
+
+
+
+ Enabled
+ Boolean
+ false
+
+
+
+ OnNodes
+ Boolean
+ true
+
+
+
+ OnGroups
+ Boolean
+ false
+
+
+
+ OnLinks
+ Boolean
+ false
+
+
+
+
+
+
\ No newline at end of file
diff --git a/site/Site/Pages/Documentation/Diagram/Overview.razor b/site/Site/Pages/Documentation/Diagram/Overview.razor
new file mode 100644
index 000000000..0ce5b26f1
--- /dev/null
+++ b/site/Site/Pages/Documentation/Diagram/Overview.razor
@@ -0,0 +1,66 @@
+@page "/documentation/diagram"
+@layout DocumentationLayout
+@inherits DocumentationPage
+
+Diagram - Documentation - Blazor Diagrams
+
+Diagram
+
+Structure
+
+The component DiagramCanvas
generates the follolwing structure:
+
+
+<div class="diagram-canvas" tabindex="-1">
+ <!-- LAYERS -->
+</div>
+
+
+You can add your own CSS classes using the Class
parameter.
+
+Layers
+
+
+ It is very important to know that in Blazor Diagrams, everything is rendered in 2 layers:
+
+
+SVG
+
+
+<svg class="diagram-svg-layer" style="transform: translate(0px, 0px) scale(1); z-index: 0;">
+
+
+
+ This layer is basically a svg. It is mainly used to render links, since they require the <path>
element.
+ However, it is also possible to render nodes in it by inherting from SvgNodeModel
or SvgGroupModel
.
+ This lets you use any svg elements and features with ease.
+
+
+HTML
+
+
+<div class="diagram-html-layer" style="transform: translate(0px, 0px) scale(1); z-index: 0;">
+
+
+
+ This layer is a simple html div. It is mainly used to render nodes.
+ It is very useful since you can re-use any html you might already have in your nodes, as well as include interactivity through inputs for example.
+
+
+Additional Content
+
+You can add additional content to each layer using the available render fragments:
+
+
+<DiagramCanvas>
+ <AdditionalHtml>
+ <!-- EXTRA HTML -->
+ </AdditionalHtml>
+ <AdditionalSvg>
+ <!-- EXTRA SVG -->
+ </AdditionalSvg>
+</DiagramCanvas>
+
+
+
\ No newline at end of file
diff --git a/site/Site/Pages/Documentation/DocumentationPage.cs b/site/Site/Pages/Documentation/DocumentationPage.cs
new file mode 100644
index 000000000..881f60445
--- /dev/null
+++ b/site/Site/Pages/Documentation/DocumentationPage.cs
@@ -0,0 +1,15 @@
+using Microsoft.AspNetCore.Components;
+using Microsoft.JSInterop;
+
+namespace Site.Pages.Documentation
+{
+ public class DocumentationPage : ComponentBase
+ {
+ [Inject] protected IJSRuntime JSRuntime { get; set; } = null!;
+
+ protected override async Task OnAfterRenderAsync(bool firstRender)
+ {
+ await JSRuntime.InvokeVoidAsync("Prism.highlightAll");
+ }
+ }
+}
diff --git a/site/Site/Pages/Documentation/GettingStarted/DiagramCreation.razor b/site/Site/Pages/Documentation/GettingStarted/DiagramCreation.razor
new file mode 100644
index 000000000..f2069f5f2
--- /dev/null
+++ b/site/Site/Pages/Documentation/GettingStarted/DiagramCreation.razor
@@ -0,0 +1,126 @@
+@page "/documentation/diagram-creation"
+@layout DocumentationLayout
+@inherits DocumentationPage
+
+Diagram Creation - Documentation - Blazor Diagrams
+
+Diagram Creation
+
+
+ You've installed the library, now let's create a diagram!
+ Blazor.Diagrams is very code/oop oriented at the moment, which means all the diagram information needs to be given to it using code.
+
+
+Creating a Diagram
+
+
+ First, we'll start by creating a BlazorDiagram
, which holds all the data of a diagram, as well as its behaviors.
+ Create a new component and initialize a new diagram:
+
+
+MyDiagram.razor.cs
+
+using Blazor.Diagrams;
+
+namespace MyNamespace;
+
+public partial class MyDiagram
+{
+ private BlazorDiagram Diagram { get; set; } = null!;
+
+ protected override void OnInitialized()
+ {
+ Diagram = new BlazorDiagram();
+ }
+}
+
+
+Changing Options
+
+MyDiagram.razor.cs
+
+using Blazor.Diagrams;
+using Blazor.Diagrams.Core.PathGenerators;
+using Blazor.Diagrams.Core.Routers;
+using Blazor.Diagrams.Options;
+
+namespace Site.Components.Documentation;
+
+public partial class MyDiagram
+{
+ private BlazorDiagram Diagram { get; set; } = null!;
+
+ protected override void OnInitialized()
+ {
+ var options = new BlazorDiagramOptions
+ {
+ AllowMultiSelection = true,
+ Zoom =
+ {
+ Enabled = false,
+ },
+ Links =
+ {
+ DefaultRouter = new NormalRouter(),
+ DefaultPathGenerator = new SmoothPathGenerator()
+ },
+ };
+
+ Diagram = new BlazorDiagram(options);
+ }
+}
+
+
+
+ You can check out all the available options here .
+
+
+Adding Nodes
+
+
+ Continuing on our initialization code before, let's create two nodes:
+
+
+MyDiagram.razor.cs
+
+var firstNode = Diagram.Nodes.Add(new NodeModel(position: new Point(50, 50))
+{
+ Title = "Node 1"
+});
+var secondNode = Diagram.Nodes.Add(new NodeModel(position: new Point(200, 100))
+{
+ Title = "Node 2"
+});
+var leftPort = secondNode.AddPort(PortAlignment.Left);
+var rightPort = secondNode.AddPort(PortAlignment.Right);
+
+
+
+ As you can see, we give the nodes a position and a title, as well as 2 ports for the second node.
+
+
+Adding a Link
+
+
+ In Blazor.Diagrams, links are created between 2 anchors (input and output).
+ The library comes with a few out of the box anchors, we'll be using two of them to show how a link can be created from a node to a port.
+
+
+MyDiagram.razor.cs
+
+// The connection point will be the intersection of
+// a line going from the target to the center of the source
+var sourceAnchor = new ShapeIntersectionAnchor(firstNode);
+// The connection point will be the port's position
+var targetAnchor = new SinglePortAnchor(leftPort);
+var link = Diagram.Links.Add(new LinkModel(sourceAnchor, targetAnchor));
+
+
+
+ You now have a diagram containing 2 nodes, 2 ports and one link.
+
+
+
\ No newline at end of file
diff --git a/site/Site/Pages/Documentation/GettingStarted/Display.razor b/site/Site/Pages/Documentation/GettingStarted/Display.razor
new file mode 100644
index 000000000..b249297f7
--- /dev/null
+++ b/site/Site/Pages/Documentation/GettingStarted/Display.razor
@@ -0,0 +1,57 @@
+@page "/documentation/display"
+@layout DocumentationLayout
+@inherits DocumentationPage
+
+Display - Documentation - Blazor Diagrams
+
+Display
+
+
+ You've created a diagram and populated it, let's display it!
+ First, let's setup our component so that it has a specific width and height.
+ In order for us to display the diagram, a visible parent is mandatory, since the canvas uses 100% width/height.
+
+
+MyDiagram.razor
+
+<div class="diagram-container">
+
+</div>
+
+
+Now create a scoped CSS file with the following content:
+MyDiagram.razor.css
+
+.diagram-container {
+ width: 100%;
+ height: 400px;
+ border: 1px solid black; /* Just visual */
+}
+
+
+Now that we have a visible container, let's display the diagram:
+
+MyDiagram.razor
+
+@@using Blazor.Diagrams.Components
+
+<div class="diagram-container">
+ <CascadingValue Value="Diagram" IsFixed="true">
+ <DiagramCanvas></DiagramCanvas>
+ </CascadingValue>
+</div>
+
+
+
+ If you used the default styles and did everything correctly, here's how the diagram would look:
+ It's interactive, play with the nodes!
+
+
+
+
+
+ Congratulations! You're now ready to move to the interesting stuff.
+
+
+
\ No newline at end of file
diff --git a/site/Site/Pages/Documentation/GettingStarted/Installation.razor b/site/Site/Pages/Documentation/GettingStarted/Installation.razor
new file mode 100644
index 000000000..d4993daf1
--- /dev/null
+++ b/site/Site/Pages/Documentation/GettingStarted/Installation.razor
@@ -0,0 +1,55 @@
+@page "/documentation/installation"
+@layout DocumentationLayout
+@inherits DocumentationPage
+
+Installation - Documentation - Blazor Diagrams
+
+Installation
+
+
+ Let's begin by installing the NuGet package.
+ You can use Visual Studio's Nuget Package Manager, or one of the following commands:
+
+
+.NET CLI
+
+
+dotnet add package Z.Blazor.Diagrams
+
+
+Package Manager
+
+
+Install-Package Z.Blazor.Diagrams
+
+
+Project Setup
+
+ Next, we'll setup the project and add the static resources for the library to work.
+
+
+wwwroot/index.html or Pages/_Host.cshtml
+
+<!DOCTYPE html>
+<html>
+
+<head>
+ <!-- ... -->
+ <link href="_content/Z.Blazor.Diagrams/style.min.css" rel="stylesheet" />
+ <!-- If you want the default style -->
+ <link href="_content/Z.Blazor.Diagrams/default.styles.min.css" rel="stylesheet" />
+</head>
+
+<body>
+ <!-- ... -->
+ <script src="_content/Z.Blazor.Diagrams/script.min.js"></script>
+</body>
+
+</html>
+
+
+
+ You're now ready to create your first diagram!
+
+
+
\ No newline at end of file
diff --git a/site/Site/Pages/Documentation/Groups/Overview.razor b/site/Site/Pages/Documentation/Groups/Overview.razor
new file mode 100644
index 000000000..4e7b56ada
--- /dev/null
+++ b/site/Site/Pages/Documentation/Groups/Overview.razor
@@ -0,0 +1,68 @@
+@page "/documentation/groups-overview"
+@layout DocumentationLayout
+@inherits DocumentationPage
+
+Groups - Documentation - Blazor Diagrams
+
+Groups
+
+
+ In Blazor Diagrams, Groups are a way to group nodes together.
+ Groups can also contain other groups, so you can create hierarchies.
+
+
+Structure
+
+
+ The component GroupRenderer
generates the follolwing structure:
+
+
+
+<div class="diagram-group ..."
+ data-group-id="28e9606d-08dd-47d5-a4c7-b95e541bcf1e"
+ style="top: 10px; left: 10px; width: 100px; height: 100px">
+ <!-- YOUR CONTENT WILL BE HERE -->
+</div>
+
+
+
+ The classes that the div can have (beside diagram-group
) are: locked
, selected
and default
.
+
+
+Creating a group
+
+Interactively
+
+
+ By default, users can group selected nodes/groups using the keys Ctrl+g
(if Options.Groups.Enabled
is set to true
).
+ Check out Keyboard Shortcuts in order to change the required keys.
+
+
+Programmatically
+
+Assuming we have two nodes, we can simply:
+
+
+var node1 = new NodeModel(new Point(10, 10));
+var node2 = new NodeModel(new Point(50, 50));
+var group = BlazorDiagram.Groups.Group(node1, node2);
+
+
+! Important Note
+
+Most of the time, groups will have a background of some sort (e.g. a color), which will hide all the links beneath it since
+if you remember, the nodes layer is above the links layer. There are 2 solutions for this:
+
+SVG Group
+
+If you use svg nodes, then make sure you add your groups first, then your links.
+
+If you're grouping nodes dynamically, make sure to bring links associated to those groups and their nodes to the front (or the created groups to the back) using the ordering methods available.
+
+Layers order
+
+The BlazorDiagramOptions
class contains two properties in order to control the order (z-index) of the layers: LinksLayerOrder
and NodesLayerOrder
.
+All you have to do for groups to work properly is to set LinksLayerOrder
to a bigger value than the other.
+
+
\ No newline at end of file
diff --git a/site/Site/Pages/Documentation/Groups/SVG.razor b/site/Site/Pages/Documentation/Groups/SVG.razor
new file mode 100644
index 000000000..449bac86d
--- /dev/null
+++ b/site/Site/Pages/Documentation/Groups/SVG.razor
@@ -0,0 +1,60 @@
+@page "/documentation/groups-svg"
+@layout DocumentationLayout
+@inherits DocumentationPage
+
+SVG Groups - Documentation - Blazor Diagrams
+
+SVG Groups
+
+
+ SVG groups are groups that will be rendered in the SVG layer.
+ In this page, we will talk about the differences between them and normal groups.
+
+
+Structure
+
+
+ The component GroupRenderer
generates the follolwing structure:
+
+
+
+<g class="diagram-group ..."
+ data-group-id="28e9606d-08dd-47d5-a4c7-b95e541bcf1e"
+ transform="translate(10 10)">
+
+ <rect width="100" height="100" fill="none"></rect>
+
+ <!-- YOUR CONTENT WILL BE HERE -->
+
+</g>
+
+
+The rect
is there for you to be able to style the background of the group, since we can't style g
elements.
+
+Creating a SVG group
+
+Interactively
+
+First, we will need how groups are created:
+
+
+yourDiagram.Options.Groups.Factory = (diagram, children) => new SvgGroupModel(children);
+
+
+
+ By default, users can group selected nodes/groups using the keys Ctrl+g
(if Options.Groups.Enabled
is set to true
).
+ Check out Keyboard Shortcuts in order to change the required keys.
+
+
+Programmatically
+
+Assuming we have two nodes, we can simply:
+
+
+var node1 = new SvgNodeModel(new Point(10, 10));
+var node2 = new SvgNodeModel(new Point(50, 50));
+var group = new SvgGroupModel(new[] { node1, node2 });
+
+
+
\ No newline at end of file
diff --git a/site/Site/Pages/Documentation/Index.razor b/site/Site/Pages/Documentation/Index.razor
new file mode 100644
index 000000000..a04f46c7a
--- /dev/null
+++ b/site/Site/Pages/Documentation/Index.razor
@@ -0,0 +1,6 @@
+@page "/documentation"
+@layout DocumentationLayout
+
+Documentation - Blazor Diagrams
+
+Overview
\ No newline at end of file
diff --git a/site/Site/Pages/Landing/Index.razor b/site/Site/Pages/Landing/Index.razor
new file mode 100644
index 000000000..5e3a2b116
--- /dev/null
+++ b/site/Site/Pages/Landing/Index.razor
@@ -0,0 +1,70 @@
+@page "/"
+
+Blazor Diagrams
+
+
+
+
+
+
+
+
+ @* configurable *@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Get in touch
+
+
+
+ Don't hesitate to ask about anything either on GitHub or even by email!
+
+
\ No newline at end of file
diff --git a/site/Site/Program.cs b/site/Site/Program.cs
new file mode 100644
index 000000000..41906bb16
--- /dev/null
+++ b/site/Site/Program.cs
@@ -0,0 +1,11 @@
+using Microsoft.AspNetCore.Components.Web;
+using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
+using Site;
+
+var builder = WebAssemblyHostBuilder.CreateDefault(args);
+builder.RootComponents.Add("#app");
+builder.RootComponents.Add("head::after");
+
+builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
+
+await builder.Build().RunAsync();
\ No newline at end of file
diff --git a/site/Site/Properties/launchSettings.json b/site/Site/Properties/launchSettings.json
new file mode 100644
index 000000000..4219fb5f4
--- /dev/null
+++ b/site/Site/Properties/launchSettings.json
@@ -0,0 +1,30 @@
+{
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:40851",
+ "sslPort": 44376
+ }
+ },
+ "profiles": {
+ "Site": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
+ "applicationUrl": "https://localhost:8032;http://localhost:8031",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/site/Site/Shared/DocumentationLayout.razor b/site/Site/Shared/DocumentationLayout.razor
new file mode 100644
index 000000000..106e8b8dc
--- /dev/null
+++ b/site/Site/Shared/DocumentationLayout.razor
@@ -0,0 +1,116 @@
+@inherits LayoutComponentBase
+
+@* Header *@
+
+
+
+
+ @* Sidebar Toggle *@
+
+
+
+ Toggle Navigation
+
+
+
+
+
+
+
+ @* Sidebar *@
+
+
+ @* Content *@
+
+
\ No newline at end of file
diff --git a/site/Site/Shared/DocumentationLayout.razor.cs b/site/Site/Shared/DocumentationLayout.razor.cs
new file mode 100644
index 000000000..0f0984fb2
--- /dev/null
+++ b/site/Site/Shared/DocumentationLayout.razor.cs
@@ -0,0 +1,31 @@
+using Microsoft.AspNetCore.Components;
+
+namespace Site.Shared
+{
+ public partial class DocumentationLayout
+ {
+ [Inject] private NavigationManager NavigationManager { get; set; } = null!;
+
+ private (string, string) GetMenuItemExtraClasses(string link)
+ {
+ if (IsActive(link, false))
+ return ("font-semibold text-main", "bg-main text-white");
+
+ return ("font-medium hover:text-slate-900", "bg-gray-100 text-black");
+ }
+
+ private string GetGroupMenuItemExtraClasses(string link)
+ {
+ if (IsActive(link, true))
+ return "text-palette-main border-palette-main font-semibold";
+
+ return "text-slate-700 hover:border-slate-400 hover:text-slate-900";
+ }
+
+ private bool IsActive(string link, bool fullMatch)
+ {
+ var relativePath = "/" + NavigationManager.ToBaseRelativePath(NavigationManager.Uri).ToLower();
+ return (fullMatch && relativePath == link) || (!fullMatch && relativePath.StartsWith(link));
+ }
+ }
+}
diff --git a/site/Site/Shared/DocumentationLayout.razor.css b/site/Site/Shared/DocumentationLayout.razor.css
new file mode 100644
index 000000000..5f282702b
--- /dev/null
+++ b/site/Site/Shared/DocumentationLayout.razor.css
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/site/Site/Shared/LandingLayout.razor b/site/Site/Shared/LandingLayout.razor
new file mode 100644
index 000000000..0eb50b998
--- /dev/null
+++ b/site/Site/Shared/LandingLayout.razor
@@ -0,0 +1,53 @@
+@inherits LayoutComponentBase
+
+
+
+
+
+
+@Body
\ No newline at end of file
diff --git a/site/Site/Shared/LandingLayout.razor.css b/site/Site/Shared/LandingLayout.razor.css
new file mode 100644
index 000000000..e69de29bb
diff --git a/site/Site/Site.csproj b/site/Site/Site.csproj
new file mode 100644
index 000000000..a85adb61e
--- /dev/null
+++ b/site/Site/Site.csproj
@@ -0,0 +1,26 @@
+
+
+
+ net6.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+ <_ContentIncludedByDefault Remove="wwwroot\css\bootstrap\bootstrap.min.css" />
+ <_ContentIncludedByDefault Remove="wwwroot\css\bootstrap\bootstrap.min.css.map" />
+ <_ContentIncludedByDefault Remove="wwwroot\sample-data\weather.json" />
+
+
+
+
+
+
+
+
+
diff --git a/site/Site/Static/Documentation.cs b/site/Site/Static/Documentation.cs
new file mode 100644
index 000000000..4ebb32f1f
--- /dev/null
+++ b/site/Site/Static/Documentation.cs
@@ -0,0 +1,34 @@
+using Site.Models.Documentation;
+
+namespace Site.Static
+{
+ public static class Documentation
+ {
+ public static readonly Menu Menu = new(new List
+ {
+ new MenuItem("Documentation", "/documentation", Icons.BookOpen),
+ new MenuItem("Examples", "/examples", Icons.FolderOpen),
+ }, new List
+ {
+ new MenuGroup("Getting Started", new List
+ {
+ new MenuItem("Installation", "/documentation/installation"),
+ new MenuItem("Diagram Creation", "/documentation/diagram-creation"),
+ new MenuItem("Display", "/documentation/display"),
+ }),
+ new MenuGroup("Diagram", new List
+ {
+ new MenuItem("Overview", "/documentation/diagram"),
+ new MenuItem("Behaviors", "/documentation/diagram-behaviors"),
+ new MenuItem("Options", "/documentation/diagram-options"),
+ new MenuItem("Keyboard Shortcuts", "/documentation/keyboard-shortcuts"),
+ new MenuItem("API", "/documentation/diagram-api"),
+ }),
+ new MenuGroup("Groups", new List
+ {
+ new MenuItem("Overview", "/documentation/groups-overview"),
+ new MenuItem("SVG", "/documentation/groups-svg"),
+ })
+ });
+ }
+}
diff --git a/site/Site/Static/Icons.cs b/site/Site/Static/Icons.cs
new file mode 100644
index 000000000..078529be9
--- /dev/null
+++ b/site/Site/Static/Icons.cs
@@ -0,0 +1,9 @@
+namespace Site.Static
+{
+ public static class Icons
+ {
+ public static readonly string Github = "M8 0C3.58 0 0 3.58 0 8C0 11.54 2.29 14.53 5.47 15.59C5.87 15.66 6.02 15.42 6.02 15.21C6.02 15.02 6.01 14.39 6.01 13.72C4 14.09 3.48 13.23 3.32 12.78C3.23 12.55 2.84 11.84 2.5 11.65C2.22 11.5 1.82 11.13 2.49 11.12C3.12 11.11 3.57 11.7 3.72 11.94C4.44 13.15 5.59 12.81 6.05 12.6C6.12 12.08 6.33 11.73 6.56 11.53C4.78 11.33 2.92 10.64 2.92 7.58C2.92 6.71 3.23 5.99 3.74 5.43C3.66 5.23 3.38 4.41 3.82 3.31C3.82 3.31 4.49 3.1 6.02 4.13C6.66 3.95 7.34 3.86 8.02 3.86C8.7 3.86 9.38 3.95 10.02 4.13C11.55 3.09 12.22 3.31 12.22 3.31C12.66 4.41 12.38 5.23 12.3 5.43C12.81 5.99 13.12 6.7 13.12 7.58C13.12 10.65 11.25 11.33 9.47 11.53C9.76 11.78 10.01 12.26 10.01 13.01C10.01 14.08 10 14.94 10 15.21C10 15.42 10.15 15.67 10.55 15.59C13.71 14.53 16 11.53 16 8C16 3.58 12.42 0 8 0Z";
+ public static readonly string BookOpen = "M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 016 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0018 18a8.967 8.967 0 00-6 2.292m0-14.25v14.25";
+ public static readonly string FolderOpen = "M3.75 9.776c.112-.017.227-.026.344-.026h15.812c.117 0 .232.009.344.026m-16.5 0a2.25 2.25 0 00-1.883 2.542l.857 6a2.25 2.25 0 002.227 1.932H19.05a2.25 2.25 0 002.227-1.932l.857-6a2.25 2.25 0 00-1.883-2.542m-16.5 0V6A2.25 2.25 0 016 3.75h3.879a1.5 1.5 0 011.06.44l2.122 2.12a1.5 1.5 0 001.06.44H18A2.25 2.25 0 0120.25 9v.776";
+ }
+}
diff --git a/site/Site/_Imports.razor b/site/Site/_Imports.razor
new file mode 100644
index 000000000..b1a980cc7
--- /dev/null
+++ b/site/Site/_Imports.razor
@@ -0,0 +1,30 @@
+@using System.Net.Http
+@using System.Net.Http.Json
+@using Microsoft.AspNetCore.Components.Forms
+@using Microsoft.AspNetCore.Components.Routing
+@using Microsoft.AspNetCore.Components.Web
+@using Microsoft.AspNetCore.Components.Web.Virtualization
+@using Microsoft.AspNetCore.Components.WebAssembly.Http
+@using Microsoft.JSInterop
+@using Site
+@using Site.Shared
+@using Site.Components
+@using Site.Components.Landing
+@using Site.Components.Landing.SvgAndHtml
+@using Site.Components.Landing.Groups
+@using Site.Components.Landing.Features
+@using Site.Models
+@using Site.Models.Landing
+@using Site.Models.Landing.Groups
+@using Site.Models.Landing.SvgAndHtml
+@using Site.Models.Documentation
+@using Blazor.Diagrams.Core
+@using Blazor.Diagrams.Core.Models
+@using Blazor.Diagrams.Components
+@using Blazor.Diagrams.Core.Extensions
+@using Blazor.Diagrams.Core.Geometry
+@using Blazor.Diagrams.Components.Renderers
+@using Blazor.Diagrams.Components.Widgets
+@using Site.Static
+@using Site.Components.Documentation
+@using Blazor.Diagrams
\ No newline at end of file
diff --git a/site/Site/tailwind.config.js b/site/Site/tailwind.config.js
new file mode 100644
index 000000000..9dc2ac896
--- /dev/null
+++ b/site/Site/tailwind.config.js
@@ -0,0 +1,13 @@
+/** @type {import('tailwindcss').Config} */
+module.exports = {
+ darkMode: 'class',
+ content: ["./**/*.{razor,html,cshtml,cs}"],
+ theme: {
+ extend: {
+ colors: {
+ 'palette': { main: "#40BABD" },
+ }
+ }
+ },
+ plugins: [],
+}
\ No newline at end of file
diff --git a/site/Site/wwwroot/css/app.css b/site/Site/wwwroot/css/app.css
new file mode 100644
index 000000000..804be6c5f
--- /dev/null
+++ b/site/Site/wwwroot/css/app.css
@@ -0,0 +1,1730 @@
+/*
+! tailwindcss v3.1.8 | MIT License | https://tailwindcss.com
+*/
+
+/*
+1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
+2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
+*/
+
+*,
+::before,
+::after {
+ box-sizing: border-box;
+ /* 1 */
+ border-width: 0;
+ /* 2 */
+ border-style: solid;
+ /* 2 */
+ border-color: #e5e7eb;
+ /* 2 */
+}
+
+::before,
+::after {
+ --tw-content: '';
+}
+
+/*
+1. Use a consistent sensible line-height in all browsers.
+2. Prevent adjustments of font size after orientation changes in iOS.
+3. Use a more readable tab size.
+4. Use the user's configured `sans` font-family by default.
+*/
+
+html {
+ line-height: 1.5;
+ /* 1 */
+ -webkit-text-size-adjust: 100%;
+ /* 2 */
+ -moz-tab-size: 4;
+ /* 3 */
+ -o-tab-size: 4;
+ tab-size: 4;
+ /* 3 */
+ font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+ /* 4 */
+}
+
+/*
+1. Remove the margin in all browsers.
+2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
+*/
+
+body {
+ margin: 0;
+ /* 1 */
+ line-height: inherit;
+ /* 2 */
+}
+
+/*
+1. Add the correct height in Firefox.
+2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
+3. Ensure horizontal rules are visible by default.
+*/
+
+hr {
+ height: 0;
+ /* 1 */
+ color: inherit;
+ /* 2 */
+ border-top-width: 1px;
+ /* 3 */
+}
+
+/*
+Add the correct text decoration in Chrome, Edge, and Safari.
+*/
+
+abbr:where([title]) {
+ -webkit-text-decoration: underline dotted;
+ text-decoration: underline dotted;
+}
+
+/*
+Remove the default font size and weight for headings.
+*/
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-size: inherit;
+ font-weight: inherit;
+}
+
+/*
+Reset links to optimize for opt-in styling instead of opt-out.
+*/
+
+a {
+ color: inherit;
+ text-decoration: inherit;
+}
+
+/*
+Add the correct font weight in Edge and Safari.
+*/
+
+b,
+strong {
+ font-weight: bolder;
+}
+
+/*
+1. Use the user's configured `mono` font family by default.
+2. Correct the odd `em` font sizing in all browsers.
+*/
+
+code,
+kbd,
+samp,
+pre {
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+ /* 1 */
+ font-size: 1em;
+ /* 2 */
+}
+
+/*
+Add the correct font size in all browsers.
+*/
+
+small {
+ font-size: 80%;
+}
+
+/*
+Prevent `sub` and `sup` elements from affecting the line height in all browsers.
+*/
+
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+sup {
+ top: -0.5em;
+}
+
+/*
+1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
+2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
+3. Remove gaps between table borders by default.
+*/
+
+table {
+ text-indent: 0;
+ /* 1 */
+ border-color: inherit;
+ /* 2 */
+ border-collapse: collapse;
+ /* 3 */
+}
+
+/*
+1. Change the font styles in all browsers.
+2. Remove the margin in Firefox and Safari.
+3. Remove default padding in all browsers.
+*/
+
+button,
+input,
+optgroup,
+select,
+textarea {
+ font-family: inherit;
+ /* 1 */
+ font-size: 100%;
+ /* 1 */
+ font-weight: inherit;
+ /* 1 */
+ line-height: inherit;
+ /* 1 */
+ color: inherit;
+ /* 1 */
+ margin: 0;
+ /* 2 */
+ padding: 0;
+ /* 3 */
+}
+
+/*
+Remove the inheritance of text transform in Edge and Firefox.
+*/
+
+button,
+select {
+ text-transform: none;
+}
+
+/*
+1. Correct the inability to style clickable types in iOS and Safari.
+2. Remove default button styles.
+*/
+
+button,
+[type='button'],
+[type='reset'],
+[type='submit'] {
+ -webkit-appearance: button;
+ /* 1 */
+ background-color: transparent;
+ /* 2 */
+ background-image: none;
+ /* 2 */
+}
+
+/*
+Use the modern Firefox focus style for all focusable elements.
+*/
+
+:-moz-focusring {
+ outline: auto;
+}
+
+/*
+Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
+*/
+
+:-moz-ui-invalid {
+ box-shadow: none;
+}
+
+/*
+Add the correct vertical alignment in Chrome and Firefox.
+*/
+
+progress {
+ vertical-align: baseline;
+}
+
+/*
+Correct the cursor style of increment and decrement buttons in Safari.
+*/
+
+::-webkit-inner-spin-button,
+::-webkit-outer-spin-button {
+ height: auto;
+}
+
+/*
+1. Correct the odd appearance in Chrome and Safari.
+2. Correct the outline style in Safari.
+*/
+
+[type='search'] {
+ -webkit-appearance: textfield;
+ /* 1 */
+ outline-offset: -2px;
+ /* 2 */
+}
+
+/*
+Remove the inner padding in Chrome and Safari on macOS.
+*/
+
+::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+
+/*
+1. Correct the inability to style clickable types in iOS and Safari.
+2. Change font properties to `inherit` in Safari.
+*/
+
+::-webkit-file-upload-button {
+ -webkit-appearance: button;
+ /* 1 */
+ font: inherit;
+ /* 2 */
+}
+
+/*
+Add the correct display in Chrome and Safari.
+*/
+
+summary {
+ display: list-item;
+}
+
+/*
+Removes the default spacing and border for appropriate elements.
+*/
+
+blockquote,
+dl,
+dd,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+hr,
+figure,
+p,
+pre {
+ margin: 0;
+}
+
+fieldset {
+ margin: 0;
+ padding: 0;
+}
+
+legend {
+ padding: 0;
+}
+
+ol,
+ul,
+menu {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+/*
+Prevent resizing textareas horizontally by default.
+*/
+
+textarea {
+ resize: vertical;
+}
+
+/*
+1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
+2. Set the default placeholder color to the user's configured gray 400 color.
+*/
+
+input::-moz-placeholder, textarea::-moz-placeholder {
+ opacity: 1;
+ /* 1 */
+ color: #9ca3af;
+ /* 2 */
+}
+
+input::placeholder,
+textarea::placeholder {
+ opacity: 1;
+ /* 1 */
+ color: #9ca3af;
+ /* 2 */
+}
+
+/*
+Set the default cursor for buttons.
+*/
+
+button,
+[role="button"] {
+ cursor: pointer;
+}
+
+/*
+Make sure disabled buttons don't get the pointer cursor.
+*/
+
+:disabled {
+ cursor: default;
+}
+
+/*
+1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
+2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
+ This can trigger a poorly considered lint error in some tools but is included by design.
+*/
+
+img,
+svg,
+video,
+canvas,
+audio,
+iframe,
+embed,
+object {
+ display: block;
+ /* 1 */
+ vertical-align: middle;
+ /* 2 */
+}
+
+/*
+Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
+*/
+
+img,
+video {
+ max-width: 100%;
+ height: auto;
+}
+
+*, ::before, ::after {
+ --tw-border-spacing-x: 0;
+ --tw-border-spacing-y: 0;
+ --tw-translate-x: 0;
+ --tw-translate-y: 0;
+ --tw-rotate: 0;
+ --tw-skew-x: 0;
+ --tw-skew-y: 0;
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ --tw-pan-x: ;
+ --tw-pan-y: ;
+ --tw-pinch-zoom: ;
+ --tw-scroll-snap-strictness: proximity;
+ --tw-ordinal: ;
+ --tw-slashed-zero: ;
+ --tw-numeric-figure: ;
+ --tw-numeric-spacing: ;
+ --tw-numeric-fraction: ;
+ --tw-ring-inset: ;
+ --tw-ring-offset-width: 0px;
+ --tw-ring-offset-color: #fff;
+ --tw-ring-color: rgb(59 130 246 / 0.5);
+ --tw-ring-offset-shadow: 0 0 #0000;
+ --tw-ring-shadow: 0 0 #0000;
+ --tw-shadow: 0 0 #0000;
+ --tw-shadow-colored: 0 0 #0000;
+ --tw-blur: ;
+ --tw-brightness: ;
+ --tw-contrast: ;
+ --tw-grayscale: ;
+ --tw-hue-rotate: ;
+ --tw-invert: ;
+ --tw-saturate: ;
+ --tw-sepia: ;
+ --tw-drop-shadow: ;
+ --tw-backdrop-blur: ;
+ --tw-backdrop-brightness: ;
+ --tw-backdrop-contrast: ;
+ --tw-backdrop-grayscale: ;
+ --tw-backdrop-hue-rotate: ;
+ --tw-backdrop-invert: ;
+ --tw-backdrop-opacity: ;
+ --tw-backdrop-saturate: ;
+ --tw-backdrop-sepia: ;
+}
+
+::-webkit-backdrop {
+ --tw-border-spacing-x: 0;
+ --tw-border-spacing-y: 0;
+ --tw-translate-x: 0;
+ --tw-translate-y: 0;
+ --tw-rotate: 0;
+ --tw-skew-x: 0;
+ --tw-skew-y: 0;
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ --tw-pan-x: ;
+ --tw-pan-y: ;
+ --tw-pinch-zoom: ;
+ --tw-scroll-snap-strictness: proximity;
+ --tw-ordinal: ;
+ --tw-slashed-zero: ;
+ --tw-numeric-figure: ;
+ --tw-numeric-spacing: ;
+ --tw-numeric-fraction: ;
+ --tw-ring-inset: ;
+ --tw-ring-offset-width: 0px;
+ --tw-ring-offset-color: #fff;
+ --tw-ring-color: rgb(59 130 246 / 0.5);
+ --tw-ring-offset-shadow: 0 0 #0000;
+ --tw-ring-shadow: 0 0 #0000;
+ --tw-shadow: 0 0 #0000;
+ --tw-shadow-colored: 0 0 #0000;
+ --tw-blur: ;
+ --tw-brightness: ;
+ --tw-contrast: ;
+ --tw-grayscale: ;
+ --tw-hue-rotate: ;
+ --tw-invert: ;
+ --tw-saturate: ;
+ --tw-sepia: ;
+ --tw-drop-shadow: ;
+ --tw-backdrop-blur: ;
+ --tw-backdrop-brightness: ;
+ --tw-backdrop-contrast: ;
+ --tw-backdrop-grayscale: ;
+ --tw-backdrop-hue-rotate: ;
+ --tw-backdrop-invert: ;
+ --tw-backdrop-opacity: ;
+ --tw-backdrop-saturate: ;
+ --tw-backdrop-sepia: ;
+}
+
+::backdrop {
+ --tw-border-spacing-x: 0;
+ --tw-border-spacing-y: 0;
+ --tw-translate-x: 0;
+ --tw-translate-y: 0;
+ --tw-rotate: 0;
+ --tw-skew-x: 0;
+ --tw-skew-y: 0;
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ --tw-pan-x: ;
+ --tw-pan-y: ;
+ --tw-pinch-zoom: ;
+ --tw-scroll-snap-strictness: proximity;
+ --tw-ordinal: ;
+ --tw-slashed-zero: ;
+ --tw-numeric-figure: ;
+ --tw-numeric-spacing: ;
+ --tw-numeric-fraction: ;
+ --tw-ring-inset: ;
+ --tw-ring-offset-width: 0px;
+ --tw-ring-offset-color: #fff;
+ --tw-ring-color: rgb(59 130 246 / 0.5);
+ --tw-ring-offset-shadow: 0 0 #0000;
+ --tw-ring-shadow: 0 0 #0000;
+ --tw-shadow: 0 0 #0000;
+ --tw-shadow-colored: 0 0 #0000;
+ --tw-blur: ;
+ --tw-brightness: ;
+ --tw-contrast: ;
+ --tw-grayscale: ;
+ --tw-hue-rotate: ;
+ --tw-invert: ;
+ --tw-saturate: ;
+ --tw-sepia: ;
+ --tw-drop-shadow: ;
+ --tw-backdrop-blur: ;
+ --tw-backdrop-brightness: ;
+ --tw-backdrop-contrast: ;
+ --tw-backdrop-grayscale: ;
+ --tw-backdrop-hue-rotate: ;
+ --tw-backdrop-invert: ;
+ --tw-backdrop-opacity: ;
+ --tw-backdrop-saturate: ;
+ --tw-backdrop-sepia: ;
+}
+
+.container {
+ width: 100%;
+}
+
+@media (min-width: 640px) {
+ .container {
+ max-width: 640px;
+ }
+}
+
+@media (min-width: 768px) {
+ .container {
+ max-width: 768px;
+ }
+}
+
+@media (min-width: 1024px) {
+ .container {
+ max-width: 1024px;
+ }
+}
+
+@media (min-width: 1280px) {
+ .container {
+ max-width: 1280px;
+ }
+}
+
+@media (min-width: 1536px) {
+ .container {
+ max-width: 1536px;
+ }
+}
+
+h1 {
+ margin-top: 1rem;
+ margin-bottom: 1rem;
+ font-size: 1.875rem;
+ line-height: 2.25rem;
+ font-weight: 800;
+}
+
+h2 {
+ margin-top: 1rem;
+ margin-bottom: 1rem;
+ font-size: 1.5rem;
+ line-height: 2rem;
+ font-weight: 700;
+}
+
+h3 {
+ margin-top: 1rem;
+ margin-bottom: 1rem;
+ font-size: 1.25rem;
+ line-height: 1.75rem;
+ font-weight: 700;
+}
+
+table {
+ margin-top: 1rem;
+ margin-bottom: 1rem;
+ width: 100%;
+ table-layout: auto;
+ border-collapse: collapse;
+}
+
+tr {
+ border-bottom-width: 1px;
+}
+
+td, th {
+ padding-left: 1.5rem;
+ padding-right: 1.5rem;
+ padding-top: 1rem;
+ padding-bottom: 1rem;
+ text-align: left;
+}
+
+.sr-only {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ white-space: nowrap;
+ border-width: 0;
+}
+
+.visible {
+ visibility: visible;
+}
+
+.static {
+ position: static;
+}
+
+.fixed {
+ position: fixed;
+}
+
+.absolute {
+ position: absolute;
+}
+
+.relative {
+ position: relative;
+}
+
+.sticky {
+ position: -webkit-sticky;
+ position: sticky;
+}
+
+.inset-x-0 {
+ left: 0px;
+ right: 0px;
+}
+
+.top-0 {
+ top: 0px;
+}
+
+.left-0 {
+ left: 0px;
+}
+
+.bottom-0 {
+ bottom: 0px;
+}
+
+.top-1\/2 {
+ top: 50%;
+}
+
+.-right-3 {
+ right: -0.75rem;
+}
+
+.-left-3 {
+ left: -0.75rem;
+}
+
+.z-30 {
+ z-index: 30;
+}
+
+.z-20 {
+ z-index: 20;
+}
+
+.z-\[60\] {
+ z-index: 60;
+}
+
+.m-8 {
+ margin: 2rem;
+}
+
+.mx-auto {
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.my-4 {
+ margin-top: 1rem;
+ margin-bottom: 1rem;
+}
+
+.my-6 {
+ margin-top: 1.5rem;
+ margin-bottom: 1.5rem;
+}
+
+.my-2 {
+ margin-top: 0.5rem;
+ margin-bottom: 0.5rem;
+}
+
+.my-0 {
+ margin-top: 0px;
+ margin-bottom: 0px;
+}
+
+.mb-6 {
+ margin-bottom: 1.5rem;
+}
+
+.mt-2 {
+ margin-top: 0.5rem;
+}
+
+.mr-2 {
+ margin-right: 0.5rem;
+}
+
+.mt-0 {
+ margin-top: 0px;
+}
+
+.mr-3 {
+ margin-right: 0.75rem;
+}
+
+.ml-auto {
+ margin-left: auto;
+}
+
+.mb-3 {
+ margin-bottom: 0.75rem;
+}
+
+.ml-0\.5 {
+ margin-left: 0.125rem;
+}
+
+.ml-0 {
+ margin-left: 0px;
+}
+
+.-ml-px {
+ margin-left: -1px;
+}
+
+.mt-4 {
+ margin-top: 1rem;
+}
+
+.mt-10 {
+ margin-top: 2.5rem;
+}
+
+.ml-2 {
+ margin-left: 0.5rem;
+}
+
+.mb-8 {
+ margin-bottom: 2rem;
+}
+
+.mb-4 {
+ margin-bottom: 1rem;
+}
+
+.mb-12 {
+ margin-bottom: 3rem;
+}
+
+.mb-2 {
+ margin-bottom: 0.5rem;
+}
+
+.block {
+ display: block;
+}
+
+.inline-block {
+ display: inline-block;
+}
+
+.inline {
+ display: inline;
+}
+
+.flex {
+ display: flex;
+}
+
+.table {
+ display: table;
+}
+
+.grid {
+ display: grid;
+}
+
+.hidden {
+ display: none;
+}
+
+.h-8 {
+ height: 2rem;
+}
+
+.h-6 {
+ height: 1.5rem;
+}
+
+.h-5 {
+ height: 1.25rem;
+}
+
+.h-4 {
+ height: 1rem;
+}
+
+.h-1 {
+ height: 0.25rem;
+}
+
+.h-full {
+ height: 100%;
+}
+
+.w-full {
+ width: 100%;
+}
+
+.w-6 {
+ width: 1.5rem;
+}
+
+.w-8 {
+ width: 2rem;
+}
+
+.w-5 {
+ width: 1.25rem;
+}
+
+.w-80 {
+ width: 20rem;
+}
+
+.w-4 {
+ width: 1rem;
+}
+
+.w-1\/6 {
+ width: 16.666667%;
+}
+
+.max-w-\[90rem\] {
+ max-width: 90rem;
+}
+
+.max-w-3xl {
+ max-width: 48rem;
+}
+
+.max-w-5xl {
+ max-width: 64rem;
+}
+
+.flex-1 {
+ flex: 1 1 0%;
+}
+
+.flex-grow {
+ flex-grow: 1;
+}
+
+.-translate-x-full {
+ --tw-translate-x: -100%;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.-translate-y-1\/2 {
+ --tw-translate-y: -50%;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.transform {
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.list-inside {
+ list-style-position: inside;
+}
+
+.list-disc {
+ list-style-type: disc;
+}
+
+.grid-cols-4 {
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+}
+
+.flex-row {
+ flex-direction: row;
+}
+
+.flex-col {
+ flex-direction: column;
+}
+
+.flex-wrap {
+ flex-wrap: wrap;
+}
+
+.items-start {
+ align-items: flex-start;
+}
+
+.items-center {
+ align-items: center;
+}
+
+.justify-end {
+ justify-content: flex-end;
+}
+
+.justify-center {
+ justify-content: center;
+}
+
+.justify-between {
+ justify-content: space-between;
+}
+
+.gap-4 {
+ gap: 1rem;
+}
+
+.gap-x-2 {
+ -moz-column-gap: 0.5rem;
+ column-gap: 0.5rem;
+}
+
+.space-y-8 > :not([hidden]) ~ :not([hidden]) {
+ --tw-space-y-reverse: 0;
+ margin-top: calc(2rem * calc(1 - var(--tw-space-y-reverse)));
+ margin-bottom: calc(2rem * var(--tw-space-y-reverse));
+}
+
+.space-y-1 > :not([hidden]) ~ :not([hidden]) {
+ --tw-space-y-reverse: 0;
+ margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse)));
+ margin-bottom: calc(0.25rem * var(--tw-space-y-reverse));
+}
+
+.space-y-2 > :not([hidden]) ~ :not([hidden]) {
+ --tw-space-y-reverse: 0;
+ margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse)));
+ margin-bottom: calc(0.5rem * var(--tw-space-y-reverse));
+}
+
+.overflow-y-auto {
+ overflow-y: auto;
+}
+
+.rounded-md {
+ border-radius: 0.375rem;
+}
+
+.rounded-full {
+ border-radius: 9999px;
+}
+
+.rounded {
+ border-radius: 0.25rem;
+}
+
+.rounded-l-md {
+ border-top-left-radius: 0.375rem;
+ border-bottom-left-radius: 0.375rem;
+}
+
+.rounded-r-md {
+ border-top-right-radius: 0.375rem;
+ border-bottom-right-radius: 0.375rem;
+}
+
+.rounded-t {
+ border-top-left-radius: 0.25rem;
+ border-top-right-radius: 0.25rem;
+}
+
+.border {
+ border-width: 1px;
+}
+
+.border-y {
+ border-top-width: 1px;
+ border-bottom-width: 1px;
+}
+
+.border-l-2 {
+ border-left-width: 2px;
+}
+
+.border-t {
+ border-top-width: 1px;
+}
+
+.border-r {
+ border-right-width: 1px;
+}
+
+.border-l {
+ border-left-width: 1px;
+}
+
+.border-b {
+ border-bottom-width: 1px;
+}
+
+.border-slate-100 {
+ --tw-border-opacity: 1;
+ border-color: rgb(241 245 249 / var(--tw-border-opacity));
+}
+
+.border-transparent {
+ border-color: transparent;
+}
+
+.border-slate-200 {
+ --tw-border-opacity: 1;
+ border-color: rgb(226 232 240 / var(--tw-border-opacity));
+}
+
+.border-palette-main {
+ --tw-border-opacity: 1;
+ border-color: rgb(64 186 189 / var(--tw-border-opacity));
+}
+
+.border-gray-100 {
+ --tw-border-opacity: 1;
+ border-color: rgb(243 244 246 / var(--tw-border-opacity));
+}
+
+.border-black {
+ --tw-border-opacity: 1;
+ border-color: rgb(0 0 0 / var(--tw-border-opacity));
+}
+
+.bg-white {
+ --tw-bg-opacity: 1;
+ background-color: rgb(255 255 255 / var(--tw-bg-opacity));
+}
+
+.bg-gray-100 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(243 244 246 / var(--tw-bg-opacity));
+}
+
+.bg-gray-700 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(55 65 81 / var(--tw-bg-opacity));
+}
+
+.fill-current {
+ fill: currentColor;
+}
+
+.p-1 {
+ padding: 0.25rem;
+}
+
+.p-4 {
+ padding: 1rem;
+}
+
+.p-2 {
+ padding: 0.5rem;
+}
+
+.p-6 {
+ padding: 1.5rem;
+}
+
+.px-8 {
+ padding-left: 2rem;
+ padding-right: 2rem;
+}
+
+.py-6 {
+ padding-top: 1.5rem;
+ padding-bottom: 1.5rem;
+}
+
+.py-2 {
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
+}
+
+.px-4 {
+ padding-left: 1rem;
+ padding-right: 1rem;
+}
+
+.py-10 {
+ padding-top: 2.5rem;
+ padding-bottom: 2.5rem;
+}
+
+.py-1 {
+ padding-top: 0.25rem;
+ padding-bottom: 0.25rem;
+}
+
+.py-4 {
+ padding-top: 1rem;
+ padding-bottom: 1rem;
+}
+
+.px-3 {
+ padding-left: 0.75rem;
+ padding-right: 0.75rem;
+}
+
+.py-8 {
+ padding-top: 2rem;
+ padding-bottom: 2rem;
+}
+
+.py-0 {
+ padding-top: 0px;
+ padding-bottom: 0px;
+}
+
+.pl-4 {
+ padding-left: 1rem;
+}
+
+.pr-4 {
+ padding-right: 1rem;
+}
+
+.pt-12 {
+ padding-top: 3rem;
+}
+
+.pt-24 {
+ padding-top: 6rem;
+}
+
+.pt-6 {
+ padding-top: 1.5rem;
+}
+
+.pb-10 {
+ padding-bottom: 2.5rem;
+}
+
+.pr-6 {
+ padding-right: 1.5rem;
+}
+
+.text-center {
+ text-align: center;
+}
+
+.align-middle {
+ vertical-align: middle;
+}
+
+.text-2xl {
+ font-size: 1.5rem;
+ line-height: 2rem;
+}
+
+.text-sm {
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+}
+
+.text-5xl {
+ font-size: 3rem;
+ line-height: 1;
+}
+
+.text-3xl {
+ font-size: 1.875rem;
+ line-height: 2.25rem;
+}
+
+.text-xs {
+ font-size: 0.75rem;
+ line-height: 1rem;
+}
+
+.font-bold {
+ font-weight: 700;
+}
+
+.font-semibold {
+ font-weight: 600;
+}
+
+.font-medium {
+ font-weight: 500;
+}
+
+.font-normal {
+ font-weight: 400;
+}
+
+.uppercase {
+ text-transform: uppercase;
+}
+
+.leading-normal {
+ line-height: 1.5;
+}
+
+.leading-tight {
+ line-height: 1.25;
+}
+
+.leading-none {
+ line-height: 1;
+}
+
+.tracking-normal {
+ letter-spacing: 0em;
+}
+
+.text-black {
+ --tw-text-opacity: 1;
+ color: rgb(0 0 0 / var(--tw-text-opacity));
+}
+
+.text-pink-600 {
+ --tw-text-opacity: 1;
+ color: rgb(219 39 119 / var(--tw-text-opacity));
+}
+
+.text-gray-500 {
+ --tw-text-opacity: 1;
+ color: rgb(107 114 128 / var(--tw-text-opacity));
+}
+
+.text-gray-800 {
+ --tw-text-opacity: 1;
+ color: rgb(31 41 55 / var(--tw-text-opacity));
+}
+
+.text-white {
+ --tw-text-opacity: 1;
+ color: rgb(255 255 255 / var(--tw-text-opacity));
+}
+
+.text-pink-800 {
+ --tw-text-opacity: 1;
+ color: rgb(157 23 77 / var(--tw-text-opacity));
+}
+
+.text-slate-700 {
+ --tw-text-opacity: 1;
+ color: rgb(51 65 85 / var(--tw-text-opacity));
+}
+
+.text-slate-900 {
+ --tw-text-opacity: 1;
+ color: rgb(15 23 42 / var(--tw-text-opacity));
+}
+
+.text-slate-500 {
+ --tw-text-opacity: 1;
+ color: rgb(100 116 139 / var(--tw-text-opacity));
+}
+
+.text-palette-main {
+ --tw-text-opacity: 1;
+ color: rgb(64 186 189 / var(--tw-text-opacity));
+}
+
+.text-gray-600 {
+ --tw-text-opacity: 1;
+ color: rgb(75 85 99 / var(--tw-text-opacity));
+}
+
+.underline {
+ -webkit-text-decoration-line: underline;
+ text-decoration-line: underline;
+}
+
+.no-underline {
+ -webkit-text-decoration-line: none;
+ text-decoration-line: none;
+}
+
+.opacity-75 {
+ opacity: 0.75;
+}
+
+.opacity-25 {
+ opacity: 0.25;
+}
+
+.shadow {
+ --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
+ --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
+}
+
+.shadow-lg {
+ --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
+ --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
+}
+
+.drop-shadow-lg {
+ --tw-drop-shadow: drop-shadow(0 10px 8px rgb(0 0 0 / 0.04)) drop-shadow(0 4px 3px rgb(0 0 0 / 0.1));
+ filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
+}
+
+.transition {
+ transition-property: color, background-color, border-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-text-decoration-color, -webkit-backdrop-filter;
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-text-decoration-color, -webkit-backdrop-filter;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-duration: 150ms;
+}
+
+.transition-all {
+ transition-property: all;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-duration: 150ms;
+}
+
+.duration-300 {
+ transition-duration: 300ms;
+}
+
+.ease-in-out {
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+#blazor-error-ui {
+ background: lightyellow;
+ bottom: 0;
+ box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
+ display: none;
+ left: 0;
+ padding: 0.6rem 1.25rem 0.7rem 1.25rem;
+ position: fixed;
+ width: 100%;
+ z-index: 1000;
+}
+
+#blazor-error-ui .dismiss {
+ cursor: pointer;
+ position: absolute;
+ right: 0.75rem;
+ top: 0.5rem;
+}
+
+#header {
+ box-shadow: 0 1px 2px 0 #eee;
+}
+
+#docs-sidebar {
+ box-shadow: 1px 0 2px 0 #eee;
+}
+
+#header img {
+ margin-bottom: 6px;
+}
+
+.bg-main {
+ background-color: #40BABD;
+}
+
+.text-main {
+ color: #40BABD;
+}
+
+.bg-color1 {
+ background-color: #A0B15B;
+}
+
+.bg-color2 {
+ background-color: #DC9A7A;
+}
+
+.bg-color3 {
+ background-color: #9EA5E3;
+}
+
+.outline-color1 {
+ box-shadow: 0px 0px 0px 2px #A0B15B inset;
+}
+
+.outline-color2 {
+ box-shadow: 0px 0px 0px 2px #DC9A7A inset;
+}
+
+.outline-color3 {
+ box-shadow: 0px 0px 0px 2px #9EA5E3 inset;
+}
+
+.outline-color1-darker {
+ box-shadow: 0px 0px 0px 2px #515a2b inset;
+}
+
+.outline-color2-darker {
+ box-shadow: 0px 0px 0px 2px #874423 inset;
+}
+
+.outline-color3-darker {
+ box-shadow: 0px 0px 0px 2px #2b3595 inset;
+}
+
+.diagram-link-label > div {
+ display: inline-block;
+ color: #000000;
+ font-size: 0.875rem;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+ transform: translate(-50%, -50%);
+ white-space: nowrap;
+ background-color: white;
+}
+
+.filename {
+ background: #f5f2f0;
+ margin-bottom: -0.5rem;
+ margin-top: 0.5rem;
+ padding: 0.5rem 0.75rem;
+ border-bottom: 1px solid #999;
+ font-size: 16px;
+}
+
+.hover\:scale-105:hover {
+ --tw-scale-x: 1.05;
+ --tw-scale-y: 1.05;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.hover\:border-slate-400:hover {
+ --tw-border-opacity: 1;
+ border-color: rgb(148 163 184 / var(--tw-border-opacity));
+}
+
+.hover\:bg-palette-main:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(64 186 189 / var(--tw-bg-opacity));
+}
+
+.hover\:text-pink-500:hover {
+ --tw-text-opacity: 1;
+ color: rgb(236 72 153 / var(--tw-text-opacity));
+}
+
+.hover\:text-gray-900:hover {
+ --tw-text-opacity: 1;
+ color: rgb(17 24 39 / var(--tw-text-opacity));
+}
+
+.hover\:text-gray-800:hover {
+ --tw-text-opacity: 1;
+ color: rgb(31 41 55 / var(--tw-text-opacity));
+}
+
+.hover\:text-palette-main:hover {
+ --tw-text-opacity: 1;
+ color: rgb(64 186 189 / var(--tw-text-opacity));
+}
+
+.hover\:text-gray-600:hover {
+ --tw-text-opacity: 1;
+ color: rgb(75 85 99 / var(--tw-text-opacity));
+}
+
+.hover\:text-slate-900:hover {
+ --tw-text-opacity: 1;
+ color: rgb(15 23 42 / var(--tw-text-opacity));
+}
+
+.hover\:text-white:hover {
+ --tw-text-opacity: 1;
+ color: rgb(255 255 255 / var(--tw-text-opacity));
+}
+
+.hover\:underline:hover {
+ -webkit-text-decoration-line: underline;
+ text-decoration-line: underline;
+}
+
+.hover\:no-underline:hover {
+ -webkit-text-decoration-line: none;
+ text-decoration-line: none;
+}
+
+.focus\:outline-none:focus {
+ outline: 2px solid transparent;
+ outline-offset: 2px;
+}
+
+.dark .dark\:border-gray-700 {
+ --tw-border-opacity: 1;
+ border-color: rgb(55 65 81 / var(--tw-border-opacity));
+}
+
+.dark .dark\:border-slate-800 {
+ --tw-border-opacity: 1;
+ border-color: rgb(30 41 59 / var(--tw-border-opacity));
+}
+
+.dark .dark\:border-slate-200\/5 {
+ border-color: rgb(226 232 240 / 0.05);
+}
+
+.dark .dark\:bg-slate-900 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(15 23 42 / var(--tw-bg-opacity));
+}
+
+.dark .dark\:bg-gray-800 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(31 41 55 / var(--tw-bg-opacity));
+}
+
+.dark .dark\:text-slate-200 {
+ --tw-text-opacity: 1;
+ color: rgb(226 232 240 / var(--tw-text-opacity));
+}
+
+@media (min-width: 640px) {
+ .sm\:mb-0 {
+ margin-bottom: 0px;
+ }
+
+ .sm\:flex {
+ display: flex;
+ }
+
+ .sm\:px-6 {
+ padding-left: 1.5rem;
+ padding-right: 1.5rem;
+ }
+}
+
+@media (min-width: 768px) {
+ .md\:col-span-2 {
+ grid-column: span 2 / span 2;
+ }
+
+ .md\:col-span-3 {
+ grid-column: span 3 / span 3;
+ }
+
+ .md\:mb-6 {
+ margin-bottom: 1.5rem;
+ }
+
+ .md\:mr-0 {
+ margin-right: 0px;
+ }
+
+ .md\:block {
+ display: block;
+ }
+
+ .md\:grid {
+ display: grid;
+ }
+
+ .md\:w-2\/5 {
+ width: 40%;
+ }
+
+ .md\:grid-cols-5 {
+ grid-template-columns: repeat(5, minmax(0, 1fr));
+ }
+
+ .md\:flex-row {
+ flex-direction: row;
+ }
+
+ .md\:gap-4 {
+ gap: 1rem;
+ }
+
+ .md\:px-8 {
+ padding-left: 2rem;
+ padding-right: 2rem;
+ }
+
+ .md\:text-left {
+ text-align: left;
+ }
+}
+
+@media (min-width: 1024px) {
+ .lg\:top-0 {
+ top: 0px;
+ }
+
+ .lg\:right-auto {
+ right: auto;
+ }
+
+ .lg\:bottom-0 {
+ bottom: 0px;
+ }
+
+ .lg\:left-\[max\(0px\2c calc\(50\%-45rem\)\)\] {
+ left: max(0px,calc(50% - 45rem));
+ }
+
+ .lg\:z-10 {
+ z-index: 10;
+ }
+
+ .lg\:mx-0 {
+ margin-left: 0px;
+ margin-right: 0px;
+ }
+
+ .lg\:mt-0 {
+ margin-top: 0px;
+ }
+
+ .lg\:block {
+ display: block;
+ }
+
+ .lg\:flex {
+ display: flex;
+ }
+
+ .lg\:hidden {
+ display: none;
+ }
+
+ .lg\:w-auto {
+ width: auto;
+ }
+
+ .lg\:translate-x-0 {
+ --tw-translate-x: 0px;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ }
+
+ .lg\:items-center {
+ align-items: center;
+ }
+
+ .lg\:bg-transparent {
+ background-color: transparent;
+ }
+
+ .lg\:p-0 {
+ padding: 0px;
+ }
+
+ .lg\:pl-\[22rem\] {
+ padding-left: 22rem;
+ }
+
+ .lg\:text-4xl {
+ font-size: 2.25rem;
+ line-height: 2.5rem;
+ }
+
+ .lg\:text-3xl {
+ font-size: 1.875rem;
+ line-height: 2.25rem;
+ }
+
+ .lg\:leading-6 {
+ line-height: 1.5rem;
+ }
+}
+
+@media (min-width: 1280px) {
+ .xl\:ml-0 {
+ margin-left: 0px;
+ }
+
+ .xl\:mr-64 {
+ margin-right: 16rem;
+ }
+
+ .xl\:max-w-none {
+ max-width: none;
+ }
+
+ .xl\:pr-16 {
+ padding-right: 4rem;
+ }
+}
diff --git a/site/Site/wwwroot/css/input.css b/site/Site/wwwroot/css/input.css
new file mode 100644
index 000000000..a670b948a
--- /dev/null
+++ b/site/Site/wwwroot/css/input.css
@@ -0,0 +1,128 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+@layer components {
+ h1 {
+ @apply text-3xl font-extrabold my-4;
+ }
+
+ h2 {
+ @apply text-2xl font-bold my-4;
+ }
+
+ h3 {
+ @apply text-xl font-bold my-4;
+ }
+
+ table {
+ @apply border-collapse table-auto w-full my-4;
+ }
+
+ tr {
+ @apply border-b;
+ }
+
+ td, th {
+ @apply px-6 py-4 text-left;
+ }
+}
+
+#blazor-error-ui {
+ background: lightyellow;
+ bottom: 0;
+ box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
+ display: none;
+ left: 0;
+ padding: 0.6rem 1.25rem 0.7rem 1.25rem;
+ position: fixed;
+ width: 100%;
+ z-index: 1000;
+}
+
+ #blazor-error-ui .dismiss {
+ cursor: pointer;
+ position: absolute;
+ right: 0.75rem;
+ top: 0.5rem;
+ }
+
+#header {
+ box-shadow: 0 1px 2px 0 #eee;
+}
+
+#docs-sidebar {
+ box-shadow: 1px 0 2px 0 #eee;
+}
+
+#header img {
+ margin-bottom: 6px;
+}
+
+.bg-main {
+ background-color: #40BABD;
+}
+
+.text-main {
+ color: #40BABD;
+}
+
+.bg-color1 {
+ background-color: #A0B15B;
+}
+
+.bg-color2 {
+ background-color: #DC9A7A;
+}
+
+.bg-color3 {
+ background-color: #9EA5E3;
+}
+
+.outline-color1 {
+ box-shadow: 0px 0px 0px 2px #A0B15B inset;
+}
+
+.outline-color2 {
+ box-shadow: 0px 0px 0px 2px #DC9A7A inset;
+}
+
+.outline-color3 {
+ box-shadow: 0px 0px 0px 2px #9EA5E3 inset;
+}
+
+.outline-color1-darker {
+ box-shadow: 0px 0px 0px 2px #515a2b inset;
+}
+
+.outline-color2-darker {
+ box-shadow: 0px 0px 0px 2px #874423 inset;
+}
+
+.outline-color3-darker {
+ box-shadow: 0px 0px 0px 2px #2b3595 inset;
+}
+
+.diagram-link-label > div {
+ display: inline-block;
+ color: #000000;
+ font-size: 0.875rem;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ -webkit-transform: translate(-50%, -50%);
+ -ms-transform: translate(-50%, -50%);
+ transform: translate(-50%, -50%);
+ white-space: nowrap;
+ background-color: white;
+}
+
+.filename {
+ background: #f5f2f0;
+ margin-bottom: -0.5rem;
+ margin-top: 0.5rem;
+ padding: 0.5rem 0.75rem;
+ border-bottom: 1px solid #999;
+ font-size: 16px;
+}
diff --git a/site/Site/wwwroot/css/prism.css b/site/Site/wwwroot/css/prism.css
new file mode 100644
index 000000000..c5414b0b7
--- /dev/null
+++ b/site/Site/wwwroot/css/prism.css
@@ -0,0 +1,6 @@
+/* PrismJS 1.29.0
+https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+csharp+cshtml&plugins=line-highlight+line-numbers+normalize-whitespace+toolbar+copy-to-clipboard */
+code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shadow:0 1px #fff;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}
+pre[data-line]{position:relative;padding:1em 0 1em 3em}.line-highlight{position:absolute;left:0;right:0;padding:inherit 0;margin-top:1em;background:hsla(24,20%,50%,.08);background:linear-gradient(to right,hsla(24,20%,50%,.1) 70%,hsla(24,20%,50%,0));pointer-events:none;line-height:inherit;white-space:pre}@media print{.line-highlight{-webkit-print-color-adjust:exact;color-adjust:exact}}.line-highlight:before,.line-highlight[data-end]:after{content:attr(data-start);position:absolute;top:.4em;left:.6em;min-width:1em;padding:0 .5em;background-color:hsla(24,20%,50%,.4);color:#f4f1ef;font:bold 65%/1.5 sans-serif;text-align:center;vertical-align:.3em;border-radius:999px;text-shadow:none;box-shadow:0 1px #fff}.line-highlight[data-end]:after{content:attr(data-end);top:auto;bottom:.4em}.line-numbers .line-highlight:after,.line-numbers .line-highlight:before{content:none}pre[id].linkable-line-numbers span.line-numbers-rows{pointer-events:all}pre[id].linkable-line-numbers span.line-numbers-rows>span:before{cursor:pointer}pre[id].linkable-line-numbers span.line-numbers-rows>span:hover:before{background-color:rgba(128,128,128,.2)}
+pre[class*=language-].line-numbers{position:relative;padding-left:3.8em;counter-reset:linenumber}pre[class*=language-].line-numbers>code{position:relative;white-space:inherit}.line-numbers .line-numbers-rows{position:absolute;pointer-events:none;top:0;font-size:100%;left:-3.8em;width:3em;letter-spacing:-1px;border-right:1px solid #999;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.line-numbers-rows>span{display:block;counter-increment:linenumber}.line-numbers-rows>span:before{content:counter(linenumber);color:#999;display:block;padding-right:.8em;text-align:right}
+div.code-toolbar{position:relative}div.code-toolbar>.toolbar{position:absolute;z-index:10;top:.3em;right:.2em;transition:opacity .3s ease-in-out;opacity:0}div.code-toolbar:hover>.toolbar{opacity:1}div.code-toolbar:focus-within>.toolbar{opacity:1}div.code-toolbar>.toolbar>.toolbar-item{display:inline-block}div.code-toolbar>.toolbar>.toolbar-item>a{cursor:pointer}div.code-toolbar>.toolbar>.toolbar-item>button{background:0 0;border:0;color:inherit;font:inherit;line-height:normal;overflow:visible;padding:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}div.code-toolbar>.toolbar>.toolbar-item>a,div.code-toolbar>.toolbar>.toolbar-item>button,div.code-toolbar>.toolbar>.toolbar-item>span{color:#bbb;font-size:.8em;padding:0 .5em;background:#f5f2f0;background:rgba(224,224,224,.2);box-shadow:0 2px 0 0 rgba(0,0,0,.2);border-radius:.5em}div.code-toolbar>.toolbar>.toolbar-item>a:focus,div.code-toolbar>.toolbar>.toolbar-item>a:hover,div.code-toolbar>.toolbar>.toolbar-item>button:focus,div.code-toolbar>.toolbar>.toolbar-item>button:hover,div.code-toolbar>.toolbar>.toolbar-item>span:focus,div.code-toolbar>.toolbar>.toolbar-item>span:hover{color:inherit;text-decoration:none}
diff --git a/site/Site/wwwroot/favicon.ico b/site/Site/wwwroot/favicon.ico
new file mode 100644
index 000000000..475ad21e2
Binary files /dev/null and b/site/Site/wwwroot/favicon.ico differ
diff --git a/site/Site/wwwroot/icon-192.png b/site/Site/wwwroot/icon-192.png
new file mode 100644
index 000000000..166f56da7
Binary files /dev/null and b/site/Site/wwwroot/icon-192.png differ
diff --git a/site/Site/wwwroot/img/ZBD.png b/site/Site/wwwroot/img/ZBD.png
new file mode 100644
index 000000000..e641498c8
Binary files /dev/null and b/site/Site/wwwroot/img/ZBD.png differ
diff --git a/site/Site/wwwroot/index.html b/site/Site/wwwroot/index.html
new file mode 100644
index 000000000..d77c6dbcf
--- /dev/null
+++ b/site/Site/wwwroot/index.html
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+ Site
+
+
+
+
+
+
+
+
+
+
+ Loading...
+
+
+ An unhandled error has occurred.
+
Reload
+
🗙
+
+
+
+
+
+
+
+
diff --git a/site/Site/wwwroot/js/prism.js b/site/Site/wwwroot/js/prism.js
new file mode 100644
index 000000000..2091ce848
--- /dev/null
+++ b/site/Site/wwwroot/js/prism.js
@@ -0,0 +1,14 @@
+/* PrismJS 1.29.0
+https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+csharp+cshtml&plugins=line-highlight+line-numbers+normalize-whitespace+toolbar+copy-to-clipboard */
+var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(e){var n=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,t=0,r={},a={manual:e.Prism&&e.Prism.manual,disableWorkerMessageHandler:e.Prism&&e.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof i?new i(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/&/g,"&").replace(/=g.reach);A+=w.value.length,w=w.next){var E=w.value;if(n.length>e.length)return;if(!(E instanceof i)){var P,L=1;if(y){if(!(P=l(b,A,e,m))||P.index>=e.length)break;var S=P.index,O=P.index+P[0].length,j=A;for(j+=w.value.length;S>=j;)j+=(w=w.next).value.length;if(A=j-=w.value.length,w.value instanceof i)continue;for(var C=w;C!==n.tail&&(jg.reach&&(g.reach=W);var z=w.prev;if(_&&(z=u(n,z,_),A+=_.length),c(n,z,L),w=u(n,z,new i(f,p?a.tokenize(N,p):N,k,N)),M&&u(n,w,M),L>1){var I={cause:f+","+d,reach:W};o(e,n,t,w.prev,A,I),g&&I.reach>g.reach&&(g.reach=I.reach)}}}}}}function s(){var e={value:null,prev:null,next:null},n={value:null,prev:e,next:null};e.next=n,this.head=e,this.tail=n,this.length=0}function u(e,n,t){var r=n.next,a={value:t,prev:n,next:r};return n.next=a,r.prev=a,e.length++,a}function c(e,n,t){for(var r=n.next,a=0;a"+i.content+""+i.tag+">"},!e.document)return e.addEventListener?(a.disableWorkerMessageHandler||e.addEventListener("message",(function(n){var t=JSON.parse(n.data),r=t.language,i=t.code,l=t.immediateClose;e.postMessage(a.highlight(i,a.languages[r],r)),l&&e.close()}),!1),a):a;var g=a.util.currentScript();function f(){a.manual||a.highlightAll()}if(g&&(a.filename=g.src,g.hasAttribute("data-manual")&&(a.manual=!0)),!a.manual){var h=document.readyState;"loading"===h||"interactive"===h&&g&&g.defer?document.addEventListener("DOMContentLoaded",f):window.requestAnimationFrame?window.requestAnimationFrame(f):window.setTimeout(f,16)}return a}(_self);"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism);
+Prism.languages.markup={comment:{pattern://,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^$|[[\]]/,"doctype-tag":/^DOCTYPE/i,name:/[^\s<>'"]+/}},cdata:{pattern://i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/?[\da-f]{1,8};/i]},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.languages.markup.doctype.inside["internal-subset"].inside=Prism.languages.markup,Prism.hooks.add("wrap",(function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&/,"&"))})),Object.defineProperty(Prism.languages.markup.tag,"addInlined",{value:function(a,e){var s={};s["language-"+e]={pattern:/(^$)/i,lookbehind:!0,inside:Prism.languages[e]},s.cdata=/^$/i;var t={"included-cdata":{pattern://i,inside:s}};t["language-"+e]={pattern:/[\s\S]+/,inside:Prism.languages[e]};var n={};n[a]={pattern:RegExp("(<__[^>]*>)(?:))*\\]\\]>|(?!)".replace(/__/g,(function(){return a})),"i"),lookbehind:!0,greedy:!0,inside:t},Prism.languages.insertBefore("markup","cdata",n)}}),Object.defineProperty(Prism.languages.markup.tag,"addAttribute",{value:function(a,e){Prism.languages.markup.tag.inside["special-attr"].push({pattern:RegExp("(^|[\"'\\s])(?:"+a+")\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s'\">=]+(?=[\\s>]))","i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[e,"language-"+e],inside:Prism.languages[e]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup,Prism.languages.xml=Prism.languages.extend("markup",{}),Prism.languages.ssml=Prism.languages.xml,Prism.languages.atom=Prism.languages.xml,Prism.languages.rss=Prism.languages.xml;
+!function(s){var e=/(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/;s.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:RegExp("@[\\w-](?:[^;{\\s\"']|\\s+(?!\\s)|"+e.source+")*?(?:;|(?=\\s*\\{))"),inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+e.source+"|(?:[^\\\\\r\n()\"']|\\\\[^])*)\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+e.source+"$"),alias:"url"}}},selector:{pattern:RegExp("(^|[{}\\s])[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|"+e.source+")*(?=\\s*\\{)"),lookbehind:!0},string:{pattern:e,greedy:!0},property:{pattern:/(^|[^-\w\xA0-\uFFFF])(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,lookbehind:!0},important:/!important\b/i,function:{pattern:/(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i,lookbehind:!0},punctuation:/[(){};:,]/},s.languages.css.atrule.inside.rest=s.languages.css;var t=s.languages.markup;t&&(t.tag.addInlined("style","css"),t.tag.addAttribute("style","css"))}(Prism);
+Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/,boolean:/\b(?:false|true)\b/,function:/\b\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/};
+Prism.languages.javascript=Prism.languages.extend("clike",{"class-name":[Prism.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:constructor|prototype))/,lookbehind:!0}],keyword:[{pattern:/((?:^|\})\s*)catch\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],function:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,number:{pattern:RegExp("(^|[^\\w$])(?:NaN|Infinity|0[bB][01]+(?:_[01]+)*n?|0[oO][0-7]+(?:_[0-7]+)*n?|0[xX][\\dA-Fa-f]+(?:_[\\dA-Fa-f]+)*n?|\\d+(?:_\\d+)*n|(?:\\d+(?:_\\d+)*(?:\\.(?:\\d+(?:_\\d+)*)?)?|\\.\\d+(?:_\\d+)*)(?:[Ee][+-]?\\d+(?:_\\d+)*)?)(?![\\w$])"),lookbehind:!0},operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),Prism.languages.javascript["class-name"][0].pattern=/(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/,Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:RegExp("((?:^|[^$\\w\\xA0-\\uFFFF.\"'\\])\\s]|\\b(?:return|yield))\\s*)/(?:(?:\\[(?:[^\\]\\\\\r\n]|\\\\.)*\\]|\\\\.|[^/\\\\\\[\r\n])+/[dgimyus]{0,7}|(?:\\[(?:[^[\\]\\\\\r\n]|\\\\.|\\[(?:[^[\\]\\\\\r\n]|\\\\.|\\[(?:[^[\\]\\\\\r\n]|\\\\.)*\\])*\\])*\\]|\\\\.|[^/\\\\\\[\r\n])+/[dgimyus]{0,7}v[dgimyus]{0,7})(?=(?:\\s|/\\*(?:[^*]|\\*(?!/))*\\*/)*(?:$|[\r\n,.;:})\\]]|//))"),lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:Prism.languages.regex},"regex-delimiter":/^\/|\/$/,"regex-flags":/^[a-z]+$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:Prism.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),Prism.languages.insertBefore("javascript","string",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"template-string":{pattern:/`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}},"string-property":{pattern:/((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,lookbehind:!0,greedy:!0,alias:"property"}}),Prism.languages.insertBefore("javascript","operator",{"literal-property":{pattern:/((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,lookbehind:!0,alias:"property"}}),Prism.languages.markup&&(Prism.languages.markup.tag.addInlined("script","javascript"),Prism.languages.markup.tag.addAttribute("on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)","javascript")),Prism.languages.js=Prism.languages.javascript;
+!function(e){function n(e,n){return e.replace(/<<(\d+)>>/g,(function(e,s){return"(?:"+n[+s]+")"}))}function s(e,s,a){return RegExp(n(e,s),a||"")}function a(e,n){for(var s=0;s>/g,(function(){return"(?:"+e+")"}));return e.replace(/<>/g,"[^\\s\\S]")}var t="bool byte char decimal double dynamic float int long object sbyte short string uint ulong ushort var void",r="class enum interface record struct",i="add alias and ascending async await by descending from(?=\\s*(?:\\w|$)) get global group into init(?=\\s*;) join let nameof not notnull on or orderby partial remove select set unmanaged value when where with(?=\\s*{)",o="abstract as base break case catch checked const continue default delegate do else event explicit extern finally fixed for foreach goto if implicit in internal is lock namespace new null operator out override params private protected public readonly ref return sealed sizeof stackalloc static switch this throw try typeof unchecked unsafe using virtual volatile while yield";function l(e){return"\\b(?:"+e.trim().replace(/ /g,"|")+")\\b"}var d=l(r),p=RegExp(l(t+" "+r+" "+i+" "+o)),c=l(r+" "+i+" "+o),u=l(t+" "+r+" "+o),g=a("<(?:[^<>;=+\\-*/%&|^]|<>)*>",2),b=a("\\((?:[^()]|<>)*\\)",2),h="@?\\b[A-Za-z_]\\w*\\b",f=n("<<0>>(?:\\s*<<1>>)?",[h,g]),m=n("(?!<<0>>)<<1>>(?:\\s*\\.\\s*<<1>>)*",[c,f]),k="\\[\\s*(?:,\\s*)*\\]",y=n("<<0>>(?:\\s*(?:\\?\\s*)?<<1>>)*(?:\\s*\\?)?",[m,k]),w=n("[^,()<>[\\];=+\\-*/%&|^]|<<0>>|<<1>>|<<2>>",[g,b,k]),v=n("\\(<<0>>+(?:,<<0>>+)+\\)",[w]),x=n("(?:<<0>>|<<1>>)(?:\\s*(?:\\?\\s*)?<<2>>)*(?:\\s*\\?)?",[v,m,k]),$={keyword:p,punctuation:/[<>()?,.:[\]]/},_="'(?:[^\r\n'\\\\]|\\\\.|\\\\[Uux][\\da-fA-F]{1,8})'",B='"(?:\\\\.|[^\\\\"\r\n])*"';e.languages.csharp=e.languages.extend("clike",{string:[{pattern:s("(^|[^$\\\\])<<0>>",['@"(?:""|\\\\[^]|[^\\\\"])*"(?!")']),lookbehind:!0,greedy:!0},{pattern:s("(^|[^@$\\\\])<<0>>",[B]),lookbehind:!0,greedy:!0}],"class-name":[{pattern:s("(\\busing\\s+static\\s+)<<0>>(?=\\s*;)",[m]),lookbehind:!0,inside:$},{pattern:s("(\\busing\\s+<<0>>\\s*=\\s*)<<1>>(?=\\s*;)",[h,x]),lookbehind:!0,inside:$},{pattern:s("(\\busing\\s+)<<0>>(?=\\s*=)",[h]),lookbehind:!0},{pattern:s("(\\b<<0>>\\s+)<<1>>",[d,f]),lookbehind:!0,inside:$},{pattern:s("(\\bcatch\\s*\\(\\s*)<<0>>",[m]),lookbehind:!0,inside:$},{pattern:s("(\\bwhere\\s+)<<0>>",[h]),lookbehind:!0},{pattern:s("(\\b(?:is(?:\\s+not)?|as)\\s+)<<0>>",[y]),lookbehind:!0,inside:$},{pattern:s("\\b<<0>>(?=\\s+(?!<<1>>|with\\s*\\{)<<2>>(?:\\s*[=,;:{)\\]]|\\s+(?:in|when)\\b))",[x,u,h]),inside:$}],keyword:p,number:/(?:\b0(?:x[\da-f_]*[\da-f]|b[01_]*[01])|(?:\B\.\d+(?:_+\d+)*|\b\d+(?:_+\d+)*(?:\.\d+(?:_+\d+)*)?)(?:e[-+]?\d+(?:_+\d+)*)?)(?:[dflmu]|lu|ul)?\b/i,operator:/>>=?|<<=?|[-=]>|([-+&|])\1|~|\?\?=?|[-+*/%&|^!=<>]=?/,punctuation:/\?\.?|::|[{}[\];(),.:]/}),e.languages.insertBefore("csharp","number",{range:{pattern:/\.\./,alias:"operator"}}),e.languages.insertBefore("csharp","punctuation",{"named-parameter":{pattern:s("([(,]\\s*)<<0>>(?=\\s*:)",[h]),lookbehind:!0,alias:"punctuation"}}),e.languages.insertBefore("csharp","class-name",{namespace:{pattern:s("(\\b(?:namespace|using)\\s+)<<0>>(?:\\s*\\.\\s*<<0>>)*(?=\\s*[;{])",[h]),lookbehind:!0,inside:{punctuation:/\./}},"type-expression":{pattern:s("(\\b(?:default|sizeof|typeof)\\s*\\(\\s*(?!\\s))(?:[^()\\s]|\\s(?!\\s)|<<0>>)*(?=\\s*\\))",[b]),lookbehind:!0,alias:"class-name",inside:$},"return-type":{pattern:s("<<0>>(?=\\s+(?:<<1>>\\s*(?:=>|[({]|\\.\\s*this\\s*\\[)|this\\s*\\[))",[x,m]),inside:$,alias:"class-name"},"constructor-invocation":{pattern:s("(\\bnew\\s+)<<0>>(?=\\s*[[({])",[x]),lookbehind:!0,inside:$,alias:"class-name"},"generic-method":{pattern:s("<<0>>\\s*<<1>>(?=\\s*\\()",[h,g]),inside:{function:s("^<<0>>",[h]),generic:{pattern:RegExp(g),alias:"class-name",inside:$}}},"type-list":{pattern:s("\\b((?:<<0>>\\s+<<1>>|record\\s+<<1>>\\s*<<5>>|where\\s+<<2>>)\\s*:\\s*)(?:<<3>>|<<4>>|<<1>>\\s*<<5>>|<<6>>)(?:\\s*,\\s*(?:<<3>>|<<4>>|<<6>>))*(?=\\s*(?:where|[{;]|=>|$))",[d,f,h,x,p.source,b,"\\bnew\\s*\\(\\s*\\)"]),lookbehind:!0,inside:{"record-arguments":{pattern:s("(^(?!new\\s*\\()<<0>>\\s*)<<1>>",[f,b]),lookbehind:!0,greedy:!0,inside:e.languages.csharp},keyword:p,"class-name":{pattern:RegExp(x),greedy:!0,inside:$},punctuation:/[,()]/}},preprocessor:{pattern:/(^[\t ]*)#.*/m,lookbehind:!0,alias:"property",inside:{directive:{pattern:/(#)\b(?:define|elif|else|endif|endregion|error|if|line|nullable|pragma|region|undef|warning)\b/,lookbehind:!0,alias:"keyword"}}}});var E=B+"|"+_,R=n("/(?![*/])|//[^\r\n]*[\r\n]|/\\*(?:[^*]|\\*(?!/))*\\*/|<<0>>",[E]),z=a(n("[^\"'/()]|<<0>>|\\(<>*\\)",[R]),2),S="\\b(?:assembly|event|field|method|module|param|property|return|type)\\b",j=n("<<0>>(?:\\s*\\(<<1>>*\\))?",[m,z]);e.languages.insertBefore("csharp","class-name",{attribute:{pattern:s("((?:^|[^\\s\\w>)?])\\s*\\[\\s*)(?:<<0>>\\s*:\\s*)?<<1>>(?:\\s*,\\s*<<1>>)*(?=\\s*\\])",[S,j]),lookbehind:!0,greedy:!0,inside:{target:{pattern:s("^<<0>>(?=\\s*:)",[S]),alias:"keyword"},"attribute-arguments":{pattern:s("\\(<<0>>*\\)",[z]),inside:e.languages.csharp},"class-name":{pattern:RegExp(m),inside:{punctuation:/\./}},punctuation:/[:,]/}}});var A=":[^}\r\n]+",F=a(n("[^\"'/()]|<<0>>|\\(<>*\\)",[R]),2),P=n("\\{(?!\\{)(?:(?![}:])<<0>>)*<<1>>?\\}",[F,A]),U=a(n("[^\"'/()]|/(?!\\*)|/\\*(?:[^*]|\\*(?!/))*\\*/|<<0>>|\\(<>*\\)",[E]),2),Z=n("\\{(?!\\{)(?:(?![}:])<<0>>)*<<1>>?\\}",[U,A]);function q(n,a){return{interpolation:{pattern:s("((?:^|[^{])(?:\\{\\{)*)<<0>>",[n]),lookbehind:!0,inside:{"format-string":{pattern:s("(^\\{(?:(?![}:])<<0>>)*)<<1>>(?=\\}$)",[a,A]),lookbehind:!0,inside:{punctuation:/^:/}},punctuation:/^\{|\}$/,expression:{pattern:/[\s\S]+/,alias:"language-csharp",inside:e.languages.csharp}}},string:/[\s\S]+/}}e.languages.insertBefore("csharp","string",{"interpolation-string":[{pattern:s('(^|[^\\\\])(?:\\$@|@\\$)"(?:""|\\\\[^]|\\{\\{|<<0>>|[^\\\\{"])*"',[P]),lookbehind:!0,greedy:!0,inside:q(P,F)},{pattern:s('(^|[^@\\\\])\\$"(?:\\\\.|\\{\\{|<<0>>|[^\\\\"{])*"',[Z]),lookbehind:!0,greedy:!0,inside:q(Z,U)}],char:{pattern:RegExp(_),greedy:!0}}),e.languages.dotnet=e.languages.cs=e.languages.csharp}(Prism);
+!function(e){function s(e,s){for(var a=0;a/g,(function(){return"(?:"+e+")"}));return e.replace(//g,"[^\\s\\S]").replace(//g,'(?:@(?!")|"(?:[^\r\n\\\\"]|\\\\.)*"|@"(?:[^\\\\"]|""|\\\\[^])*"(?!")|\'(?:(?:[^\r\n\'\\\\]|\\\\.|\\\\[Uux][\\da-fA-F]{1,8})\'|(?=[^\\\\](?!\'))))').replace(//g,"(?:/(?![/*])|//.*[\r\n]|/\\*[^*]*(?:\\*(?!/)[^*]*)*\\*/)")}var a=s("\\((?:[^()'\"@/]|||)*\\)",2),t=s("\\[(?:[^\\[\\]'\"@/]|||)*\\]",1),r=s("\\{(?:[^{}'\"@/]|||)*\\}",2),n="@(?:await\\b\\s*)?(?:(?!await\\b)\\w+\\b|"+a+")(?:[?!]?\\.\\w+\\b|(?:"+s("<(?:[^<>'\"@/]||)*>",1)+")?"+a+"|"+t+")*(?![?!\\.(\\[]|<(?!/))",l="(?:\"[^\"@]*\"|'[^'@]*'|[^\\s'\"@>=]+(?=[\\s>])|[\"'][^\"'@]*(?:(?:@(?![\\w()])|"+n+")[^\"'@]*)+[\"'])",i="(?:\\s(?:\\s*[^\\s>/=]+(?:\\s*=\\s*|(?=[\\s/>])))+)?".replace(//,l),g="(?!\\d)[^\\s>/=$<%]+"+i+"\\s*/?>",o="\\B@?(?:<([a-zA-Z][\\w:]*)"+i+"\\s*>(?:[^<]|?(?!\\1\\b)"+g+"|"+s("<\\1"+i+"\\s*>(?:[^<]|?(?!\\1\\b)"+g+"|)*\\1\\s*>",2)+")*\\1\\s*>|<"+g+")";e.languages.cshtml=e.languages.extend("markup",{});var c={pattern:/\S[\s\S]*/,alias:"language-csharp",inside:e.languages.insertBefore("csharp","string",{html:{pattern:RegExp(o),greedy:!0,inside:e.languages.cshtml}},{csharp:e.languages.extend("csharp",{})})},p={pattern:RegExp("(^|[^@])"+n),lookbehind:!0,greedy:!0,alias:"variable",inside:{keyword:/^@/,csharp:c}};e.languages.cshtml.tag.pattern=RegExp("?"+g),e.languages.cshtml.tag.inside["attr-value"].pattern=RegExp("=\\s*"+l),e.languages.insertBefore("inside","punctuation",{value:p},e.languages.cshtml.tag.inside["attr-value"]),e.languages.insertBefore("cshtml","prolog",{"razor-comment":{pattern:/@\*[\s\S]*?\*@/,greedy:!0,alias:"comment"},block:{pattern:RegExp("(^|[^@])@(?:"+[r,"(?:code|functions)\\s*"+r,"(?:for|foreach|lock|switch|using|while)\\s*"+a+"\\s*"+r,"do\\s*"+r+"\\s*while\\s*"+a+"(?:\\s*;)?","try\\s*"+r+"\\s*catch\\s*"+a+"\\s*"+r+"\\s*finally\\s*"+r,"if\\s*"+a+"\\s*"+r+"(?:\\s*else(?:\\s+if\\s*"+a+")?\\s*"+r+")*","helper\\s+\\w+\\s*"+a+"\\s*"+r].join("|")+")"),lookbehind:!0,greedy:!0,inside:{keyword:/^@\w*/,csharp:c}},directive:{pattern:/^([ \t]*)@(?:addTagHelper|attribute|implements|inherits|inject|layout|model|namespace|page|preservewhitespace|removeTagHelper|section|tagHelperPrefix|using)(?=\s).*/m,lookbehind:!0,greedy:!0,inside:{keyword:/^@\w+/,csharp:c}},value:p,"delegate-operator":{pattern:/(^|[^@])@(?=<)/,lookbehind:!0,alias:"operator"}}),e.languages.razor=e.languages.cshtml}(Prism);
+!function(){if("undefined"!=typeof Prism&&"undefined"!=typeof document&&document.querySelector){var e,t="line-numbers",i="linkable-line-numbers",n=/\n(?!$)/g,r=!0;Prism.plugins.lineHighlight={highlightLines:function(o,u,c){var h=(u="string"==typeof u?u:o.getAttribute("data-line")||"").replace(/\s+/g,"").split(",").filter(Boolean),d=+o.getAttribute("data-line-offset")||0,f=(function(){if(void 0===e){var t=document.createElement("div");t.style.fontSize="13px",t.style.lineHeight="1.5",t.style.padding="0",t.style.border="0",t.innerHTML=" ",document.body.appendChild(t),e=38===t.offsetHeight,document.body.removeChild(t)}return e}()?parseInt:parseFloat)(getComputedStyle(o).lineHeight),p=Prism.util.isActive(o,t),g=o.querySelector("code"),m=p?o:g||o,v=[],y=g.textContent.match(n),b=y?y.length+1:1,A=g&&m!=g?function(e,t){var i=getComputedStyle(e),n=getComputedStyle(t);function r(e){return+e.substr(0,e.length-2)}return t.offsetTop+r(n.borderTopWidth)+r(n.paddingTop)-r(i.paddingTop)}(o,g):0;h.forEach((function(e){var t=e.split("-"),i=+t[0],n=+t[1]||i;if(!((n=Math.min(b+d,n))i&&r.setAttribute("data-end",String(n)),r.style.top=(i-d-1)*f+A+"px",r.textContent=new Array(n-i+2).join(" \n")}));v.push((function(){r.style.width=o.scrollWidth+"px"})),v.push((function(){m.appendChild(r)}))}}));var P=o.id;if(p&&Prism.util.isActive(o,i)&&P){l(o,i)||v.push((function(){o.classList.add(i)}));var E=parseInt(o.getAttribute("data-start")||"1");s(".line-numbers-rows > span",o).forEach((function(e,t){var i=t+E;e.onclick=function(){var e=P+"."+i;r=!1,location.hash=e,setTimeout((function(){r=!0}),1)}}))}return function(){v.forEach(a)}}};var o=0;Prism.hooks.add("before-sanity-check",(function(e){var t=e.element.parentElement;if(u(t)){var i=0;s(".line-highlight",t).forEach((function(e){i+=e.textContent.length,e.parentNode.removeChild(e)})),i&&/^(?: \n)+$/.test(e.code.slice(-i))&&(e.code=e.code.slice(0,-i))}})),Prism.hooks.add("complete",(function e(i){var n=i.element.parentElement;if(u(n)){clearTimeout(o);var r=Prism.plugins.lineNumbers,s=i.plugins&&i.plugins.lineNumbers;l(n,t)&&r&&!s?Prism.hooks.add("line-numbers",e):(Prism.plugins.lineHighlight.highlightLines(n)(),o=setTimeout(c,1))}})),window.addEventListener("hashchange",c),window.addEventListener("resize",(function(){s("pre").filter(u).map((function(e){return Prism.plugins.lineHighlight.highlightLines(e)})).forEach(a)}))}function s(e,t){return Array.prototype.slice.call((t||document).querySelectorAll(e))}function l(e,t){return e.classList.contains(t)}function a(e){e()}function u(e){return!!(e&&/pre/i.test(e.nodeName)&&(e.hasAttribute("data-line")||e.id&&Prism.util.isActive(e,i)))}function c(){var e=location.hash.slice(1);s(".temporary.line-highlight").forEach((function(e){e.parentNode.removeChild(e)}));var t=(e.match(/\.([\d,-]+)$/)||[,""])[1];if(t&&!document.getElementById(e)){var i=e.slice(0,e.lastIndexOf(".")),n=document.getElementById(i);n&&(n.hasAttribute("data-line")||n.setAttribute("data-line",""),Prism.plugins.lineHighlight.highlightLines(n,t,"temporary ")(),r&&document.querySelector(".temporary.line-highlight").scrollIntoView())}}}();
+!function(){if("undefined"!=typeof Prism&&"undefined"!=typeof document){var e="line-numbers",n=/\n(?!$)/g,t=Prism.plugins.lineNumbers={getLine:function(n,t){if("PRE"===n.tagName&&n.classList.contains(e)){var i=n.querySelector(".line-numbers-rows");if(i){var r=parseInt(n.getAttribute("data-start"),10)||1,s=r+(i.children.length-1);ts&&(t=s);var l=t-r;return i.children[l]}}},resize:function(e){r([e])},assumeViewportIndependence:!0},i=void 0;window.addEventListener("resize",(function(){t.assumeViewportIndependence&&i===window.innerWidth||(i=window.innerWidth,r(Array.prototype.slice.call(document.querySelectorAll("pre.line-numbers"))))})),Prism.hooks.add("complete",(function(t){if(t.code){var i=t.element,s=i.parentNode;if(s&&/pre/i.test(s.nodeName)&&!i.querySelector(".line-numbers-rows")&&Prism.util.isActive(i,e)){i.classList.remove(e),s.classList.add(e);var l,o=t.code.match(n),a=o?o.length+1:1,u=new Array(a+1).join(" ");(l=document.createElement("span")).setAttribute("aria-hidden","true"),l.className="line-numbers-rows",l.innerHTML=u,s.hasAttribute("data-start")&&(s.style.counterReset="linenumber "+(parseInt(s.getAttribute("data-start"),10)-1)),t.element.appendChild(l),r([s]),Prism.hooks.run("line-numbers",t)}}})),Prism.hooks.add("line-numbers",(function(e){e.plugins=e.plugins||{},e.plugins.lineNumbers=!0}))}function r(e){if(0!=(e=e.filter((function(e){var n,t=(n=e,n?window.getComputedStyle?getComputedStyle(n):n.currentStyle||null:null)["white-space"];return"pre-wrap"===t||"pre-line"===t}))).length){var t=e.map((function(e){var t=e.querySelector("code"),i=e.querySelector(".line-numbers-rows");if(t&&i){var r=e.querySelector(".line-numbers-sizer"),s=t.textContent.split(n);r||((r=document.createElement("span")).className="line-numbers-sizer",t.appendChild(r)),r.innerHTML="0",r.style.display="block";var l=r.getBoundingClientRect().height;return r.innerHTML="",{element:e,lines:s,lineHeights:[],oneLinerHeight:l,sizer:r}}})).filter(Boolean);t.forEach((function(e){var n=e.sizer,t=e.lines,i=e.lineHeights,r=e.oneLinerHeight;i[t.length-1]=void 0,t.forEach((function(e,t){if(e&&e.length>1){var s=n.appendChild(document.createElement("span"));s.style.display="block",s.textContent=e}else i[t]=r}))})),t.forEach((function(e){for(var n=e.sizer,t=e.lineHeights,i=0,r=0;rt&&(o[l]="\n"+o[l],a=s)}n[i]=o.join("")}return n.join("\n")}},"undefined"!=typeof module&&module.exports&&(module.exports=n),Prism.plugins.NormalizeWhitespace=new n({"remove-trailing":!0,"remove-indent":!0,"left-trim":!0,"right-trim":!0}),Prism.hooks.add("before-sanity-check",(function(e){var n=Prism.plugins.NormalizeWhitespace;if((!e.settings||!1!==e.settings["whitespace-normalization"])&&Prism.util.isActive(e.element,"whitespace-normalization",!0))if(e.element&&e.element.parentNode||!e.code){var r=e.element.parentNode;if(e.code&&r&&"pre"===r.nodeName.toLowerCase()){for(var i in null==e.settings&&(e.settings={}),t)if(Object.hasOwnProperty.call(t,i)){var o=t[i];if(r.hasAttribute("data-"+i))try{var a=JSON.parse(r.getAttribute("data-"+i)||"true");typeof a===o&&(e.settings[i]=a)}catch(e){}}for(var l=r.childNodes,s="",c="",u=!1,m=0;m3.0.0
3.0.0
https://github.com/zHaytam/Blazor.Diagrams
- 3.0.0-beta.5
+ 3.0.0-beta.6
Z.Blazor.Diagrams.Algorithms
blazor diagrams diagramming svg drag algorithms layouts
Z.Blazor.Diagrams.Algorithms
diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs
index 4171853ef..2ac47210e 100644
--- a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs
+++ b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs
@@ -93,6 +93,7 @@ private void OnPointerUp(Model? model, MouseEventArgs e)
if (_ongoingLink.IsAttached) // Snapped already
{
+ _ongoingLink.TriggerTargetAttached();
_ongoingLink = null;
return;
}
@@ -101,6 +102,7 @@ private void OnPointerUp(Model? model, MouseEventArgs e)
{
var targetAnchor = Diagram.Options.Links.TargetAnchorFactory(Diagram, _ongoingLink, linkable);
_ongoingLink.SetTarget(targetAnchor);
+ _ongoingLink.TriggerTargetAttached();
_ongoingLink.Refresh();
_ongoingLink.RefreshLinks();
}
@@ -114,17 +116,29 @@ private void OnPointerUp(Model? model, MouseEventArgs e)
private PortModel? FindNearPortToAttachTo()
{
- var ongoingPosition = _targetPositionAnchor!.GetPosition(_ongoingLink!)!;
- foreach (var port in Diagram.Nodes.SelectMany(n => n.Ports))
+ if (_ongoingLink is null || _targetPositionAnchor is null)
+ return null;
+
+ PortModel? nearestSnapPort = null;
+ var nearestSnapPortDistance = double.PositiveInfinity;
+
+ var position = _targetPositionAnchor!.GetPosition(_ongoingLink)!;
+
+ foreach (var port in Diagram.Nodes.SelectMany((NodeModel n) => n.Ports))
{
- if (ongoingPosition.DistanceTo(port.MiddlePosition) < Diagram.Options.Links.SnappingRadius
- && (_ongoingLink!.Source.Model == null || _ongoingLink.Source.Model.CanAttachTo(port)))
+ var distance = position.DistanceTo(port.Position);
+
+ if (distance <= Diagram.Options.Links.SnappingRadius && (_ongoingLink.Source.Model?.CanAttachTo(port) != false))
{
- return port;
+ if (distance < nearestSnapPortDistance)
+ {
+ nearestSnapPortDistance = distance;
+ nearestSnapPort = port;
+ }
}
}
- return null;
+ return nearestSnapPort;
}
public override void Dispose()
diff --git a/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsBehavior.cs
index 86f6b6a53..c48293617 100644
--- a/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsBehavior.cs
+++ b/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsBehavior.cs
@@ -34,7 +34,6 @@ public bool RemoveShortcut(string key, bool ctrl, bool shift, bool alt)
private async void OnDiagramKeyDown(KeyboardEventArgs e)
{
var k = KeysUtils.GetStringRepresentation(e.CtrlKey, e.ShiftKey, e.AltKey, e.Key);
- Console.WriteLine(k);
if (_shortcuts.TryGetValue(k, out var action))
{
await action(Diagram);
diff --git a/src/Blazor.Diagrams.Core/Behaviors/SelectionBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/SelectionBehavior.cs
index 94c730eee..016427eb8 100644
--- a/src/Blazor.Diagrams.Core/Behaviors/SelectionBehavior.cs
+++ b/src/Blazor.Diagrams.Core/Behaviors/SelectionBehavior.cs
@@ -10,10 +10,9 @@ public SelectionBehavior(Diagram diagram) : base(diagram)
Diagram.PointerDown += OnPointerDown;
}
- private void OnPointerDown(Model? model, PointerEventArgs e) => Process(model, e.CtrlKey);
-
- private void Process(Model? model, bool ctrlKey)
+ private void OnPointerDown(Model? model, PointerEventArgs e)
{
+ var ctrlKey = e.CtrlKey;
switch (model)
{
case null:
diff --git a/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj b/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj
index ca57e422b..0c6375669 100644
--- a/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj
+++ b/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj
@@ -10,7 +10,7 @@
3.0.0
3.0.0
https://github.com/Blazor-Diagrams/Blazor.Diagrams
- 3.0.0-beta.5
+ 3.0.0-beta.6
Z.Blazor.Diagrams.Core
blazor diagrams diagramming svg drag
Z.Blazor.Diagrams.Core
diff --git a/src/Blazor.Diagrams.Core/Diagram.cs b/src/Blazor.Diagrams.Core/Diagram.cs
index 502295be7..4467a62f8 100644
--- a/src/Blazor.Diagrams.Core/Diagram.cs
+++ b/src/Blazor.Diagrams.Core/Diagram.cs
@@ -21,7 +21,6 @@ public abstract class Diagram
{
private readonly Dictionary _behaviors;
private readonly List _orderedSelectables;
- private bool _suspendSorting;
public event Action? PointerDown;
public event Action? PointerMove;
@@ -77,6 +76,7 @@ protected Diagram()
public Point Pan { get; private set; } = Point.Zero;
public double Zoom { get; private set; } = 1;
public bool SuspendRefresh { get; set; }
+ public bool SuspendSorting { get; set; }
public IReadOnlyList OrderedSelectables => _orderedSelectables;
public void Refresh()
@@ -304,12 +304,12 @@ public void SendToBack(SelectableModel model)
// Todo: can optimize this by only updating the order of items before model
Batch(() =>
{
- _suspendSorting = true;
+ SuspendSorting = true;
for (var i = 0; i < _orderedSelectables.Count; i++)
{
_orderedSelectables[i].Order = i + 1;
}
- _suspendSorting = false;
+ SuspendSorting = false;
});
}
@@ -324,9 +324,9 @@ public void SendToFront(SelectableModel model)
_orderedSelectables.Add(model);
- _suspendSorting = true;
+ SuspendSorting = true;
model.Order = maxOrder + 1;
- _suspendSorting = false;
+ SuspendSorting = false;
Refresh();
}
@@ -340,11 +340,29 @@ public int GetMaxOrder()
return _orderedSelectables.Count > 0 ? _orderedSelectables[^1].Order : 0;
}
+ ///
+ /// Sorts the list of selectables based on their order
+ ///
+ public void RefreshOrders(bool refresh = true)
+ {
+ _orderedSelectables.Sort((a, b) => a.Order.CompareTo(b.Order));
+
+ if (refresh)
+ {
+ Refresh();
+ }
+ }
+
private void OnSelectableAdded(SelectableModel model)
{
- var maxOrder = GetMaxOrder();
_orderedSelectables.Add(model);
- model.Order = maxOrder + 1;
+
+ if (model.Order == 0)
+ {
+ var maxOrder = GetMaxOrder();
+ model.Order = maxOrder + 1;
+ }
+
model.OrderChanged += OnModelOrderChanged;
}
@@ -356,11 +374,10 @@ private void OnSelectableRemoved(SelectableModel model)
private void OnModelOrderChanged(Model model)
{
- if (_suspendSorting)
+ if (SuspendSorting)
return;
- _orderedSelectables.Sort((a, b) => a.Order.CompareTo(b.Order));
- Refresh();
+ RefreshOrders();
}
#endregion
diff --git a/src/Blazor.Diagrams.Core/Layers/BaseLayer.cs b/src/Blazor.Diagrams.Core/Layers/BaseLayer.cs
index 0880a7ec8..57d21bc69 100644
--- a/src/Blazor.Diagrams.Core/Layers/BaseLayer.cs
+++ b/src/Blazor.Diagrams.Core/Layers/BaseLayer.cs
@@ -17,7 +17,7 @@ public BaseLayer(Diagram diagram)
Diagram = diagram;
}
- public virtual T Add(T item)
+ public virtual TSpecific Add(TSpecific item) where TSpecific : T
{
if (item is null)
throw new ArgumentNullException(nameof(item));
diff --git a/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs b/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs
index 75f420cfe..da51dd783 100644
--- a/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs
+++ b/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs
@@ -10,9 +10,10 @@ namespace Blazor.Diagrams.Core.Models.Base;
public abstract class BaseLinkModel : SelectableModel, IHasBounds, ILinkable
{
private readonly List _links = new();
-
+
public event Action? SourceChanged;
public event Action? TargetChanged;
+ public event Action? TargetAttached;
protected BaseLinkModel(Anchor source, Anchor target)
{
@@ -86,20 +87,23 @@ public void SetTarget(Anchor anchor)
var maxX = double.NegativeInfinity;
var maxY = double.NegativeInfinity;
- foreach (var path in PathGeneratorResult.Paths)
- {
- var bbox = path.GetBBox();
- minX = Math.Min(minX, bbox.Left);
- minY = Math.Min(minY, bbox.Top);
- maxX = Math.Max(maxX, bbox.Right);
- maxY = Math.Max(maxY, bbox.Bottom);
- }
+ var path = PathGeneratorResult.FullPath;
+ var bbox = path.GetBBox();
+ minX = Math.Min(minX, bbox.Left);
+ minY = Math.Min(minY, bbox.Top);
+ maxX = Math.Max(maxX, bbox.Right);
+ maxY = Math.Max(maxY, bbox.Bottom);
return new Rectangle(minX, minY, maxX, maxY);
}
public bool CanAttachTo(ILinkable other) => true;
+ ///
+ /// Triggers the TargetAttached event
+ ///
+ public void TriggerTargetAttached() => TargetAttached?.Invoke(this);
+
private void GeneratePath()
{
if (Diagram != null)
diff --git a/src/Blazor.Diagrams.Core/Models/GroupModel.cs b/src/Blazor.Diagrams.Core/Models/GroupModel.cs
index 4153fbb17..88a9636fc 100644
--- a/src/Blazor.Diagrams.Core/Models/GroupModel.cs
+++ b/src/Blazor.Diagrams.Core/Models/GroupModel.cs
@@ -61,7 +61,10 @@ public override void SetPosition(double x, double y)
base.SetPosition(x, y);
foreach (var node in Children)
+ {
node.UpdatePositionSilently(deltaX, deltaY);
+ node.RefreshLinks();
+ }
Refresh();
RefreshLinks();
diff --git a/src/Blazor.Diagrams/Blazor.Diagrams.csproj b/src/Blazor.Diagrams/Blazor.Diagrams.csproj
index aff38c09b..6113ca473 100644
--- a/src/Blazor.Diagrams/Blazor.Diagrams.csproj
+++ b/src/Blazor.Diagrams/Blazor.Diagrams.csproj
@@ -9,7 +9,7 @@
3.0.0
https://github.com/Blazor-Diagrams/Blazor.Diagrams
A fully customizable and extensible all-purpose diagrams library for Blazor
- 3.0.0-beta.5
+ 3.0.0-beta.6
true
blazor diagrams diagramming svg drag
Z.Blazor.Diagrams
diff --git a/src/Blazor.Diagrams/Components/Controls/ControlsLayerRenderer.razor.cs b/src/Blazor.Diagrams/Components/Controls/ControlsLayerRenderer.razor.cs
index afb34664b..e3e8f4a64 100644
--- a/src/Blazor.Diagrams/Components/Controls/ControlsLayerRenderer.razor.cs
+++ b/src/Blazor.Diagrams/Components/Controls/ControlsLayerRenderer.razor.cs
@@ -57,7 +57,7 @@ private RenderFragment RenderControl(Model model, Control control, Point positio
{
builder.OpenElement(0, svg ? "g" : "div");
builder.AddAttribute(1, "class",
- $"{(control is ExecutableControl ? "executable " : "")}control {control.GetType().Name}");
+ $"{(control is ExecutableControl ? "executable " : "")}diagram-control {control.GetType().Name}");
if (svg)
builder.AddAttribute(2, "transform",
$"translate({position.X.ToInvariantString()} {position.Y.ToInvariantString()})");
diff --git a/src/Blazor.Diagrams/Components/DefaultLinkLabelWidget.razor b/src/Blazor.Diagrams/Components/DefaultLinkLabelWidget.razor
index 7f2a1c0a7..e4814d1d3 100644
--- a/src/Blazor.Diagrams/Components/DefaultLinkLabelWidget.razor
+++ b/src/Blazor.Diagrams/Components/DefaultLinkLabelWidget.razor
@@ -1,4 +1,4 @@
-
+
@Label.Content
diff --git a/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs
index 041a993f9..c83391541 100644
--- a/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs
+++ b/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs
@@ -84,7 +84,7 @@ protected override void BuildRenderTree(RenderTreeBuilder builder)
return;
var componentType = BlazorDiagram.GetComponent(Group) ?? typeof(DefaultGroupWidget);
- var classes = new StringBuilder("group")
+ var classes = new StringBuilder("diagram-group")
.AppendIf(" locked", Group.Locked)
.AppendIf(" selected", Group.Selected)
.AppendIf(" default", componentType == typeof(DefaultGroupWidget));
diff --git a/src/Blazor.Diagrams/Components/Renderers/LinkLabelRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/LinkLabelRenderer.cs
index cee45c432..802a83b4b 100644
--- a/src/Blazor.Diagrams/Components/Renderers/LinkLabelRenderer.cs
+++ b/src/Blazor.Diagrams/Components/Renderers/LinkLabelRenderer.cs
@@ -39,7 +39,7 @@ protected override void BuildRenderTree(RenderTreeBuilder builder)
var componentType = BlazorDiagram.GetComponent(Label) ?? typeof(DefaultLinkLabelWidget);
builder.OpenElement(0, "foreignObject");
- builder.AddAttribute(1, "class", "link-label");
+ builder.AddAttribute(1, "class", "diagram-link-label");
builder.AddAttribute(2, "x", (position.X + (Label.Offset?.X ?? 0)).ToInvariantString());
builder.AddAttribute(3, "y", (position.Y + (Label.Offset?.Y ?? 0)).ToInvariantString());
diff --git a/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs
index 3e33e9428..8cbe42d9d 100644
--- a/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs
+++ b/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs
@@ -46,7 +46,7 @@ protected override void BuildRenderTree(RenderTreeBuilder builder)
var componentType = BlazorDiagram.GetComponent(Link) ?? typeof(LinkWidget);
var classes = new StringBuilder()
- .Append("link")
+ .Append("diagram-link")
.AppendIf(" attached", Link.IsAttached)
.ToString();
diff --git a/src/Blazor.Diagrams/Components/Renderers/LinkVertexRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/LinkVertexRenderer.cs
index 8976e5e7e..023aac91f 100644
--- a/src/Blazor.Diagrams/Components/Renderers/LinkVertexRenderer.cs
+++ b/src/Blazor.Diagrams/Components/Renderers/LinkVertexRenderer.cs
@@ -43,7 +43,7 @@ protected override void BuildRenderTree(RenderTreeBuilder builder)
var componentType = BlazorDiagram.GetComponent(Vertex);
builder.OpenElement(0, "g");
- builder.AddAttribute(1, "class", "link-vertex");
+ builder.AddAttribute(1, "class", "diagram-link-vertex");
builder.AddAttribute(4, "cursor", "move");
builder.AddAttribute(5, "ondblclick", value: EventCallback.Factory.Create
(this, OnDoubleClick));
builder.AddAttribute(6, "onpointerdown", EventCallback.Factory.Create(this, OnPointerDown));
diff --git a/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs
index a1c934190..9778b46b7 100644
--- a/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs
+++ b/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs
@@ -89,7 +89,7 @@ protected override void BuildRenderTree(RenderTreeBuilder builder)
var componentType = BlazorDiagram.GetComponent(Node) ??
(_isSvg ? typeof(SvgNodeWidget) : typeof(NodeWidget));
- var classes = new StringBuilder("node")
+ var classes = new StringBuilder("diagram-node")
.AppendIf(" locked", Node.Locked)
.AppendIf(" selected", Node.Selected)
.AppendIf(" grouped", Node.Group != null);
diff --git a/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs
index baf5c21f6..c7f0c7997 100644
--- a/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs
+++ b/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs
@@ -22,13 +22,10 @@ public class PortRenderer : ComponentBase, IDisposable
private bool _updatingDimensions;
[CascadingParameter] public BlazorDiagram BlazorDiagram { get; set; } = null!;
-
[Inject] private IJSRuntime JSRuntime { get; set; } = null!;
-
[Parameter] public PortModel Port { get; set; } = null!;
-
[Parameter] public string? Class { get; set; }
-
+ [Parameter] public string? Style { get; set; }
[Parameter] public RenderFragment? ChildContent { get; set; }
public void Dispose()
@@ -67,16 +64,17 @@ protected override void BuildRenderTree(RenderTreeBuilder builder)
return;
builder.OpenElement(0, _isParentSvg ? "g" : "div");
- builder.AddAttribute(1, "class",
- "port" + " " + Port.Alignment.ToString().ToLower() + " " + (Port.Links.Count > 0 ? "has-links" : "") + " " +
+ builder.AddAttribute(1, "style", Style);
+ builder.AddAttribute(2, "class",
+ "diagram-port" + " " + Port.Alignment.ToString().ToLower() + " " + (Port.Links.Count > 0 ? "has-links" : "") + " " +
Class);
- builder.AddAttribute(2, "data-port-id", Port.Id);
- builder.AddAttribute(3, "onpointerdown", EventCallback.Factory.Create(this, OnPointerDown));
- builder.AddEventStopPropagationAttribute(4, "onpointerdown", true);
- builder.AddAttribute(5, "onpointerup", EventCallback.Factory.Create(this, OnPointerUp));
- builder.AddEventStopPropagationAttribute(6, "onpointerup", true);
- builder.AddElementReferenceCapture(7, __value => { _element = __value; });
- builder.AddContent(8, ChildContent);
+ builder.AddAttribute(3, "data-port-id", Port.Id);
+ builder.AddAttribute(4, "onpointerdown", EventCallback.Factory.Create(this, OnPointerDown));
+ builder.AddEventStopPropagationAttribute(5, "onpointerdown", true);
+ builder.AddAttribute(6, "onpointerup", EventCallback.Factory.Create(this, OnPointerUp));
+ builder.AddEventStopPropagationAttribute(7, "onpointerup", true);
+ builder.AddElementReferenceCapture(8, __value => { _element = __value; });
+ builder.AddContent(9, ChildContent);
builder.CloseElement();
}
diff --git a/src/Blazor.Diagrams/wwwroot/default.styles.css b/src/Blazor.Diagrams/wwwroot/default.styles.css
index bcb3061dd..b095bbe4e 100644
--- a/src/Blazor.Diagrams/wwwroot/default.styles.css
+++ b/src/Blazor.Diagrams/wwwroot/default.styles.css
@@ -22,61 +22,61 @@
border: 1px solid #6e9fd4;
}
-.default-node.selected .port {
- border: 1px solid #6e9fd4;
-}
-
-.default-node .port, .default.group .port {
- width: 20px;
- height: 20px;
- margin: -10px;
- border-radius: 50%;
- background-color: #f5f5f5;
- border: 1px solid #d4d4d4;
- cursor: pointer;
- position: absolute;
-}
-
-.default-node .port:hover, .default-node .port.has-links, .default.group .port.has-links {
- background-color: black;
-}
-
-.default-node .port.bottom, .default.group .port.bottom {
- bottom: 0px;
- left: 50%;
-}
-
-.default-node .port.bottomleft, .default.group .port.bottomleft {
- bottom: 0px;
- left: 0px;
-}
-
-.default-node .port.bottomright, .default.group .port.bottomright {
+ .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: black;
+ }
+
+ .default-node .diagram-port.bottom, .default.diagram-group .diagram-port.bottom {
+ bottom: 0px;
+ left: 50%;
+ }
+
+ .default-node .diagram-port.bottomleft, .default.diagram-group .diagram-port.bottomleft {
+ bottom: 0px;
+ left: 0px;
+ }
+
+.default-node .diagram-port.bottomright, .default.diagram-group .diagram-port.bottomright {
bottom: 0px;
right: 0px;
}
-.default-node .port.top, .default.group .port.top {
+.default-node .diagram-port.top, .default.diagram-group .diagram-port.top {
top: 0px;
left: 50%;
}
-.default-node .port.topleft, .default.group .port.topleft {
+.default-node .diagram-port.topleft, .default.diagram-group .diagram-port.topleft {
top: 0px;
left: 0px;
}
-.default-node .port.topright, .default.group .port.topright {
+.default-node .diagram-port.topright, .default.diagram-group .diagram-port.topright {
top: 0px;
right: 0px;
}
-.default-node .port.left, .default.group .port.left {
+.default-node .diagram-port.left, .default.diagram-group .diagram-port.left {
left: 0px;
top: 50%;
}
-.default-node .port.right, .default.group .port.right {
+.default-node .diagram-port.right, .default.diagram-group .diagram-port.right {
right: 0px;
top: 50%;
}
@@ -91,25 +91,25 @@
background-color: white;
}
-div.group.default {
+div.diagram-group.default {
outline: 2px solid black;
background: rgb(198, 198, 198);
}
-div.group.default.selected {
+div.diagram-group.default.selected {
outline: 2px solid #6e9fd4;
}
-g.group.default rect {
+g.diagram-group.default rect {
outline: 2px solid black;
fill: rgb(198, 198, 50);
}
-g.group.default.selected > rect {
+g.diagram-group.default.selected > rect {
outline: 2px solid green;
}
-.link div.link-label {
+.diagram-link div.default-link-label {
display: inline-block;
color: #fff;
background-color: rgb(110, 159, 212);
diff --git a/src/Blazor.Diagrams/wwwroot/default.styles.min.css b/src/Blazor.Diagrams/wwwroot/default.styles.min.css
index 010c8a863..c44adedc3 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 .port{border:1px solid #6e9fd4;}.default-node .port,.default.group .port{width:20px;height:20px;margin:-10px;border-radius:50%;background-color:#f5f5f5;border:1px solid #d4d4d4;cursor:pointer;position:absolute;}.default-node .port:hover,.default-node .port.has-links,.default.group .port.has-links{background-color:#000;}.default-node .port.bottom,.default.group .port.bottom{bottom:0;left:50%;}.default-node .port.bottomleft,.default.group .port.bottomleft{bottom:0;left:0;}.default-node .port.bottomright,.default.group .port.bottomright{bottom:0;right:0;}.default-node .port.top,.default.group .port.top{top:0;left:50%;}.default-node .port.topleft,.default.group .port.topleft{top:0;left:0;}.default-node .port.topright,.default.group .port.topright{top:0;right:0;}.default-node .port.left,.default.group .port.left{left:0;top:50%;}.default-node .port.right,.default.group .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.group.default{outline:2px solid #000;background:#c6c6c6;}div.group.default.selected{outline:2px solid #6e9fd4;}g.group.default rect{outline:2px solid #000;fill:#c6c632;}g.group.default.selected>rect{outline:2px solid #008000;}.link div.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%);}
\ 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 eb44349f8..905f20cd3 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/src/Blazor.Diagrams/wwwroot/style.css b/src/Blazor.Diagrams/wwwroot/style.css
index a3b81d994..35c0aaa30 100644
--- a/src/Blazor.Diagrams/wwwroot/style.css
+++ b/src/Blazor.Diagrams/wwwroot/style.css
@@ -35,7 +35,7 @@
overflow: visible;
}
-.node {
+.diagram-node {
position: absolute;
-webkit-user-select: none;
-moz-user-select: none;
@@ -45,11 +45,11 @@
pointer-events: all;
}
-.node.locked {
- cursor: pointer;
-}
+ .diagram-node.locked {
+ cursor: pointer;
+ }
-.link {
+.diagram-link {
pointer-events: visiblePainted;
cursor: pointer;
}
@@ -63,7 +63,7 @@
border: 2px solid black;
}
-.group {
+.diagram-group {
position: absolute;
-webkit-user-select: none;
-moz-user-select: none;
@@ -73,22 +73,22 @@
pointer-events: all;
}
-.group .children {
- position: absolute;
- overflow: visible;
- pointer-events: none;
-}
+ .diagram-group .children {
+ position: absolute;
+ overflow: visible;
+ pointer-events: none;
+ }
-.link foreignObject.link-label {
+.diagram-link foreignObject.diagram-link-label {
overflow: visible;
pointer-events: none;
}
-div.control {
+div.diagram-control {
position: absolute;
}
-.executable.control {
+.executable.diagram-control {
pointer-events: all;
cursor: pointer;
}
diff --git a/src/Blazor.Diagrams/wwwroot/style.min.css b/src/Blazor.Diagrams/wwwroot/style.min.css
index 87e09655e..4abcd9d42 100644
--- a/src/Blazor.Diagrams/wwwroot/style.min.css
+++ b/src/Blazor.Diagrams/wwwroot/style.min.css
@@ -1 +1 @@
-.diagram-canvas{width:100%;height:100%;position:relative;outline:none;overflow:hidden;cursor:-webkit-grab;cursor:grab;touch-action:none;}.diagram-svg-layer,.diagram-html-layer{top:0;left:0;right:0;bottom:0;position:absolute;pointer-events:none;-webkit-transform-origin:0 0;-ms-transform-origin:0 0;transform-origin:0 0;width:100%;height:100%;overflow:visible;}.html-layer,.svg-layer{position:absolute;pointer-events:none;-webkit-transform-origin:0 0;-ms-transform-origin:0 0;transform-origin:0 0;width:100%;height:100%;overflow:visible;}.node{position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:move;pointer-events:all;}.node.locked{cursor:pointer;}.link{pointer-events:visiblePainted;cursor:pointer;}.diagram-navigator{z-index:10;}.diagram-navigator .current-view{position:absolute;border:2px solid #000;}.group{position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:move;pointer-events:all;}.group .children{position:absolute;overflow:visible;pointer-events:none;}.link foreignObject.link-label{overflow:visible;pointer-events:none;}div.control{position:absolute;}.executable.control{pointer-events:all;cursor:pointer;}
\ No newline at end of file
+.diagram-canvas{width:100%;height:100%;position:relative;outline:none;overflow:hidden;cursor:-webkit-grab;cursor:grab;touch-action:none;}.diagram-svg-layer,.diagram-html-layer{top:0;left:0;right:0;bottom:0;position:absolute;pointer-events:none;-webkit-transform-origin:0 0;-ms-transform-origin:0 0;transform-origin:0 0;width:100%;height:100%;overflow:visible;}.html-layer,.svg-layer{position:absolute;pointer-events:none;-webkit-transform-origin:0 0;-ms-transform-origin:0 0;transform-origin:0 0;width:100%;height:100%;overflow:visible;}.diagram-node{position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:move;pointer-events:all;}.diagram-node.locked{cursor:pointer;}.diagram-link{pointer-events:visiblePainted;cursor:pointer;}.diagram-navigator{z-index:10;}.diagram-navigator .current-view{position:absolute;border:2px solid #000;}.diagram-group{position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:move;pointer-events:all;}.diagram-group .children{position:absolute;overflow:visible;pointer-events:none;}.diagram-link foreignObject.diagram-link-label{overflow:visible;pointer-events:none;}div.diagram-control{position:absolute;}.executable.diagram-control{pointer-events:all;cursor:pointer;}
\ No newline at end of file
diff --git a/src/Blazor.Diagrams/wwwroot/style.min.css.gz b/src/Blazor.Diagrams/wwwroot/style.min.css.gz
index 39e001315..e72cb7d8a 100644
Binary files a/src/Blazor.Diagrams/wwwroot/style.min.css.gz and b/src/Blazor.Diagrams/wwwroot/style.min.css.gz differ
diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs
index 01134f6ef..bf31df407 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs
@@ -255,7 +255,7 @@ public void Behavior_ShouldUnSnapAndRefreshPort_WhenSnappingIsEnabledAndPortIsNo
}
[Fact]
- public void Behavior_ShouldRemoveLink_WhenMouseUpOnCanvas()
+ public void Behavior_ShouldRemoveLink_WhenMouseUpOnCanvasAndRequireTargetIsTrue()
{
// Arrange
var diagram = new TestDiagram();
@@ -364,5 +364,82 @@ public void Behavior_ShouldNotCreateOngoingLink_WhenFactoryReturnsNull()
// Assert
diagram.Links.Should().HaveCount(0);
}
+
+ [Fact]
+ public void Behavior_ShouldTriggerLinkTargetAttached_WhenMouseUpOnOtherPort()
+ {
+ // Arrange
+ var diagram = new TestDiagram();
+ diagram.SetContainer(new Rectangle(0, 0, 1000, 400));
+ var node1 = new NodeModel(position: new Point(100, 50));
+ var node2 = new NodeModel(position: new Point(160, 50));
+ diagram.Nodes.Add(new[] { node1, node2 });
+ var port1 = node1.AddPort(new PortModel(node1)
+ {
+ Initialized = true,
+ Position = new Point(110, 60),
+ Size = new Size(10, 20)
+ });
+ var port2 = node2.AddPort(new PortModel(node2)
+ {
+ Initialized = true,
+ Position = new Point(170, 60),
+ Size = new Size(10, 20)
+ });
+ var targetAttachedTriggers = 0;
+
+ // Act
+ diagram.TriggerPointerDown(port1,
+ new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true));
+
+ diagram.Links.Single().TargetAttached += _ => targetAttachedTriggers++;
+
+ diagram.TriggerPointerUp(port2,
+ new PointerEventArgs(105, 105, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true));
+
+ // Assert
+ targetAttachedTriggers.Should().Be(1);
+ }
+
+ [Fact]
+ public void Behavior_ShouldTriggerLinkTargetAttached_WhenLinkSnappedToPortAndMouseUp()
+ {
+ // Arrange
+ var diagram = new TestDiagram();
+ diagram.SetContainer(new Rectangle(0, 0, 1000, 400));
+ diagram.Options.Links.EnableSnapping = true;
+ diagram.Options.Links.SnappingRadius = 60;
+ var node1 = new NodeModel(position: new Point(100, 50));
+ var node2 = new NodeModel(position: new Point(160, 50));
+ diagram.Nodes.Add(new[] { node1, node2 });
+ var port1 = node1.AddPort(new PortModel(node1)
+ {
+ Initialized = true,
+ Position = new Point(110, 60),
+ Size = new Size(10, 20)
+ });
+ var port2 = node2.AddPort(new PortModel(node2)
+ {
+ Initialized = true,
+ Position = new Point(170, 60),
+ Size = new Size(10, 20)
+ });
+ var targetAttachedTriggers = 0;
+
+ // Act
+ diagram.TriggerPointerDown(port1,
+ new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true));
+
+ diagram.TriggerPointerMove(null,
+ new PointerEventArgs(140, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true));
+
+ diagram.Links.Single().TargetAttached += _ => targetAttachedTriggers++;
+
+ diagram.TriggerPointerUp(null,
+ new PointerEventArgs(140, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true));
+
+ // Assert
+ targetAttachedTriggers.Should().Be(1);
+ }
}
}
\ No newline at end of file
diff --git a/tests/Blazor.Diagrams.Core.Tests/DiagramOrderingTests.cs b/tests/Blazor.Diagrams.Core.Tests/DiagramOrderingTests.cs
index 8add0604e..492f139b8 100644
--- a/tests/Blazor.Diagrams.Core.Tests/DiagramOrderingTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/DiagramOrderingTests.cs
@@ -161,5 +161,38 @@ public void Diagram_ShouldRefreshOnceWhenMultipleModelsWereRemoved()
// Assert
refreshes.Should().Be(1);
}
+
+ [Fact]
+ public void Diagram_ShouldNotUpdateOrders_WhenSuspendSortingIsTrue()
+ {
+ // Arrange
+ var diagram = new TestDiagram();
+ diagram.SuspendSorting = true;
+ var node1 = diagram.Nodes.Add(new NodeModel()); // 1
+ var node2 = diagram.Nodes.Add(new NodeModel()); // 2
+
+ // Act
+ node1.Order = 10;
+
+ // Assert
+ diagram.OrderedSelectables[0].Should().Be(node1);
+ diagram.OrderedSelectables[1].Should().Be(node2);
+ }
+
+ [Fact]
+ public void RefreshOrders_ShouldSortModels()
+ {
+ // Arrange
+ var diagram = new TestDiagram();
+ var node1 = diagram.Nodes.Add(new NodeModel() { Order = 10 });
+ var node2 = diagram.Nodes.Add(new NodeModel() { Order = 5 });
+
+ // Act
+ diagram.RefreshOrders();
+
+ // Assert
+ diagram.OrderedSelectables[0].Should().Be(node2);
+ diagram.OrderedSelectables[1].Should().Be(node1);
+ }
}
}
diff --git a/tests/Blazor.Diagrams.Core.Tests/Models/Base/BaseLinkModelTests.cs b/tests/Blazor.Diagrams.Core.Tests/Models/Base/BaseLinkModelTests.cs
index d9596fc8e..577843a1d 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Models/Base/BaseLinkModelTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Models/Base/BaseLinkModelTests.cs
@@ -2,6 +2,8 @@
using Blazor.Diagrams.Core.Geometry;
using Blazor.Diagrams.Core.Models;
using Blazor.Diagrams.Core.Models.Base;
+using Blazor.Diagrams.Core.PathGenerators;
+using Blazor.Diagrams.Core.Routers;
using FluentAssertions;
using Xunit;
@@ -74,5 +76,25 @@ public void SetTarget_ShouldChangePropertiesAndTriggerEvent()
linkInstance.Should().BeSameAs(link);
link.Target!.Model.Should().BeSameAs(port);
}
+
+ [Fact]
+ public void GetBounds_ShouldReturnPathBBox()
+ {
+ // Arrange
+ var link = new LinkModel(new PositionAnchor(new Point(10, 5)), new PositionAnchor(new Point(100, 80)));
+ link.Diagram = new TestDiagram();
+ link.PathGenerator = new StraightPathGenerator();
+ link.Router = new NormalRouter();
+
+ // Act
+ link.Refresh();
+ var bounds = link.GetBounds()!;
+
+ // Assert
+ bounds.Left.Should().Be(10);
+ bounds.Top.Should().Be(5);
+ bounds.Width.Should().Be(90);
+ bounds.Height.Should().Be(75);
+ }
}
}
diff --git a/tests/Blazor.Diagrams.Tests/Components/LinkVertexWidgetTests.cs b/tests/Blazor.Diagrams.Tests/Components/LinkVertexWidgetTests.cs
index 990762f55..fb8128020 100644
--- a/tests/Blazor.Diagrams.Tests/Components/LinkVertexWidgetTests.cs
+++ b/tests/Blazor.Diagrams.Tests/Components/LinkVertexWidgetTests.cs
@@ -31,7 +31,7 @@ public void ShouldRenderCircle()
.Add(n => n.BlazorDiagram, new BlazorDiagram()));
// Assert
- cut.MarkupMatches(" ");
+ cut.MarkupMatches(" ");
}
[Fact]
@@ -54,7 +54,7 @@ public void ShouldRenderCircleWithSelectedColor_WhenVertexIsSelected()
.Add(n => n.BlazorDiagram, new BlazorDiagram()));
// Assert
- cut.MarkupMatches(" ");
+ cut.MarkupMatches(" ");
}
[Fact]
@@ -129,7 +129,7 @@ public void ShouldUseCustomComponent_WhenProvided()
.Add(n => n.BlazorDiagram, diagram));
// Assert
- cut.MarkupMatches(" ");
+ cut.MarkupMatches(" ");
}
}
}