navigationRegions = new()
+ {
+ new RectangleHotSpot
+ {
+ Left = 20, Top = 50, Right = 180, Bottom = 120,
+ AlternateText = "Home Page",
+ NavigateUrl = "/"
+ },
+ new RectangleHotSpot
+ {
+ Left = 200, Top = 50, Right = 360, Bottom = 120,
+ AlternateText = "Products",
+ NavigateUrl = "/products"
+ },
+ new RectangleHotSpot
+ {
+ Left = 380, Top = 50, Right = 540, Bottom = 120,
+ AlternateText = "Contact Us",
+ NavigateUrl = "/contact"
+ }
+ };
+}
+```
+
+### Interactive Diagram with PostBack
+
+```razor
+@page "/floor-plan"
+
+Office Floor Plan
+Click on a room to see details
+
+
+
+@if (!string.IsNullOrEmpty(selectedRoom))
+{
+
+
@selectedRoom
+
@roomDetails
+
+}
+
+@code {
+ private string selectedRoom = "";
+ private string roomDetails = "";
+
+ private List roomRegions = new()
+ {
+ new PolygonHotSpot
+ {
+ Coordinates = "50,50,150,50,150,150,50,150",
+ AlternateText = "Conference Room A",
+ PostBackValue = "ConfA"
+ },
+ new CircleHotSpot
+ {
+ X = 300, Y = 100, Radius = 40,
+ AlternateText = "Break Room",
+ PostBackValue = "Break"
+ }
+ };
+
+ private void ShowRoomDetails(ImageMapEventArgs e)
+ {
+ selectedRoom = e.PostBackValue switch
+ {
+ "ConfA" => "Conference Room A",
+ "Break" => "Break Room",
+ _ => "Unknown Room"
+ };
+
+ roomDetails = e.PostBackValue switch
+ {
+ "ConfA" => "Capacity: 12 people. Equipped with projector and whiteboard.",
+ "Break" => "Kitchen facilities, coffee maker, and seating for 8.",
+ _ => ""
+ };
+ }
+}
+```
+
+### Mixed Mode Map
+
+```razor
+
+
+@code {
+ private List mixedRegions = new()
+ {
+ // External link - opens in new window
+ new RectangleHotSpot
+ {
+ Left = 10, Top = 10, Right = 100, Bottom = 60,
+ AlternateText = "Documentation",
+ NavigateUrl = "https://docs.example.com",
+ Target = "_blank",
+ HotSpotMode = HotSpotMode.Navigate
+ },
+
+ // Server action - triggers event
+ new CircleHotSpot
+ {
+ X = 150, Y = 35, Radius = 25,
+ AlternateText = "Download",
+ PostBackValue = "StartDownload",
+ HotSpotMode = HotSpotMode.PostBack
+ },
+
+ // Inactive region - informational only
+ new PolygonHotSpot
+ {
+ Coordinates = "200,10,250,10,225,50",
+ AlternateText = "Coming Soon",
+ HotSpotMode = HotSpotMode.Inactive
+ }
+ };
+
+ private void HandleAction(ImageMapEventArgs e)
+ {
+ if (e.PostBackValue == "StartDownload")
+ {
+ // Initiate download logic
+ }
+ }
+}
+```
+
+## Migration Tips
+
+1. **Convert Declarative HotSpots to Collection**: In Web Forms, HotSpots are declared as child elements. In Blazor, create a List in your @code block.
+
+2. **Event Handler Signature**: Web Forms uses `ImageMapEventHandler` with sender and `ImageMapEventArgs`. Blazor simplifies this - you only need the `ImageMapEventArgs` parameter.
+
+3. **Coordinate Validation**: Consider validating HotSpot coordinates are within image bounds to prevent rendering issues.
+
+4. **Dynamic HotSpots**: In Blazor, you can easily add/remove HotSpots dynamically by modifying the List and calling `StateHasChanged()`.
+
+## See Also
+
+- [Image](Image.md) - Display static images
+- [ImageButton](ImageButton.md) - Clickable image that acts as a button
+- [HyperLink](HyperLink.md) - Text or image hyperlinks
diff --git a/mkdocs.yml b/mkdocs.yml
index fb0ac6c9..0c72ca45 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -72,6 +72,7 @@ nav:
- HiddenField: EditorControls/HiddenField.md
- Image: EditorControls/Image.md
- ImageButton: EditorControls/ImageButton.md
+ - ImageMap: EditorControls/ImageMap.md
- Label: EditorControls/Label.md
- LinkButton: EditorControls/LinkButton.md
- ListBox: EditorControls/ListBox.md
diff --git a/src/BlazorWebFormsComponents.Test/ImageMap/CircleHotSpot.razor b/src/BlazorWebFormsComponents.Test/ImageMap/CircleHotSpot.razor
new file mode 100644
index 00000000..b4916f60
--- /dev/null
+++ b/src/BlazorWebFormsComponents.Test/ImageMap/CircleHotSpot.razor
@@ -0,0 +1,53 @@
+@using BlazorWebFormsComponents.Enums
+
+@code {
+ [Fact]
+ public void ImageMap_CircleHotSpot_RendersCorrectly()
+ {
+ var hotSpots = new List();
+ var circle = new BlazorWebFormsComponents.CircleHotSpot();
+ circle.X = 100;
+ circle.Y = 100;
+ circle.Radius = 50;
+ circle.AlternateText = "Circle Area";
+ circle.NavigateUrl = "/page1.html";
+ hotSpots.Add(circle);
+
+ var cut = Render(@);
+
+ var area = cut.Find("area");
+ area.GetAttribute("shape").ShouldBe("circle");
+ area.GetAttribute("coords").ShouldBe("100,100,50");
+ area.GetAttribute("alt").ShouldBe("Circle Area");
+ area.GetAttribute("href").ShouldBe("/page1.html");
+ }
+
+ [Fact]
+ public void ImageMap_MultipleCircleHotSpots_RendersAll()
+ {
+ var hotSpots = new List();
+
+ var circle1 = new BlazorWebFormsComponents.CircleHotSpot();
+ circle1.X = 50;
+ circle1.Y = 50;
+ circle1.Radius = 25;
+ circle1.AlternateText = "Circle 1";
+ circle1.NavigateUrl = "/page1.html";
+ hotSpots.Add(circle1);
+
+ var circle2 = new BlazorWebFormsComponents.CircleHotSpot();
+ circle2.X = 150;
+ circle2.Y = 150;
+ circle2.Radius = 35;
+ circle2.AlternateText = "Circle 2";
+ circle2.NavigateUrl = "/page2.html";
+ hotSpots.Add(circle2);
+
+ var cut = Render(@);
+
+ var areas = cut.FindAll("area");
+ areas.Count.ShouldBe(2);
+ areas[0].GetAttribute("coords").ShouldBe("50,50,25");
+ areas[1].GetAttribute("coords").ShouldBe("150,150,35");
+ }
+}
diff --git a/src/BlazorWebFormsComponents.Test/ImageMap/InactiveMode.razor b/src/BlazorWebFormsComponents.Test/ImageMap/InactiveMode.razor
new file mode 100644
index 00000000..b578151c
--- /dev/null
+++ b/src/BlazorWebFormsComponents.Test/ImageMap/InactiveMode.razor
@@ -0,0 +1,58 @@
+@using BlazorWebFormsComponents.Enums
+
+@code {
+ [Fact]
+ public void ImageMap_InactiveMode_RendersNoHrefAttribute()
+ {
+ var hotSpots = new List();
+ var rect = new BlazorWebFormsComponents.RectangleHotSpot();
+ rect.Left = 0;
+ rect.Top = 0;
+ rect.Right = 100;
+ rect.Bottom = 100;
+ rect.AlternateText = "Inactive Area";
+ rect.HotSpotMode = HotSpotMode.Inactive;
+ hotSpots.Add(rect);
+
+ var cut = Render(@);
+
+ var area = cut.Find("area");
+ area.GetAttribute("shape").ShouldBe("rect");
+ area.GetAttribute("nohref").ShouldBe("nohref");
+ area.HasAttribute("href").ShouldBeFalse();
+ }
+
+ [Fact]
+ public void ImageMap_InactiveMode_MultipleHotSpots_AllInactive()
+ {
+ var hotSpots = new List();
+
+ var rect = new BlazorWebFormsComponents.RectangleHotSpot();
+ rect.Left = 0;
+ rect.Top = 0;
+ rect.Right = 50;
+ rect.Bottom = 50;
+ rect.AlternateText = "Area 1";
+ rect.HotSpotMode = HotSpotMode.Inactive;
+ hotSpots.Add(rect);
+
+ var circle = new BlazorWebFormsComponents.CircleHotSpot();
+ circle.X = 100;
+ circle.Y = 100;
+ circle.Radius = 30;
+ circle.AlternateText = "Area 2";
+ circle.HotSpotMode = HotSpotMode.Inactive;
+ hotSpots.Add(circle);
+
+ var cut = Render(@);
+
+ var areas = cut.FindAll("area");
+ areas.Count.ShouldBe(2);
+
+ foreach (var area in areas)
+ {
+ area.GetAttribute("nohref").ShouldBe("nohref");
+ area.HasAttribute("href").ShouldBeFalse();
+ }
+ }
+}
diff --git a/src/BlazorWebFormsComponents.Test/ImageMap/MixedHotSpots.razor b/src/BlazorWebFormsComponents.Test/ImageMap/MixedHotSpots.razor
new file mode 100644
index 00000000..e174b5cf
--- /dev/null
+++ b/src/BlazorWebFormsComponents.Test/ImageMap/MixedHotSpots.razor
@@ -0,0 +1,41 @@
+@using BlazorWebFormsComponents.Enums
+
+@code {
+ [Fact]
+ public void ImageMap_MixedHotSpots_RendersAll()
+ {
+ var hotSpots = new List();
+
+ var rect = new BlazorWebFormsComponents.RectangleHotSpot();
+ rect.Left = 0;
+ rect.Top = 0;
+ rect.Right = 100;
+ rect.Bottom = 100;
+ rect.AlternateText = "Rectangle";
+ rect.NavigateUrl = "/rect.html";
+ hotSpots.Add(rect);
+
+ var circle = new BlazorWebFormsComponents.CircleHotSpot();
+ circle.X = 200;
+ circle.Y = 50;
+ circle.Radius = 40;
+ circle.AlternateText = "Circle";
+ circle.NavigateUrl = "/circle.html";
+ hotSpots.Add(circle);
+
+ var polygon = new BlazorWebFormsComponents.PolygonHotSpot();
+ polygon.Coordinates = "300,10,350,50,320,90";
+ polygon.AlternateText = "Triangle";
+ polygon.NavigateUrl = "/triangle.html";
+ hotSpots.Add(polygon);
+
+ var cut = Render(@);
+
+ var areas = cut.FindAll("area");
+ areas.Count.ShouldBe(3);
+
+ areas[0].GetAttribute("shape").ShouldBe("rect");
+ areas[1].GetAttribute("shape").ShouldBe("circle");
+ areas[2].GetAttribute("shape").ShouldBe("poly");
+ }
+}
diff --git a/src/BlazorWebFormsComponents.Test/ImageMap/NavigateMode.razor b/src/BlazorWebFormsComponents.Test/ImageMap/NavigateMode.razor
new file mode 100644
index 00000000..2f368158
--- /dev/null
+++ b/src/BlazorWebFormsComponents.Test/ImageMap/NavigateMode.razor
@@ -0,0 +1,65 @@
+@using BlazorWebFormsComponents.Enums
+
+@code {
+ [Fact]
+ public void ImageMap_NavigateMode_RendersHref()
+ {
+ var hotSpots = new List();
+ var rect = new BlazorWebFormsComponents.RectangleHotSpot();
+ rect.Left = 0;
+ rect.Top = 0;
+ rect.Right = 100;
+ rect.Bottom = 100;
+ rect.AlternateText = "Area 1";
+ rect.NavigateUrl = "/page1.html";
+ rect.HotSpotMode = HotSpotMode.Navigate;
+ hotSpots.Add(rect);
+
+ var cut = Render(@);
+
+ var area = cut.Find("area");
+ area.GetAttribute("href").ShouldBe("/page1.html");
+ area.GetAttribute("alt").ShouldBe("Area 1");
+ }
+
+ [Fact]
+ public void ImageMap_NavigateMode_WithTarget_RendersTargetAttribute()
+ {
+ var hotSpots = new List();
+ var rect = new BlazorWebFormsComponents.RectangleHotSpot();
+ rect.Left = 0;
+ rect.Top = 0;
+ rect.Right = 100;
+ rect.Bottom = 100;
+ rect.AlternateText = "Area 1";
+ rect.NavigateUrl = "/page1.html";
+ rect.HotSpotMode = HotSpotMode.Navigate;
+ rect.Target = "_blank";
+ hotSpots.Add(rect);
+
+ var cut = Render(@);
+
+ var area = cut.Find("area");
+ area.GetAttribute("target").ShouldBe("_blank");
+ }
+
+ [Fact]
+ public void ImageMap_NavigateMode_DefaultTarget_UsesImageMapTarget()
+ {
+ var hotSpots = new List();
+ var rect = new BlazorWebFormsComponents.RectangleHotSpot();
+ rect.Left = 0;
+ rect.Top = 0;
+ rect.Right = 100;
+ rect.Bottom = 100;
+ rect.AlternateText = "Area 1";
+ rect.NavigateUrl = "/page1.html";
+ rect.HotSpotMode = HotSpotMode.Navigate;
+ hotSpots.Add(rect);
+
+ var cut = Render(@);
+
+ var area = cut.Find("area");
+ area.GetAttribute("target").ShouldBe("_self");
+ }
+}
diff --git a/src/BlazorWebFormsComponents.Test/ImageMap/PolygonHotSpot.razor b/src/BlazorWebFormsComponents.Test/ImageMap/PolygonHotSpot.razor
new file mode 100644
index 00000000..806660a1
--- /dev/null
+++ b/src/BlazorWebFormsComponents.Test/ImageMap/PolygonHotSpot.razor
@@ -0,0 +1,39 @@
+@using BlazorWebFormsComponents.Enums
+
+@code {
+ [Fact]
+ public void ImageMap_PolygonHotSpot_RendersCorrectly()
+ {
+ var hotSpots = new List();
+ var polygon = new BlazorWebFormsComponents.PolygonHotSpot();
+ polygon.Coordinates = "10,10,50,10,50,50,10,50";
+ polygon.AlternateText = "Polygon Area";
+ polygon.NavigateUrl = "/page1.html";
+ hotSpots.Add(polygon);
+
+ var cut = Render(@);
+
+ var area = cut.Find("area");
+ area.GetAttribute("shape").ShouldBe("poly");
+ area.GetAttribute("coords").ShouldBe("10,10,50,10,50,50,10,50");
+ area.GetAttribute("alt").ShouldBe("Polygon Area");
+ area.GetAttribute("href").ShouldBe("/page1.html");
+ }
+
+ [Fact]
+ public void ImageMap_ComplexPolygonHotSpot_RendersCorrectly()
+ {
+ var hotSpots = new List();
+ var polygon = new BlazorWebFormsComponents.PolygonHotSpot();
+ polygon.Coordinates = "100,50,120,80,110,120,80,120,70,80,90,50";
+ polygon.AlternateText = "Star Shape";
+ polygon.NavigateUrl = "/star.html";
+ hotSpots.Add(polygon);
+
+ var cut = Render(@);
+
+ var area = cut.Find("area");
+ area.GetAttribute("shape").ShouldBe("poly");
+ area.GetAttribute("coords").ShouldBe("100,50,120,80,110,120,80,120,70,80,90,50");
+ }
+}
diff --git a/src/BlazorWebFormsComponents.Test/ImageMap/PostBackMode.razor b/src/BlazorWebFormsComponents.Test/ImageMap/PostBackMode.razor
new file mode 100644
index 00000000..162601a7
--- /dev/null
+++ b/src/BlazorWebFormsComponents.Test/ImageMap/PostBackMode.razor
@@ -0,0 +1,90 @@
+@using BlazorWebFormsComponents.Enums
+
+@code {
+ [Fact]
+ public void ImageMap_PostBackMode_InvokesClickEvent()
+ {
+ var clickedValue = string.Empty;
+
+ var hotSpots = new List();
+ var rect = new BlazorWebFormsComponents.RectangleHotSpot();
+ rect.Left = 0;
+ rect.Top = 0;
+ rect.Right = 100;
+ rect.Bottom = 100;
+ rect.AlternateText = "Area 1";
+ rect.PostBackValue = "Area1Clicked";
+ rect.HotSpotMode = HotSpotMode.PostBack;
+ hotSpots.Add(rect);
+
+ var cut = Render(@);
+
+ var area = cut.Find("area");
+ area.Click();
+
+ clickedValue.ShouldBe("Area1Clicked");
+ }
+
+ [Fact]
+ public void ImageMap_PostBackMode_MultipleHotSpots_InvokesCorrectEvent()
+ {
+ var clickedValue = string.Empty;
+
+ var hotSpots = new List();
+
+ var rect = new BlazorWebFormsComponents.RectangleHotSpot();
+ rect.Left = 0;
+ rect.Top = 0;
+ rect.Right = 50;
+ rect.Bottom = 50;
+ rect.AlternateText = "Area 1";
+ rect.PostBackValue = "Area1";
+ rect.HotSpotMode = HotSpotMode.PostBack;
+ hotSpots.Add(rect);
+
+ var circle = new BlazorWebFormsComponents.CircleHotSpot();
+ circle.X = 100;
+ circle.Y = 100;
+ circle.Radius = 30;
+ circle.AlternateText = "Area 2";
+ circle.PostBackValue = "Area2";
+ circle.HotSpotMode = HotSpotMode.PostBack;
+ hotSpots.Add(circle);
+
+ var cut = Render(@);
+
+ // Click first area
+ var areas1 = cut.FindAll("area");
+ areas1[0].Click();
+ clickedValue.ShouldBe("Area1");
+
+ // Re-find areas after render and click second area
+ var areas2 = cut.FindAll("area");
+ areas2[1].Click();
+ clickedValue.ShouldBe("Area2");
+ }
+
+ [Fact]
+ public void ImageMap_PostBackMode_RendersHrefHash()
+ {
+ var hotSpots = new List();
+ var rect = new BlazorWebFormsComponents.RectangleHotSpot();
+ rect.Left = 0;
+ rect.Top = 0;
+ rect.Right = 100;
+ rect.Bottom = 100;
+ rect.AlternateText = "Area 1";
+ rect.PostBackValue = "Area1";
+ rect.HotSpotMode = HotSpotMode.PostBack;
+ hotSpots.Add(rect);
+
+ var cut = Render(@);
+
+ var area = cut.Find("area");
+ area.GetAttribute("href").ShouldBe("#");
+ }
+}
diff --git a/src/BlazorWebFormsComponents.Test/ImageMap/Properties.razor b/src/BlazorWebFormsComponents.Test/ImageMap/Properties.razor
new file mode 100644
index 00000000..be786e61
--- /dev/null
+++ b/src/BlazorWebFormsComponents.Test/ImageMap/Properties.razor
@@ -0,0 +1,111 @@
+@using BlazorWebFormsComponents.Enums
+
+@code {
+ [Fact]
+ public void ImageMap_WithAlternateText_RendersAltAttribute()
+ {
+ var hotSpots = new List();
+ var rect = new BlazorWebFormsComponents.RectangleHotSpot();
+ rect.Left = 0;
+ rect.Top = 0;
+ rect.Right = 100;
+ rect.Bottom = 100;
+ rect.AlternateText = "Area 1";
+ hotSpots.Add(rect);
+
+ var cut = Render(@);
+
+ var img = cut.Find("img");
+ img.GetAttribute("alt").ShouldBe("Site Map");
+ }
+
+ [Fact]
+ public void ImageMap_WithToolTip_RendersTitleAttribute()
+ {
+ var hotSpots = new List();
+ var rect = new BlazorWebFormsComponents.RectangleHotSpot();
+ rect.Left = 0;
+ rect.Top = 0;
+ rect.Right = 100;
+ rect.Bottom = 100;
+ rect.AlternateText = "Area 1";
+ hotSpots.Add(rect);
+
+ var cut = Render(@);
+
+ var img = cut.Find("img");
+ img.GetAttribute("title").ShouldBe("Click on the map");
+ }
+
+ [Fact]
+ public void ImageMap_WithDescriptionUrl_RendersLongDescAttribute()
+ {
+ var hotSpots = new List();
+ var rect = new BlazorWebFormsComponents.RectangleHotSpot();
+ rect.Left = 0;
+ rect.Top = 0;
+ rect.Right = 100;
+ rect.Bottom = 100;
+ rect.AlternateText = "Area 1";
+ hotSpots.Add(rect);
+
+ var cut = Render(@);
+
+ var img = cut.Find("img");
+ img.GetAttribute("longdesc").ShouldBe("/descriptions/map.html");
+ }
+
+ [Fact]
+ public void ImageMap_GenerateEmptyAlternateText_RendersEmptyAlt()
+ {
+ var hotSpots = new List();
+ var rect = new BlazorWebFormsComponents.RectangleHotSpot();
+ rect.Left = 0;
+ rect.Top = 0;
+ rect.Right = 100;
+ rect.Bottom = 100;
+ rect.AlternateText = "Area 1";
+ hotSpots.Add(rect);
+
+ var cut = Render(@);
+
+ var img = cut.Find("img");
+ img.GetAttribute("alt").ShouldBe("");
+ }
+
+ [Fact]
+ public void ImageMap_WithClientID_RendersIdAttribute()
+ {
+ var hotSpots = new List();
+ var rect = new BlazorWebFormsComponents.RectangleHotSpot();
+ rect.Left = 0;
+ rect.Top = 0;
+ rect.Right = 100;
+ rect.Bottom = 100;
+ rect.AlternateText = "Area 1";
+ hotSpots.Add(rect);
+
+ var cut = Render(@);
+
+ var img = cut.Find("img");
+ img.GetAttribute("id").ShouldBe("myMap");
+ }
+
+ [Fact]
+ public void ImageMap_WithImageAlign_RendersAlignAttribute()
+ {
+ var hotSpots = new List();
+ var rect = new BlazorWebFormsComponents.RectangleHotSpot();
+ rect.Left = 0;
+ rect.Top = 0;
+ rect.Right = 100;
+ rect.Bottom = 100;
+ rect.AlternateText = "Area 1";
+ hotSpots.Add(rect);
+
+ var cut = Render(@);
+
+ var img = cut.Find("img");
+ img.GetAttribute("align").ShouldBe("left");
+ }
+}
diff --git a/src/BlazorWebFormsComponents.Test/ImageMap/RectangleHotSpot.razor b/src/BlazorWebFormsComponents.Test/ImageMap/RectangleHotSpot.razor
new file mode 100644
index 00000000..d70d569a
--- /dev/null
+++ b/src/BlazorWebFormsComponents.Test/ImageMap/RectangleHotSpot.razor
@@ -0,0 +1,66 @@
+@using BlazorWebFormsComponents.Enums
+
+@code {
+ [Fact]
+ public void ImageMap_RectangleHotSpot_RendersCorrectly()
+ {
+ var hotSpots = new List();
+ var rect = new BlazorWebFormsComponents.RectangleHotSpot();
+ rect.Left = 10;
+ rect.Top = 20;
+ rect.Right = 110;
+ rect.Bottom = 120;
+ rect.AlternateText = "Rectangle Area";
+ rect.NavigateUrl = "/page1.html";
+ hotSpots.Add(rect);
+
+ var cut = Render(@);
+
+ var area = cut.Find("area");
+ area.GetAttribute("shape").ShouldBe("rect");
+ area.GetAttribute("coords").ShouldBe("10,20,110,120");
+ area.GetAttribute("alt").ShouldBe("Rectangle Area");
+ area.GetAttribute("href").ShouldBe("/page1.html");
+ }
+
+ [Fact]
+ public void ImageMap_MultipleRectangleHotSpots_RendersAll()
+ {
+ var hotSpots = new List();
+
+ var rect1 = new BlazorWebFormsComponents.RectangleHotSpot();
+ rect1.Left = 0;
+ rect1.Top = 0;
+ rect1.Right = 50;
+ rect1.Bottom = 50;
+ rect1.AlternateText = "Area 1";
+ rect1.NavigateUrl = "/page1.html";
+ hotSpots.Add(rect1);
+
+ var rect2 = new BlazorWebFormsComponents.RectangleHotSpot();
+ rect2.Left = 60;
+ rect2.Top = 60;
+ rect2.Right = 110;
+ rect2.Bottom = 110;
+ rect2.AlternateText = "Area 2";
+ rect2.NavigateUrl = "/page2.html";
+ hotSpots.Add(rect2);
+
+ var rect3 = new BlazorWebFormsComponents.RectangleHotSpot();
+ rect3.Left = 120;
+ rect3.Top = 120;
+ rect3.Right = 170;
+ rect3.Bottom = 170;
+ rect3.AlternateText = "Area 3";
+ rect3.NavigateUrl = "/page3.html";
+ hotSpots.Add(rect3);
+
+ var cut = Render(@);
+
+ var areas = cut.FindAll("area");
+ areas.Count.ShouldBe(3);
+ areas[0].GetAttribute("coords").ShouldBe("0,0,50,50");
+ areas[1].GetAttribute("coords").ShouldBe("60,60,110,110");
+ areas[2].GetAttribute("coords").ShouldBe("120,120,170,170");
+ }
+}
diff --git a/src/BlazorWebFormsComponents.Test/ImageMap/Visible.razor b/src/BlazorWebFormsComponents.Test/ImageMap/Visible.razor
new file mode 100644
index 00000000..7c43f2c9
--- /dev/null
+++ b/src/BlazorWebFormsComponents.Test/ImageMap/Visible.razor
@@ -0,0 +1,42 @@
+@using BlazorWebFormsComponents.Enums
+
+@code {
+ [Fact]
+ public void ImageMap_Visible_True_RendersImageAndMap()
+ {
+ var hotSpots = new List();
+ var rect = new BlazorWebFormsComponents.RectangleHotSpot();
+ rect.Left = 0;
+ rect.Top = 0;
+ rect.Right = 100;
+ rect.Bottom = 100;
+ rect.AlternateText = "Area 1";
+ hotSpots.Add(rect);
+
+ var cut = Render(@);
+
+ // Should render an img element
+ cut.Find("img").GetAttribute("src").ShouldBe("/images/map.jpg");
+
+ // Should render a map element
+ var map = cut.Find("map");
+ map.ShouldNotBeNull();
+ }
+
+ [Fact]
+ public void ImageMap_Visible_False_RendersNothing()
+ {
+ var hotSpots = new List();
+ var rect = new BlazorWebFormsComponents.RectangleHotSpot();
+ rect.Left = 0;
+ rect.Top = 0;
+ rect.Right = 100;
+ rect.Bottom = 100;
+ rect.AlternateText = "Area 1";
+ hotSpots.Add(rect);
+
+ var cut = Render(@);
+
+ cut.Markup.Trim().ShouldBeEmpty();
+ }
+}
diff --git a/src/BlazorWebFormsComponents/CircleHotSpot.cs b/src/BlazorWebFormsComponents/CircleHotSpot.cs
new file mode 100644
index 00000000..fcd3464e
--- /dev/null
+++ b/src/BlazorWebFormsComponents/CircleHotSpot.cs
@@ -0,0 +1,35 @@
+namespace BlazorWebFormsComponents
+{
+ ///
+ /// Defines a circular hot spot region in an ImageMap control.
+ ///
+ public class CircleHotSpot : HotSpot
+ {
+ ///
+ /// Gets or sets the x-coordinate of the center of the circular region defined by this CircleHotSpot object.
+ ///
+ public int X { get; set; }
+
+ ///
+ /// Gets or sets the y-coordinate of the center of the circular region defined by this CircleHotSpot object.
+ ///
+ public int Y { get; set; }
+
+ ///
+ /// Gets or sets the distance from the center to the edge of the circular region defined by this CircleHotSpot object.
+ ///
+ public int Radius { get; set; }
+
+ ///
+ /// Gets the shape type for this hot spot.
+ ///
+ /// "circle"
+ public override string GetShapeType() => "circle";
+
+ ///
+ /// Gets the coordinates for this circular hot spot.
+ ///
+ /// A comma-separated string of coordinates in format: x,y,radius
+ public override string GetCoordinates() => $"{X},{Y},{Radius}";
+ }
+}
diff --git a/src/BlazorWebFormsComponents/Enums/HotSpotMode.cs b/src/BlazorWebFormsComponents/Enums/HotSpotMode.cs
new file mode 100644
index 00000000..18570dbd
--- /dev/null
+++ b/src/BlazorWebFormsComponents/Enums/HotSpotMode.cs
@@ -0,0 +1,28 @@
+namespace BlazorWebFormsComponents.Enums
+{
+ ///
+ /// Specifies the behaviors of a HotSpot object in an ImageMap control when the HotSpot is clicked.
+ ///
+ public enum HotSpotMode
+ {
+ ///
+ /// The HotSpot mode is not set and inherits from the parent ImageMap control.
+ ///
+ NotSet,
+
+ ///
+ /// The HotSpot does not have any behavior.
+ ///
+ Inactive,
+
+ ///
+ /// The HotSpot navigates to a URL.
+ ///
+ Navigate,
+
+ ///
+ /// The HotSpot generates a postback to the server.
+ ///
+ PostBack
+ }
+}
diff --git a/src/BlazorWebFormsComponents/HotSpot.cs b/src/BlazorWebFormsComponents/HotSpot.cs
new file mode 100644
index 00000000..8ba745b1
--- /dev/null
+++ b/src/BlazorWebFormsComponents/HotSpot.cs
@@ -0,0 +1,57 @@
+using BlazorWebFormsComponents.Enums;
+
+namespace BlazorWebFormsComponents
+{
+ ///
+ /// Implements the basic functionality common to all hot spot shapes.
+ ///
+ public abstract class HotSpot
+ {
+ ///
+ /// Gets or sets the alternate text to display for a HotSpot object in an ImageMap control when the image is unavailable or renders to a browser that does not support images.
+ ///
+ public string AlternateText { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the behavior of a HotSpot object in an ImageMap control when the HotSpot is clicked.
+ ///
+ public HotSpotMode HotSpotMode { get; set; } = HotSpotMode.NotSet;
+
+ ///
+ /// Gets or sets the URL to navigate to when a HotSpot object is clicked.
+ ///
+ public string NavigateUrl { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the name of the HotSpot object to pass in the event data when the HotSpot is clicked.
+ ///
+ public string PostBackValue { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the target window or frame in which to display the Web page content linked to when the HotSpot object is clicked.
+ ///
+ public string Target { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the tab index of the HotSpot object.
+ ///
+ public short TabIndex { get; set; }
+
+ ///
+ /// Gets or sets the AccessKey that allows you to quickly navigate to the HotSpot object.
+ ///
+ public string AccessKey { get; set; } = string.Empty;
+
+ ///
+ /// Gets the shape type for this hot spot.
+ ///
+ /// The shape type (rect, circle, or poly)
+ public abstract string GetShapeType();
+
+ ///
+ /// Gets the coordinates for this hot spot as a comma-separated string.
+ ///
+ /// The coordinates string
+ public abstract string GetCoordinates();
+ }
+}
diff --git a/src/BlazorWebFormsComponents/ImageMap.razor b/src/BlazorWebFormsComponents/ImageMap.razor
new file mode 100644
index 00000000..ebce4662
--- /dev/null
+++ b/src/BlazorWebFormsComponents/ImageMap.razor
@@ -0,0 +1,105 @@
+@using System.Text
+@inherits BaseStyledComponent
+
+@if (Visible)
+{
+
+
+
+}
+
+@code {
+ private string GetAltText()
+ {
+ if (!string.IsNullOrEmpty(AlternateText))
+ return AlternateText;
+ if (GenerateEmptyAlternateText)
+ return "";
+ return null;
+ }
+
+ private string GetId()
+ {
+ return !string.IsNullOrEmpty(ClientID) ? ClientID : null;
+ }
+
+ private string GetCssClassOrNull()
+ {
+ return !string.IsNullOrEmpty(CssClass) ? CssClass : null;
+ }
+
+ private string GetTitle()
+ {
+ return !string.IsNullOrEmpty(ToolTip) ? ToolTip : null;
+ }
+
+ private string GetLongDesc()
+ {
+ return !string.IsNullOrEmpty(DescriptionUrl) ? DescriptionUrl : null;
+ }
+
+ private string GetAlign()
+ {
+ return ImageAlign != Enums.ImageAlign.NotSet ? ImageAlign.ToString().ToLower() : null;
+ }
+
+ private string GetTargetOrNull(string target)
+ {
+ return !string.IsNullOrEmpty(target) ? target : null;
+ }
+
+ private string GetTabIndexOrNull(short tabIndex)
+ {
+ return tabIndex > 0 ? tabIndex.ToString() : null;
+ }
+
+ private string GetAccessKeyOrNull(string accessKey)
+ {
+ return !string.IsNullOrEmpty(accessKey) ? accessKey : null;
+ }
+}
diff --git a/src/BlazorWebFormsComponents/ImageMap.razor.cs b/src/BlazorWebFormsComponents/ImageMap.razor.cs
new file mode 100644
index 00000000..a02304d7
--- /dev/null
+++ b/src/BlazorWebFormsComponents/ImageMap.razor.cs
@@ -0,0 +1,97 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using BlazorWebFormsComponents.Enums;
+using BlazorWebFormsComponents.Interfaces;
+using Microsoft.AspNetCore.Components;
+
+namespace BlazorWebFormsComponents
+{
+ ///
+ /// Creates an image map control that displays an image with defined clickable hot spot regions.
+ ///
+ public partial class ImageMap : BaseStyledComponent, IImageComponent
+ {
+ ///
+ /// Gets or sets the alternate text to display in the ImageMap control when the image is unavailable.
+ ///
+ [Parameter]
+ public string AlternateText { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the URL to a detailed description of the image in the ImageMap control.
+ ///
+ [Parameter]
+ public string DescriptionUrl { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the alignment of the Image control in relation to other elements on the Web page.
+ ///
+ [Parameter]
+ public ImageAlign ImageAlign { get; set; } = ImageAlign.NotSet;
+
+ ///
+ /// Gets or sets the URL to the image to display in the ImageMap control.
+ ///
+ [Parameter]
+ public string ImageUrl { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the ToolTip text for the ImageMap control.
+ ///
+ [Parameter]
+ public string ToolTip { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the default behavior for the HotSpot objects in the ImageMap control when the HotSpot objects are clicked.
+ ///
+ [Parameter]
+ public HotSpotMode HotSpotMode { get; set; } = HotSpotMode.Navigate;
+
+ ///
+ /// Gets or sets the target window or frame in which to display the Web page content linked to when a HotSpot object in an ImageMap control is clicked.
+ ///
+ [Parameter]
+ public string Target { get; set; } = string.Empty;
+
+ ///
+ /// Gets the collection of HotSpot objects defined in the ImageMap control.
+ ///
+ [Parameter]
+ public List HotSpots { get; set; } = new List();
+
+ ///
+ /// Occurs when a HotSpot object in an ImageMap control is clicked.
+ ///
+ [Parameter]
+ public EventCallback OnClick { get; set; }
+
+ ///
+ /// Gets or sets a value that indicates whether the ImageMap control generates an empty alternate text attribute.
+ ///
+ [Parameter]
+ public bool GenerateEmptyAlternateText { get; set; }
+
+ private readonly string _mapId = $"ImageMap_{Guid.NewGuid():N}";
+
+ protected override void OnInitialized()
+ {
+ base.OnInitialized();
+ }
+
+ ///
+ /// Handles the click event for a hot spot.
+ ///
+ /// The hot spot that was clicked
+ protected async Task HandleHotSpotClick(HotSpot hotSpot)
+ {
+ var effectiveMode = hotSpot.HotSpotMode != HotSpotMode.NotSet ? hotSpot.HotSpotMode : HotSpotMode;
+
+ if (effectiveMode == HotSpotMode.PostBack)
+ {
+ var eventArgs = new ImageMapEventArgs(hotSpot.PostBackValue);
+ await OnClick.InvokeAsync(eventArgs);
+ }
+ }
+ }
+}
diff --git a/src/BlazorWebFormsComponents/ImageMapEventArgs.cs b/src/BlazorWebFormsComponents/ImageMapEventArgs.cs
new file mode 100644
index 00000000..dcb1d4a0
--- /dev/null
+++ b/src/BlazorWebFormsComponents/ImageMapEventArgs.cs
@@ -0,0 +1,24 @@
+using System;
+
+namespace BlazorWebFormsComponents
+{
+ ///
+ /// Provides data for the Click event of an ImageMap control.
+ ///
+ public class ImageMapEventArgs : EventArgs
+ {
+ ///
+ /// Gets the PostBackValue associated with the HotSpot object in the ImageMap control that was clicked.
+ ///
+ public string PostBackValue { get; }
+
+ ///
+ /// Initializes a new instance of the ImageMapEventArgs class.
+ ///
+ /// The string assigned to the PostBackValue property of the HotSpot object that was clicked.
+ public ImageMapEventArgs(string postBackValue)
+ {
+ PostBackValue = postBackValue;
+ }
+ }
+}
diff --git a/src/BlazorWebFormsComponents/PolygonHotSpot.cs b/src/BlazorWebFormsComponents/PolygonHotSpot.cs
new file mode 100644
index 00000000..4f672795
--- /dev/null
+++ b/src/BlazorWebFormsComponents/PolygonHotSpot.cs
@@ -0,0 +1,25 @@
+namespace BlazorWebFormsComponents
+{
+ ///
+ /// Defines a polygon-shaped hot spot region in an ImageMap control.
+ ///
+ public class PolygonHotSpot : HotSpot
+ {
+ ///
+ /// Gets or sets a string of coordinates that represents the vertexes of a PolygonHotSpot object.
+ ///
+ public string Coordinates { get; set; } = string.Empty;
+
+ ///
+ /// Gets the shape type for this hot spot.
+ ///
+ /// "poly"
+ public override string GetShapeType() => "poly";
+
+ ///
+ /// Gets the coordinates for this polygonal hot spot.
+ ///
+ /// A comma-separated string of x,y coordinate pairs
+ public override string GetCoordinates() => Coordinates;
+ }
+}
diff --git a/src/BlazorWebFormsComponents/RectangleHotSpot.cs b/src/BlazorWebFormsComponents/RectangleHotSpot.cs
new file mode 100644
index 00000000..2409a299
--- /dev/null
+++ b/src/BlazorWebFormsComponents/RectangleHotSpot.cs
@@ -0,0 +1,40 @@
+namespace BlazorWebFormsComponents
+{
+ ///
+ /// Defines a rectangular hot spot region in an ImageMap control.
+ ///
+ public class RectangleHotSpot : HotSpot
+ {
+ ///
+ /// Gets or sets the x-coordinate of the left side of the rectangular region defined by this RectangleHotSpot object.
+ ///
+ public int Left { get; set; }
+
+ ///
+ /// Gets or sets the y-coordinate of the top of the rectangular region defined by this RectangleHotSpot object.
+ ///
+ public int Top { get; set; }
+
+ ///
+ /// Gets or sets the x-coordinate of the right side of the rectangular region defined by this RectangleHotSpot object.
+ ///
+ public int Right { get; set; }
+
+ ///
+ /// Gets or sets the y-coordinate of the bottom of the rectangular region defined by this RectangleHotSpot object.
+ ///
+ public int Bottom { get; set; }
+
+ ///
+ /// Gets the shape type for this hot spot.
+ ///
+ /// "rect"
+ public override string GetShapeType() => "rect";
+
+ ///
+ /// Gets the coordinates for this rectangular hot spot.
+ ///
+ /// A comma-separated string of coordinates in format: left,top,right,bottom
+ public override string GetCoordinates() => $"{Left},{Top},{Right},{Bottom}";
+ }
+}
diff --git a/status.md b/status.md
index 208cd7dc..b5d55381 100644
--- a/status.md
+++ b/status.md
@@ -2,18 +2,18 @@
| Category | Completed | In Progress | Not Started | Total |
|----------|-----------|-------------|-------------|-------|
-| Editor Controls | 17 | 0 | 10 | 27 |
+| Editor Controls | 18 | 0 | 9 | 27 |
| Data Controls | 7 | 0 | 2 | 9 |
| Validation Controls | 7 | 0 | 0 | 7 |
| Navigation Controls | 3 | 0 | 0 | 3 |
| Login Controls | 4 | 0 | 3 | 7 |
-| **TOTAL** | **38** | **0** | **15** | **53** |
+| **TOTAL** | **39** | **0** | **14** | **53** |
---
## Detailed Component Breakdown
-### 🟡 Editor Controls (15/27 - 56% Complete)
+### 🟡 Editor Controls (18/27 - 67% Complete)
| Component | Status | Notes |
|-----------|--------|-------|
@@ -34,7 +34,7 @@
| Calendar | 🔴 Not Started | Complex date picker |
| CheckBoxList | ✅ Complete | Documented, tested (26 tests) |
| FileUpload | 🔴 Not Started | Consider Blazor InputFile |
-| ImageMap | 🔴 Not Started | Clickable image regions |
+| ImageMap | ✅ Complete | Documented, tested (23 tests) |
| ListBox | ✅ Complete | Documented, tested, supports single/multi-select |
| Localize | 🔴 Not Started | Localization control |
| MultiView | 🔴 Not Started | Tab container |
@@ -143,12 +143,12 @@
#### Lower Priority / Consider Deferring
| Component | Complexity | Notes |
|-----------|------------|-------|
-| **BulletedList** | Low | Simple HTML list |
+| ~~**BulletedList**~~ | ~~Low~~ | ~~Simple HTML list~~ | ✅ Complete |
| **Calendar** | High | Complex date picker |
| **FileUpload** | Medium | Blazor has InputFile |
-| **ImageMap** | Medium | Clickable regions |
+| ~~**ImageMap**~~ | ~~Medium~~ | ~~Clickable regions~~ | ✅ Complete |
| **MultiView/View** | Medium | Tab-like container |
-| **Table** | Low | HTML table wrapper |
+| ~~**Table**~~ | ~~Low~~ | ~~HTML table wrapper~~ | ✅ Complete |
| **Localize** | Low | Localization |
| **Xml** | Medium | XML transform |
| **Substitution** | N/A | Cache-related, may not apply |