Repository: radzenhq/radzen-blazor (csharp)

Issue:
RadzenPager breaks WCAG Accessibility when direction arrow links are disabled
<!--

IMPORTANT: Read this first!!!

1. If you own a Radzen Professional or Еnterprise subscription you can report your issue or ask us a question via email at info@radzen.com. Radzen staff will reply within 24 hours (Professional) or 16 hours (Enterprise)
2. The Radzen staff guarantees a response to issues in this repo only to paid subscribers. 
3. If you have a HOW TO question start a new forum thread in the Radzen Community forum: https://forum.radzen.com. Radzen staff will close issues that are HOWTO questions.
4. Please adhere to the issue template. Specify all the steps required to reproduce the issue or link a project which reproduces it easily (without requiring extra steps such as restoring a database).
-->

**Describe the bug**
The RadzenPager component breaks **WCAG SC 2.1.1** when the first, prev, next and last page direction links are disabled / not selectable.

**WCAG SC 2.1.1 (serious):** Ensure that elements with the "role" attribute are focusable to make them keyboard accessible.

This happens when the first page is currently selected (the first, prev links are disabled) or the last page is currently selected (next, prev links are disabled).

Adding the `disabled` attribute will suppress the issue.


**To Reproduce**
Can be seen on the demo site: [https://blazor.radzen.com/datagrid?theme=material3](https://blazor.radzen.com/datagrid?theme=material3)

Steps to reproduce the behavior:
1. Create a RadzenDataGrid with more than 1 page of results.
2. Load the first page of results.
3. The `rz-pager-first` and `rz-pager-prev` links are disabled and cant be tabbed to but there's no `disabled` attribute.

**Expected behavior**
WCAG SC 2.1.1 criteria is met on the page when the links are disabled.

 - All browsers
 - Version 5.0.3

Pull request:
Fixes #1626

Patch:
diff --git a/Radzen.Blazor/RadzenPager.razor b/Radzen.Blazor/RadzenPager.razor
index 1d401b7ad84..ba2ebd3f3d7 100644
--- a/Radzen.Blazor/RadzenPager.razor
+++ b/Radzen.Blazor/RadzenPager.razor
@@ -8,11 +8,11 @@
         {
         <span class="rz-pager-summary">@((MarkupString)string.Format(PagingSummaryFormat, CurrentPage + 1, numberOfPages, Count))</span>        
         }
-        <a d="@(GetId() + "fp")" class="rz-pager-first rz-pager-element @(skip > 0 ? "": "rz-state-disabled") @(focusedIndex == -2 ? "rz-state-focused": "")" @onclick:preventDefault="true" @onclick="@(() => FirstPage())" aria-label="@FirstPageAriaLabel" role="button" title="@FirstPageTitle">
+        <a d="@(GetId() + "fp")" class="rz-pager-first rz-pager-element @(skip > 0 ? "": "rz-state-disabled") @(focusedIndex == -2 ? "rz-state-focused": "")" @onclick:preventDefault="true" @onclick="@(() => FirstPage())" aria-label="@FirstPageAriaLabel" role="button" title="@FirstPageTitle" disabled="@(CurrentPage <= 0)">
             <span class="rz-pager-icon rzi rzi-step-backward"></span>
         </a>
 
-        <a id="@(GetId() + "pp")" class="rz-pager-prev rz-pager-element @(skip > 0 ? "": "rz-state-disabled") @(focusedIndex == -1 ? "rz-state-focused": "")" @onclick:preventDefault="true" @onclick="@(() => PrevPage())" aria-label="@PrevPageAriaLabel" role="button" title="@PrevPageTitle">
+            <a id="@(GetId() + "pp")" class="rz-pager-prev rz-pager-element @(skip > 0 ? "": "rz-state-disabled") @(focusedIndex == -1 ? "rz-state-focused": "")" @onclick:preventDefault="true" @onclick="@(() => PrevPage())" aria-label="@PrevPageAriaLabel" role="button" title="@PrevPageTitle" disabled="@(CurrentPage <= 0)">
             <span class="rz-pager-icon rzi rzi-caret-left"></span>
             @if (PrevPageLabel != null)
             {
@@ -27,7 +27,7 @@
             }
         </span>
 
-        <a id="@(GetId() + "np")" class="rz-pager-next rz-pager-element @((CurrentPage != numberOfPages - 1) ? "": "rz-state-disabled") @(focusedIndex == Math.Min(endPage + 1, PageNumbersCount)? "rz-state-focused": "")" @onclick:preventDefault="true" @onclick="@(() => NextPage())" aria-label="@NextPageAriaLabel" role="button" title="@NextPageTitle">
+        <a id="@(GetId() + "np")" class="rz-pager-next rz-pager-element @((CurrentPage != numberOfPages - 1) ? "": "rz-state-disabled") @(focusedIndex == Math.Min(endPage + 1, PageNumbersCount)? "rz-state-focused": "")" @onclick:preventDefault="true" @onclick="@(() => NextPage())" aria-label="@NextPageAriaLabel" role="button" title="@NextPageTitle" disabled="@(CurrentPage >= (numberOfPages - 1))">
             @if (NextPageLabel != null)
             {
                 <span class="rz-pager-label">@NextPageLabel</span>
@@ -35,7 +35,7 @@
             <span class="rz-pager-icon rzi rzi-caret-right"></span>
         </a>
 
-        <a id="@(GetId() + "lp")" class="rz-pager-last rz-pager-element  @((CurrentPage != numberOfPages - 1) ? "": "rz-state-disabled") @(focusedIndex == Math.Min(endPage + 1, PageNumbersCount) + 1 ? "rz-state-focused": "")" @onclick:preventDefault="true" @onclick="@(() => LastPage())" aria-label="@LastPageAriaLabel" role="button" title="@LastPageTitle">
+                <a id="@(GetId() + "lp")" class="rz-pager-last rz-pager-element  @((CurrentPage != numberOfPages - 1) ? "": "rz-state-disabled") @(focusedIndex == Math.Min(endPage + 1, PageNumbersCount) + 1 ? "rz-state-focused": "")" @onclick:preventDefault="true" @onclick="@(() => LastPage())" aria-label="@LastPageAriaLabel" role="button" title="@LastPageTitle" disabled="@(CurrentPage >= (numberOfPages - 1))">
             <span class="rz-pager-icon rzi rzi-step-forward"></span>
         </a>
 


Test patch:
diff --git a/Radzen.Blazor.Tests/PagerTests.cs b/Radzen.Blazor.Tests/PagerTests.cs
index a306a5e1701..545da936ed4 100644
--- a/Radzen.Blazor.Tests/PagerTests.cs
+++ b/Radzen.Blazor.Tests/PagerTests.cs
@@ -109,5 +109,51 @@ public void RadzenPager_Renders_PagerDensityCompact()
 
             Assert.Contains(@$"rz-density-compact", component.Markup);
         }
+
+        [Fact]
+        public async void RadzenPager_First_And_Prev_Buttons_Are_Disabled_When_On_The_First_Page()
+        {
+            using var ctx = new TestContext();
+            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
+            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
+
+            var component = ctx.RenderComponent<RadzenPager>(parameters => {
+                parameters.Add<int>(p => p.PageSize, 10);
+                parameters.Add<int>(p => p.Count, 100);
+                parameters.Add<bool>(p => p.ShowPagingSummary, true);
+            });
+
+            await component.Instance.GoToPage(0);
+            component.Render();
+
+            var firstPageButton = component.Find("a.rz-pager-first");
+            Assert.True(firstPageButton.HasAttribute("disabled"));
+
+            var prevPageButton = component.Find("a.rz-pager-prev");
+            Assert.True(prevPageButton.HasAttribute("disabled"));
+        }
+
+        [Fact]
+        public async void RadzenPager_Last_And_Next_Buttons_Are_Disabled_When_On_The_Last_Page()
+        {
+            using var ctx = new TestContext();
+            ctx.JSInterop.Mode = JSRuntimeMode.Loose;
+            ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
+
+            var component = ctx.RenderComponent<RadzenPager>(parameters => {
+                parameters.Add<int>(p => p.PageSize, 10);
+                parameters.Add<int>(p => p.Count, 100);
+                parameters.Add<bool>(p => p.ShowPagingSummary, true);
+            });
+
+            await component.Instance.GoToPage(9);
+            component.Render();
+
+            var lastPageButton = component.Find("a.rz-pager-last");
+            Assert.True(lastPageButton.HasAttribute("disabled"));
+
+            var nextPageButton = component.Find("a.rz-pager-next");
+            Assert.True(nextPageButton.HasAttribute("disabled"));
+        }
     }
 }