Merge pull request #2599 from radzenhq/accessibility-fixes

Accessibility fixes
This commit is contained in:
Vladimir Enchev
2026-07-03 12:42:54 +03:00
committed by GitHub
196 changed files with 2100 additions and 734 deletions

View File

@@ -374,6 +374,85 @@ namespace Radzen.Blazor.Tests
Assert.False(input.HasAttribute("aria-activedescendant"));
}
[Fact]
public async Task AutoComplete_AriaExpanded_TracksPopupState()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var data = new List<string> { "Apple", "Banana", "Cherry" };
var component = ctx.RenderComponent<RadzenAutoComplete>(parameters =>
{
parameters
.Add(p => p.Data, data)
.Add(p => p.OpenOnFocus, true);
});
var input = component.Find("input[role=\"combobox\"]");
Assert.Equal("false", input.GetAttribute("aria-expanded"));
await component.InvokeAsync(() => component.Instance.OnPopupOpen());
input = component.Find("input[role=\"combobox\"]");
Assert.Equal("true", input.GetAttribute("aria-expanded"));
await component.InvokeAsync(() => component.Instance.OnPopupClose());
input = component.Find("input[role=\"combobox\"]");
Assert.Equal("false", input.GetAttribute("aria-expanded"));
}
[Fact]
public async Task AutoComplete_AriaExpanded_IsFalse_AfterEscape()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var data = new List<string> { "Apple", "Banana", "Cherry" };
var component = ctx.RenderComponent<RadzenAutoComplete>(parameters =>
{
parameters
.Add(p => p.Data, data)
.Add(p => p.OpenOnFocus, true);
});
await component.InvokeAsync(() => component.Instance.OnPopupOpen());
var input = component.Find("input[role=\"combobox\"]");
Assert.Equal("true", input.GetAttribute("aria-expanded"));
input.KeyDown(new Microsoft.AspNetCore.Components.Web.KeyboardEventArgs { Code = "Escape" });
input = component.Find("input[role=\"combobox\"]");
Assert.Equal("false", input.GetAttribute("aria-expanded"));
}
[Fact]
public void AutoComplete_Sets_AriaSelected_OnHighlightedOption()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.Setup<int>("Radzen.focusListItem", _ => true).SetResult(1);
var data = new List<string> { "Apple", "Banana", "Cherry" };
var component = ctx.RenderComponent<RadzenAutoComplete>(parameters =>
{
parameters
.Add(p => p.Data, data)
.Add(p => p.OpenOnFocus, true);
});
var input = component.Find("input[role=\"combobox\"]");
input.KeyDown(new Microsoft.AspNetCore.Components.Web.KeyboardEventArgs { Code = "ArrowDown" });
var options = component.FindAll("li[role=\"option\"]");
Assert.Equal("false", options[0].GetAttribute("aria-selected"));
Assert.Equal("true", options[1].GetAttribute("aria-selected"));
Assert.Equal("false", options[2].GetAttribute("aria-selected"));
}
private sealed class AutoCompleteWithAccessibleView : RadzenAutoComplete
{
public IEnumerable CurrentView => View;

View File

@@ -193,7 +193,7 @@ namespace Radzen.Blazor.Tests
}
[Fact]
public void BreadCrumb_Item_WithPath_DoesNotRender_AriaCurrentPage()
public void BreadCrumb_NonLastItem_WithPath_DoesNotRender_AriaCurrentPage()
{
using var ctx = new TestContext();
@@ -205,10 +205,69 @@ namespace Radzen.Blazor.Tests
builder.AddAttribute(1, nameof(RadzenBreadCrumbItem.Text), "Home");
builder.AddAttribute(2, nameof(RadzenBreadCrumbItem.Path), "/");
builder.CloseComponent();
builder.OpenComponent<RadzenBreadCrumbItem>(3);
builder.AddAttribute(4, nameof(RadzenBreadCrumbItem.Text), "Products");
builder.AddAttribute(5, nameof(RadzenBreadCrumbItem.Path), "/products");
builder.CloseComponent();
});
});
Assert.DoesNotContain("aria-current", component.Markup);
var items = component.FindAll("li.rz-breadcrumb-item");
Assert.Equal(2, items.Count);
Assert.Null(items[0].GetAttribute("aria-current"));
}
[Fact]
public void BreadCrumb_LastItem_WithPath_Renders_AriaCurrentPage()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenBreadCrumb>(parameters =>
{
parameters.Add(c => c.ChildContent, builder =>
{
builder.OpenComponent<RadzenBreadCrumbItem>(0);
builder.AddAttribute(1, nameof(RadzenBreadCrumbItem.Text), "Home");
builder.AddAttribute(2, nameof(RadzenBreadCrumbItem.Path), "/");
builder.CloseComponent();
builder.OpenComponent<RadzenBreadCrumbItem>(3);
builder.AddAttribute(4, nameof(RadzenBreadCrumbItem.Text), "Products");
builder.AddAttribute(5, nameof(RadzenBreadCrumbItem.Path), "/products");
builder.CloseComponent();
});
});
var items = component.FindAll("li.rz-breadcrumb-item");
Assert.Equal("page", items[1].GetAttribute("aria-current"));
}
[Fact]
public void BreadCrumb_Renders_OrderedList_WithListItems()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenBreadCrumb>(parameters =>
{
parameters.Add(c => c.ChildContent, builder =>
{
builder.OpenComponent<RadzenBreadCrumbItem>(0);
builder.AddAttribute(1, nameof(RadzenBreadCrumbItem.Text), "Home");
builder.AddAttribute(2, nameof(RadzenBreadCrumbItem.Path), "/");
builder.CloseComponent();
builder.OpenComponent<RadzenBreadCrumbItem>(3);
builder.AddAttribute(4, nameof(RadzenBreadCrumbItem.Text), "Products");
builder.CloseComponent();
});
});
var list = component.Find("nav > ol.rz-breadcrumb-list");
Assert.Equal(2, list.QuerySelectorAll("li.rz-breadcrumb-item").Length);
}
}
}

View File

@@ -1,4 +1,4 @@
using Bunit;
using Bunit;
using Xunit;
namespace Radzen.Blazor.Tests
@@ -30,7 +30,7 @@ namespace Radzen.Blazor.Tests
component.SetParametersAndRender(parameters => parameters.Add(p => p.Icon, icon));
Assert.Contains(@$"<i class=""notranslate rz-button-icon-left rzi"">{icon}</i>", component.Markup);
Assert.Contains(@$"<i class=""notranslate rz-button-icon-left rzi"" aria-hidden=""true"">{icon}</i>", component.Markup);
}
[Fact]
@@ -48,7 +48,7 @@ namespace Radzen.Blazor.Tests
);
// does not render the actual icon when busy
Assert.DoesNotContain(@$"<i class=""notranslate rz-button-icon-left rzi"">{icon}</i>", component.Markup);
Assert.DoesNotContain(@$"<i class=""notranslate rz-button-icon-left rzi"" aria-hidden=""true"">{icon}</i>", component.Markup);
// renders the icon with busy spin animation
Assert.Contains(@"<i style=""animation: rotation", component.Markup);
@@ -71,7 +71,7 @@ namespace Radzen.Blazor.Tests
parameters.Add(p => p.Icon, icon);
});
Assert.Contains(@$"<i class=""notranslate rz-button-icon-left rzi"">{icon}</i>", component.Markup);
Assert.Contains(@$"<i class=""notranslate rz-button-icon-left rzi"" aria-hidden=""true"">{icon}</i>", component.Markup);
Assert.Contains(@$"<span class=""rz-button-text"">{text}</span>", component.Markup);
}

View File

@@ -3444,7 +3444,7 @@ namespace Radzen.Blazor.Tests
var wrapper = component.Find("div.rz-data-grid");
Assert.Equal("grid", wrapper.GetAttribute("role"));
Assert.Equal("0", wrapper.GetAttribute("tabindex"));
Assert.False(string.IsNullOrEmpty(wrapper.GetAttribute("aria-activedescendant")));
Assert.True(string.IsNullOrEmpty(wrapper.GetAttribute("aria-activedescendant")));
}
[Fact]
@@ -3467,7 +3467,7 @@ namespace Radzen.Blazor.Tests
var instance = component.Instance;
Assert.False(string.IsNullOrEmpty(instance.GetActiveDescendantId()));
Assert.Null(instance.GetActiveDescendantId());
instance.HasActiveDescendant = () => false;
Assert.Null(instance.GetActiveDescendantId());
@@ -4083,7 +4083,7 @@ namespace Radzen.Blazor.Tests
Assert.DoesNotContain("2 columns showing", label.TextContent);
Assert.Equal("Name", label.TextContent.Trim());
Assert.Equal("none", component.Find(".rz-column-picker th.rz-sortable-column").GetAttribute("aria-sort"));
Assert.Null(component.Find(".rz-column-picker th.rz-sortable-column").GetAttribute("aria-sort"));
component.Find(".rz-column-picker th.rz-sortable-column > div").Click();
Assert.Equal("ascending", component.Find(".rz-column-picker th.rz-sortable-column").GetAttribute("aria-sort"));
}

View File

@@ -119,9 +119,11 @@ namespace Radzen.Blazor.Tests
});
var searchInput = component.Find("input.rz-lookup-search-input");
var grid = component.Find("div[role='grid']");
Assert.Equal("combobox", searchInput.GetAttribute("role"));
Assert.NotNull(searchInput.GetAttribute("aria-controls"));
Assert.Equal(grid.Id, searchInput.GetAttribute("aria-controls"));
Assert.Equal("grid", searchInput.GetAttribute("aria-haspopup"));
Assert.Equal("list", searchInput.GetAttribute("aria-autocomplete"));
Assert.NotNull(searchInput.GetAttribute("aria-expanded"));
}

View File

@@ -974,6 +974,106 @@ namespace Radzen.Blazor.Tests
Assert.Equal("Filter by status", combobox.GetAttribute("aria-label"));
}
[Fact]
public void DropDown_Multiple_AriaLabel_ReflectsSelectedItems()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var data = new[]
{
new DataItem { Text = "Item 1", Id = 1 },
new DataItem { Text = "Item 2", Id = 2 },
new DataItem { Text = "Item 3", Id = 3 },
};
var component = ctx.RenderComponent<RadzenDropDown<IEnumerable<int>>>(parameters =>
{
parameters.Add(p => p.Data, data);
parameters.Add(p => p.TextProperty, nameof(DataItem.Text));
parameters.Add(p => p.ValueProperty, nameof(DataItem.Id));
parameters.Add(p => p.Multiple, true);
parameters.Add(p => p.Value, new List<int> { 1, 3 });
});
var combobox = component.Find("div[role='combobox']");
Assert.Equal("Item 1,Item 3", combobox.GetAttribute("aria-label"));
}
[Fact]
public void DropDown_Multiple_AriaLabel_ShowsCount_WhenAboveMaxSelectedLabels()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var data = new[]
{
new DataItem { Text = "Item 1", Id = 1 },
new DataItem { Text = "Item 2", Id = 2 },
new DataItem { Text = "Item 3", Id = 3 },
};
var component = ctx.RenderComponent<RadzenDropDown<IEnumerable<int>>>(parameters =>
{
parameters.Add(p => p.Data, data);
parameters.Add(p => p.TextProperty, nameof(DataItem.Text));
parameters.Add(p => p.ValueProperty, nameof(DataItem.Id));
parameters.Add(p => p.Multiple, true);
parameters.Add(p => p.MaxSelectedLabels, 2);
parameters.Add(p => p.Value, new List<int> { 1, 2, 3 });
});
var combobox = component.Find("div[role='combobox']");
Assert.Equal($"3 {component.Instance.SelectedItemsText}", combobox.GetAttribute("aria-label"));
}
[Fact]
public void DropDown_Chips_RemoveButtons_AreNotTabbable()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var data = new[]
{
new DataItem { Text = "Item 1", Id = 1 },
new DataItem { Text = "Item 2", Id = 2 },
};
var component = ctx.RenderComponent<RadzenDropDown<IEnumerable<int>>>(parameters =>
{
parameters.Add(p => p.Data, data);
parameters.Add(p => p.TextProperty, nameof(DataItem.Text));
parameters.Add(p => p.ValueProperty, nameof(DataItem.Id));
parameters.Add(p => p.Multiple, true);
parameters.Add(p => p.Chips, true);
parameters.Add(p => p.Value, new List<int> { 1, 2 });
});
var buttons = component.FindAll(".rz-chip button");
Assert.NotEmpty(buttons);
Assert.All(buttons, b => Assert.Equal("-1", b.GetAttribute("tabindex")));
}
[Fact]
public void DropDown_HiddenHelperInput_IsHiddenFromAccessibilityTree()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = DropDown<int>(ctx, parameters =>
{
parameters.Add(p => p.ValueProperty, nameof(DataItem.Id));
});
var hiddenInput = component.Find(".rz-helper-hidden-accessible input");
Assert.Equal("true", hiddenInput.GetAttribute("aria-hidden"));
Assert.Equal("-1", hiddenInput.GetAttribute("tabindex"));
}
[Fact]
public void DropDown_WithoutConsumerAriaLabel_FallsBackToComputedAriaLabel()
{

View File

@@ -271,7 +271,7 @@ namespace Radzen.Blazor.Tests
parameters.Add(p => p.Data, new List<string> { "Apple", "Banana" });
});
var listbox = component.Find("div[role=\"listbox\"]");
var listbox = component.Find("ul[role=\"listbox\"]");
Assert.NotNull(listbox.GetAttribute("tabindex"));
Assert.Equal("false", listbox.GetAttribute("aria-multiselectable"));
@@ -289,7 +289,7 @@ namespace Radzen.Blazor.Tests
parameters.Add(p => p.EmptyAriaLabel, "Fruits");
});
var listbox = component.Find("div[role=\"listbox\"]");
var listbox = component.Find("ul[role=\"listbox\"]");
Assert.Equal("Fruits", listbox.GetAttribute("aria-label"));
}
@@ -306,7 +306,7 @@ namespace Radzen.Blazor.Tests
parameters.Add(p => p.Data, new List<string> { "Apple", "Banana" });
});
var listbox = component.Find("div[role=\"listbox\"]");
var listbox = component.Find("ul[role=\"listbox\"]");
Assert.Equal("true", listbox.GetAttribute("aria-multiselectable"));
}
@@ -366,13 +366,13 @@ namespace Radzen.Blazor.Tests
parameters.Add(p => p.Data, new List<string> { "Apple", "Banana", "Cherry" });
});
var listbox = component.Find("div[role=\"listbox\"]");
var listbox = component.Find("ul[role=\"listbox\"]");
Assert.True(string.IsNullOrEmpty(listbox.GetAttribute("aria-activedescendant")));
listbox.KeyDown(new KeyboardEventArgs { Key = "ArrowDown" });
listbox = component.Find("div[role=\"listbox\"]");
listbox = component.Find("ul[role=\"listbox\"]");
var active = listbox.GetAttribute("aria-activedescendant");
Assert.False(string.IsNullOrEmpty(active));

View File

@@ -330,7 +330,7 @@ namespace Radzen.Blazor.Tests
var toggle = component.Find("div.rz-navigation-item-wrapper[role=button]");
toggle.KeyDown(new KeyboardEventArgs { Code = "ArrowDown" });
var activeId = component.Find("div.rz-navigation-item-wrapper[role=button]").GetAttribute("aria-activedescendant");
var activeId = component.Find("ul.rz-navigation-menu").GetAttribute("aria-activedescendant");
Assert.False(string.IsNullOrEmpty(activeId));
@@ -364,7 +364,7 @@ namespace Radzen.Blazor.Tests
Assert.All(component.FindAll("li[role=menuitem]"), item => Assert.Equal("-1", item.GetAttribute("tabindex")));
var activeId = toggle.GetAttribute("aria-activedescendant");
var activeId = component.Find("ul.rz-navigation-menu").GetAttribute("aria-activedescendant");
Assert.False(string.IsNullOrEmpty(activeId));
var focusedItems = component.FindAll("li[role=menuitem].rz-state-focused");
@@ -395,7 +395,7 @@ namespace Radzen.Blazor.Tests
Assert.Equal(1, component.Instance.focusedIndex);
var activeId = component.Find("div.rz-navigation-item-wrapper[role=button]").GetAttribute("aria-activedescendant");
var activeId = component.Find("ul.rz-navigation-menu").GetAttribute("aria-activedescendant");
var lastItem = component.FindAll("li[role=menuitem]")[1];
Assert.Equal(lastItem.Id, activeId);
@@ -449,7 +449,7 @@ namespace Radzen.Blazor.Tests
Assert.Equal(-1, component.Instance.focusedIndex);
Assert.True(string.IsNullOrEmpty(component.Find("div.rz-navigation-item-wrapper[role=button]").GetAttribute("aria-activedescendant")));
Assert.True(string.IsNullOrEmpty(component.Find("ul.rz-navigation-menu").GetAttribute("aria-activedescendant")));
var menu = component.Find("ul.rz-navigation-menu");
Assert.Equal("true", menu.GetAttribute("aria-hidden"));

View File

@@ -193,6 +193,68 @@ namespace Radzen.Blazor.Tests
Assert.DoesNotContain("role=\"radiogroup\"", component.Markup);
}
[Fact]
public void SelectBar_SingleSelect_ArrowKeys_SelectFocusedItem_AndWrap()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenSelectBar<int>>(parameters =>
{
parameters.Add(p => p.Value, 1);
parameters.Add(p => p.Items, builder =>
{
builder.OpenComponent<RadzenSelectBarItem>(0);
builder.AddAttribute(1, "Text", "First");
builder.AddAttribute(2, "Value", 1);
builder.CloseComponent();
builder.OpenComponent<RadzenSelectBarItem>(3);
builder.AddAttribute(4, "Text", "Second");
builder.AddAttribute(5, "Value", 2);
builder.CloseComponent();
});
});
var radiogroup = component.Find("div[role=\"radiogroup\"]");
radiogroup.KeyDown(new Microsoft.AspNetCore.Components.Web.KeyboardEventArgs { Code = "ArrowRight" });
Assert.Equal(2, component.Instance.Value);
radiogroup = component.Find("div[role=\"radiogroup\"]");
radiogroup.KeyDown(new Microsoft.AspNetCore.Components.Web.KeyboardEventArgs { Code = "ArrowRight" });
Assert.Equal(1, component.Instance.Value);
}
[Fact]
public void SelectBar_Multiple_ArrowKeys_DoNotToggleSelection()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenSelectBar<System.Collections.Generic.IEnumerable<int>>>(parameters =>
{
parameters.Add(p => p.Multiple, true);
parameters.Add(p => p.Value, new System.Collections.Generic.List<int> { 1 });
parameters.Add(p => p.Items, builder =>
{
builder.OpenComponent<RadzenSelectBarItem>(0);
builder.AddAttribute(1, "Text", "First");
builder.AddAttribute(2, "Value", 1);
builder.CloseComponent();
builder.OpenComponent<RadzenSelectBarItem>(3);
builder.AddAttribute(4, "Text", "Second");
builder.AddAttribute(5, "Value", 2);
builder.CloseComponent();
});
});
var toolbar = component.Find("div[role=\"toolbar\"]");
toolbar.KeyDown(new Microsoft.AspNetCore.Components.Web.KeyboardEventArgs { Code = "ArrowRight" });
Assert.Equal(new[] { 1 }, System.Linq.Enumerable.ToArray(component.Instance.Value));
}
[Fact]
public void SelectBar_Renders_AriaLabelledBy()
{

View File

@@ -1,4 +1,4 @@
using Bunit;
using Bunit;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Xunit;
@@ -32,34 +32,34 @@ namespace Radzen.Blazor.Tests
}
[Fact]
public void SplitButton_Renders_Wrapper_AriaHasPopupAndExpanded()
public void SplitButton_Renders_ToggleButton_AriaHasPopupAndExpanded()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = RenderWithItems(ctx);
var wrapper = component.Find("div.rz-splitbutton");
var toggle = component.Find("button.rz-splitbutton-menubutton");
Assert.Equal("menu", wrapper.GetAttribute("aria-haspopup"));
Assert.Equal("false", wrapper.GetAttribute("aria-expanded"));
Assert.Equal("menu", toggle.GetAttribute("aria-haspopup"));
Assert.Equal("false", toggle.GetAttribute("aria-expanded"));
}
[Fact]
public void SplitButton_AltArrowDown_OpensMenu_SetsAriaExpandedAndActiveDescendant()
public void SplitButton_ArrowDown_OpensMenu_SetsAriaExpandedAndActiveDescendant()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = RenderWithItems(ctx);
component.Find("div.rz-splitbutton").KeyDown(new KeyboardEventArgs { Code = "ArrowDown", AltKey = true });
component.Find("button.rz-splitbutton-menubutton").KeyDown(new KeyboardEventArgs { Code = "ArrowDown" });
var wrapper = component.Find("div.rz-splitbutton");
var toggle = component.Find("button.rz-splitbutton-menubutton");
Assert.Equal("true", wrapper.GetAttribute("aria-expanded"));
Assert.Equal("true", toggle.GetAttribute("aria-expanded"));
var activeDescendant = wrapper.GetAttribute("aria-activedescendant");
var activeDescendant = component.Find(@"ul[role=""menu""]").GetAttribute("aria-activedescendant");
Assert.False(string.IsNullOrEmpty(activeDescendant));
var firstItem = component.FindAll(@"li[role=""menuitem""]")[0];
@@ -74,12 +74,11 @@ namespace Radzen.Blazor.Tests
var component = RenderWithItems(ctx);
var wrapper = component.Find("div.rz-splitbutton");
wrapper.KeyDown(new KeyboardEventArgs { Code = "ArrowDown", AltKey = true });
component.Find("div.rz-splitbutton").KeyDown(new KeyboardEventArgs { Code = "ArrowDown" });
component.Find("button.rz-splitbutton-menubutton").KeyDown(new KeyboardEventArgs { Code = "ArrowDown" });
component.Find(@"ul[role=""menu""]").KeyDown(new KeyboardEventArgs { Code = "ArrowDown" });
var items = component.FindAll(@"li[role=""menuitem""]");
var activeDescendant = component.Find("div.rz-splitbutton").GetAttribute("aria-activedescendant");
var activeDescendant = component.Find(@"ul[role=""menu""]").GetAttribute("aria-activedescendant");
Assert.Equal(items[1].Id, activeDescendant);
Assert.Equal("-1", items[0].GetAttribute("tabindex"));
@@ -94,11 +93,11 @@ namespace Radzen.Blazor.Tests
var component = RenderWithItems(ctx);
component.Find("div.rz-splitbutton").KeyDown(new KeyboardEventArgs { Code = "ArrowDown", AltKey = true });
component.Find("div.rz-splitbutton").KeyDown(new KeyboardEventArgs { Code = "End" });
component.Find("button.rz-splitbutton-menubutton").KeyDown(new KeyboardEventArgs { Code = "ArrowDown" });
component.Find(@"ul[role=""menu""]").KeyDown(new KeyboardEventArgs { Code = "End" });
var items = component.FindAll(@"li[role=""menuitem""]");
var activeDescendant = component.Find("div.rz-splitbutton").GetAttribute("aria-activedescendant");
var activeDescendant = component.Find(@"ul[role=""menu""]").GetAttribute("aria-activedescendant");
Assert.Equal(items[items.Count - 1].Id, activeDescendant);
}
@@ -111,16 +110,32 @@ namespace Radzen.Blazor.Tests
var component = RenderWithItems(ctx);
component.Find("div.rz-splitbutton").KeyDown(new KeyboardEventArgs { Code = "ArrowDown", AltKey = true });
component.Find("div.rz-splitbutton").KeyDown(new KeyboardEventArgs { Code = "End" });
component.Find("div.rz-splitbutton").KeyDown(new KeyboardEventArgs { Code = "Home" });
component.Find("button.rz-splitbutton-menubutton").KeyDown(new KeyboardEventArgs { Code = "ArrowDown" });
component.Find(@"ul[role=""menu""]").KeyDown(new KeyboardEventArgs { Code = "End" });
component.Find(@"ul[role=""menu""]").KeyDown(new KeyboardEventArgs { Code = "Home" });
var items = component.FindAll(@"li[role=""menuitem""]");
var activeDescendant = component.Find("div.rz-splitbutton").GetAttribute("aria-activedescendant");
var activeDescendant = component.Find(@"ul[role=""menu""]").GetAttribute("aria-activedescendant");
Assert.Equal(items[0].Id, activeDescendant);
}
[Fact]
public void SplitButton_ArrowUp_OpensMenu_FocusesLastItem()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = RenderWithItems(ctx);
component.Find("button.rz-splitbutton-menubutton").KeyDown(new KeyboardEventArgs { Code = "ArrowUp" });
var items = component.FindAll(@"li[role=""menuitem""]");
var activeDescendant = component.Find(@"ul[role=""menu""]").GetAttribute("aria-activedescendant");
Assert.Equal(items[items.Count - 1].Id, activeDescendant);
}
[Fact]
public void SplitButton_Escape_ClosesMenu_ClearsAriaExpandedAndActiveDescendant()
{
@@ -129,13 +144,13 @@ namespace Radzen.Blazor.Tests
var component = RenderWithItems(ctx);
component.Find("div.rz-splitbutton").KeyDown(new KeyboardEventArgs { Code = "ArrowDown", AltKey = true });
Assert.Equal("true", component.Find("div.rz-splitbutton").GetAttribute("aria-expanded"));
component.Find("button.rz-splitbutton-menubutton").KeyDown(new KeyboardEventArgs { Code = "ArrowDown" });
Assert.Equal("true", component.Find("button.rz-splitbutton-menubutton").GetAttribute("aria-expanded"));
component.Find("div.rz-splitbutton").KeyDown(new KeyboardEventArgs { Code = "Escape" });
component.Find(@"ul[role=""menu""]").KeyDown(new KeyboardEventArgs { Code = "Escape" });
Assert.Equal("false", component.Find("div.rz-splitbutton").GetAttribute("aria-expanded"));
Assert.Null(component.Find("div.rz-splitbutton").GetAttribute("aria-activedescendant"));
Assert.Equal("false", component.Find("button.rz-splitbutton-menubutton").GetAttribute("aria-expanded"));
Assert.Null(component.Find(@"ul[role=""menu""]").GetAttribute("aria-activedescendant"));
}
[Fact]
@@ -180,7 +195,7 @@ namespace Radzen.Blazor.Tests
component.SetParametersAndRender(parameters => parameters.Add(p => p.Icon, icon));
Assert.Contains(@$"<i class=""notranslate rz-button-icon-left rzi"">{icon}</i>", component.Markup);
Assert.Contains(@$"<i class=""notranslate rz-button-icon-left rzi"" aria-hidden=""true"">{icon}</i>", component.Markup);
}
[Fact]
@@ -199,7 +214,7 @@ namespace Radzen.Blazor.Tests
parameters.Add(p => p.Icon, icon);
});
Assert.Contains(@$"<i class=""notranslate rz-button-icon-left rzi"">{icon}</i>", component.Markup);
Assert.Contains(@$"<i class=""notranslate rz-button-icon-left rzi"" aria-hidden=""true"">{icon}</i>", component.Markup);
Assert.Contains(@$"<span class=""rz-button-text"">{text}</span>", component.Markup);
}

View File

@@ -108,24 +108,26 @@ namespace Radzen.Blazor.Tests
}
[Fact]
public void Tabs_Wrapper_IsFocusTarget_WithRoleTablist()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenTabs>();
var wrapper = component.Find("div.rz-tabview");
Assert.Equal("tablist", wrapper.GetAttribute("role"));
Assert.Equal("0", wrapper.GetAttribute("tabindex"));
}
[Fact]
public void Tabs_InnerNav_HasRolePresentation_ToAvoidDuplicateTablist()
public void Tabs_Nav_IsFocusTarget_WithRoleTablist()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenTabs>();
var nav = component.Find("ul.rz-tabview-nav");
Assert.NotEqual("tablist", nav.GetAttribute("role"));
Assert.Equal("tablist", nav.GetAttribute("role"));
Assert.Equal("0", nav.GetAttribute("tabindex"));
}
[Fact]
public void Tabs_Wrapper_HasNoRole_ToAvoidTablistOwningPanels()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenTabs>();
var wrapper = component.Find("div.rz-tabview");
Assert.Null(wrapper.GetAttribute("role"));
Assert.Null(wrapper.GetAttribute("tabindex"));
Assert.Null(wrapper.GetAttribute("aria-activedescendant"));
}
static RenderFragment TabsFragment(params string[] titles) => builder =>
@@ -147,7 +149,7 @@ namespace Radzen.Blazor.Tests
.Add(p => p.Tabs, TabsFragment("One", "Two"))
);
var wrapper = component.Find("div.rz-tabview");
var wrapper = component.Find("ul.rz-tabview-nav");
var active = wrapper.GetAttribute("aria-activedescendant");
Assert.False(string.IsNullOrEmpty(active));
@@ -307,7 +309,7 @@ namespace Radzen.Blazor.Tests
Assert.Contains("First-Content", component.Markup);
var wrapper = component.Find("div.rz-tabview");
var wrapper = component.Find("ul.rz-tabview-nav");
// Simulate non-navigation keystrokes (e.g. a barcode scanner) on the focused tablist.
// Pre-fix: the second keypress latched shouldRender = false, blocking all later renders.
wrapper.KeyDown(new KeyboardEventArgs { Key = "a", Code = "KeyA" });
@@ -334,7 +336,7 @@ namespace Radzen.Blazor.Tests
.Add(p => p.Tabs, TabsFragment("One", "Two", "Three"))
);
var wrapper = component.Find("div.rz-tabview");
var wrapper = component.Find("ul.rz-tabview-nav");
Assert.Equal(expected, wrapper.GetAttribute("aria-orientation"));
}
@@ -345,7 +347,7 @@ namespace Radzen.Blazor.Tests
static string ActiveDescendantText(IRenderedComponent<RadzenTabs> component)
{
var wrapper = component.Find("div.rz-tabview");
var wrapper = component.Find("ul.rz-tabview-nav");
var active = wrapper.GetAttribute("aria-activedescendant");
return component.Find($"[id='{active}']").TextContent;
}
@@ -360,7 +362,7 @@ namespace Radzen.Blazor.Tests
.Add(p => p.Tabs, TabsFragment("One", "Two", "Three"))
);
var wrapper = component.Find("div.rz-tabview");
var wrapper = component.Find("ul.rz-tabview-nav");
wrapper.KeyDown(new KeyboardEventArgs { Key = "ArrowDown", Code = "ArrowDown" });
Assert.Equal("Two", ActiveDescendantText(component));
@@ -376,7 +378,7 @@ namespace Radzen.Blazor.Tests
.Add(p => p.Tabs, TabsFragment("One", "Two", "Three"))
);
var wrapper = component.Find("div.rz-tabview");
var wrapper = component.Find("ul.rz-tabview-nav");
wrapper.KeyDown(new KeyboardEventArgs { Key = "ArrowUp", Code = "ArrowUp" });
Assert.Equal("Two", ActiveDescendantText(component));
@@ -392,7 +394,7 @@ namespace Radzen.Blazor.Tests
.Add(p => p.Tabs, TabsFragment("One", "Two", "Three"))
);
var wrapper = component.Find("div.rz-tabview");
var wrapper = component.Find("ul.rz-tabview-nav");
wrapper.KeyDown(new KeyboardEventArgs { Key = "ArrowRight", Code = "ArrowRight" });
Assert.Equal("Two", ActiveDescendantText(component));
@@ -410,7 +412,7 @@ namespace Radzen.Blazor.Tests
.Add(p => p.Tabs, TabsFragment("One", "Two", "Three"))
);
var wrapper = component.Find("div.rz-tabview");
var wrapper = component.Find("ul.rz-tabview-nav");
wrapper.KeyDown(new KeyboardEventArgs { Key = "ArrowRight", Code = "ArrowRight" });
Assert.Equal("Two", ActiveDescendantText(component));
@@ -426,7 +428,7 @@ namespace Radzen.Blazor.Tests
.Add(p => p.Tabs, TabsFragment("One", "Two", "Three"))
);
var wrapper = component.Find("div.rz-tabview");
var wrapper = component.Find("ul.rz-tabview-nav");
wrapper.KeyDown(new KeyboardEventArgs { Key = "ArrowDown", Code = "ArrowDown" });
Assert.Equal("Two", ActiveDescendantText(component));
@@ -444,7 +446,7 @@ namespace Radzen.Blazor.Tests
.Add(p => p.Tabs, TabsFragment("One", "Two", "Three"))
);
var wrapper = component.Find("div.rz-tabview");
var wrapper = component.Find("ul.rz-tabview-nav");
wrapper.KeyDown(new KeyboardEventArgs { Key = "End", Code = "End" });
Assert.Equal("Three", ActiveDescendantText(component));
@@ -465,7 +467,7 @@ namespace Radzen.Blazor.Tests
.Add(p => p.Tabs, TabsFragmentWithContent(("One", "One-Content"), ("Two", "Two-Content"), ("Three", "Three-Content")))
);
var wrapper = component.Find("div.rz-tabview");
var wrapper = component.Find("ul.rz-tabview-nav");
wrapper.KeyDown(new KeyboardEventArgs { Key = "ArrowDown", Code = "ArrowDown" });
wrapper.KeyDown(new KeyboardEventArgs { Key = "Enter", Code = "Enter" });
@@ -482,7 +484,7 @@ namespace Radzen.Blazor.Tests
.Add(p => p.Tabs, TabsFragment("One", "Two"))
);
var wrapper = component.Find("div.rz-tabview");
var wrapper = component.Find("ul.rz-tabview-nav");
Assert.Equal("Sections", wrapper.GetAttribute("aria-label"));
}
@@ -495,7 +497,7 @@ namespace Radzen.Blazor.Tests
.Add(p => p.Tabs, TabsFragment("One", "Two"))
);
var wrapper = component.Find("div.rz-tabview");
var wrapper = component.Find("ul.rz-tabview-nav");
Assert.Equal("heading-id", wrapper.GetAttribute("aria-labelledby"));
}
@@ -522,7 +524,7 @@ namespace Radzen.Blazor.Tests
.Add(p => p.Tabs, TabsFragment("One", "Two", "Three"))
);
var wrapper = component.Find("div.rz-tabview");
var wrapper = component.Find("ul.rz-tabview-nav");
wrapper.KeyDown(new KeyboardEventArgs { Key = "ArrowRight", Code = "ArrowRight" });
Assert.Equal("One", ActiveDescendantText(component));
@@ -538,7 +540,7 @@ namespace Radzen.Blazor.Tests
.Add(p => p.Tabs, TabsFragment("One", "Two", "Three"))
);
var wrapper = component.Find("div.rz-tabview");
var wrapper = component.Find("ul.rz-tabview-nav");
wrapper.KeyDown(new KeyboardEventArgs { Key = "ArrowLeft", Code = "ArrowLeft" });
Assert.Equal("Three", ActiveDescendantText(component));

View File

@@ -530,6 +530,17 @@ namespace Radzen
return await TryCloseAsync(result);
}
/// <summary>
/// Attempts to close the side dialog. Called from JavaScript ESC handler.
/// </summary>
/// <param name="result">The result.</param>
/// <returns><c>true</c> if the side dialog was closed; <c>false</c> if closing was prevented.</returns>
[JSInvokable("DialogService.TryCloseSide")]
public virtual async Task<bool> TryCloseSideFromJs(dynamic? result = null)
{
return await TryCloseSideAsync(result);
}
/// <inheritdoc />
public void Dispose()
{
@@ -557,6 +568,7 @@ namespace Radzen
options.Style = !String.IsNullOrEmpty(options.Style) ? options.Style : "";
options.CssClass = !String.IsNullOrEmpty(options.CssClass) ? $"rz-dialog-confirm {options.CssClass}" : "rz-dialog-confirm";
options.WrapperCssClass = !String.IsNullOrEmpty(options.WrapperCssClass) ? $"rz-dialog-wrapper {options.WrapperCssClass}" : "rz-dialog-wrapper";
options.Role = "alertdialog";
return await OpenAsync(title, ds =>
{
@@ -606,6 +618,7 @@ namespace Radzen
options.Style = !String.IsNullOrEmpty(options.Style) ? options.Style : "";
options.CssClass = !String.IsNullOrEmpty(options.CssClass) ? $"rz-dialog-confirm {options.CssClass}" : "rz-dialog-confirm";
options.WrapperCssClass = !String.IsNullOrEmpty(options.WrapperCssClass) ? $"rz-dialog-wrapper {options.WrapperCssClass}" : "rz-dialog-wrapper";
options.Role = "alertdialog";
return await OpenAsync(title, ds =>
{
@@ -654,6 +667,7 @@ namespace Radzen
options.Style = !String.IsNullOrEmpty(options.Style) ? options.Style : "";
options.CssClass = !String.IsNullOrEmpty(options.CssClass) ? $"rz-dialog-alert {options.CssClass}" : "rz-dialog-alert";
options.WrapperCssClass = !String.IsNullOrEmpty(options.WrapperCssClass) ? $"rz-dialog-wrapper {options.WrapperCssClass}" : "rz-dialog-wrapper";
options.Role = "alertdialog";
options.ContentCssClass = !String.IsNullOrEmpty(options.ContentCssClass) ? $"rz-dialog-content {options.ContentCssClass}" : "rz-dialog-content";
return await OpenAsync(title, ds =>
@@ -697,6 +711,7 @@ namespace Radzen
options.Style = !String.IsNullOrEmpty(options.Style) ? options.Style : "";
options.CssClass = !String.IsNullOrEmpty(options.CssClass) ? $"rz-dialog-alert {options.CssClass}" : "rz-dialog-alert";
options.WrapperCssClass = !String.IsNullOrEmpty(options.WrapperCssClass) ? $"rz-dialog-wrapper {options.WrapperCssClass}" : "rz-dialog-wrapper";
options.Role = "alertdialog";
options.ContentCssClass = !String.IsNullOrEmpty(options.ContentCssClass) ? $"rz-dialog-content {options.ContentCssClass}" : "rz-dialog-content";
return await OpenAsync(title, ds =>
@@ -1074,7 +1089,7 @@ namespace Radzen
}
}
private bool autoFocusFirstElement;
private bool autoFocusFirstElement = true;
/// <summary>
/// Gets or sets a value indicating whether to focus the first focusable HTML element. Set to <c>true</c> by default.
@@ -1371,6 +1386,8 @@ namespace Radzen
}
}
internal string Role { get; set; } = "dialog";
private bool closeDialogOnEsc = true;
/// <summary>

View File

@@ -941,6 +941,12 @@ namespace Radzen
await OnSelectItem(null, true);
}
if (Multiple && !isFilter && selectedItems.Count > 0)
{
selectedIndex = -1;
await ClearAll();
}
if (AllowFiltering && isFilter)
{
Debounce(DebounceFilter, FilterDelay);

View File

@@ -23,7 +23,7 @@
role="heading" aria-level="@AriaLevel">
<button type="button" @ref="@item.HeaderElement" @onclick="@((args) => SelectItem(item))"
@onkeydown="@(args => OnHeaderKeyDown(args, item))" @onkeydown:preventDefault="preventKeyPress" disabled="@item.Disabled"
aria-label="@ItemAriaLabel(i, item)" title="@ItemTitle(i, item)"
aria-label="@(string.IsNullOrEmpty(item.Text) ? ItemAriaLabel(i, item) : null)" title="@(string.IsNullOrEmpty(item.Text) ? ItemTitle(i, item) : null)"
id="@accordionId" aria-controls="@($"{accordionId}-content")" aria-expanded="@(item.GetSelected() ? "true" : "false")">
<span class="@ToggleIconClass(item)">keyboard_arrow_down</span>
@if (!string.IsNullOrEmpty(item.Icon))

View File

@@ -26,7 +26,7 @@
</div>
@if (AllowClose)
{
<RadzenButton Click=@OnClose Icon="close" Variant="Variant.Text" ButtonStyle="@GetCloseButtonStyle()" Shade="@GetCloseButtonShade()" Size="@GetCloseButtonSize()" />
<RadzenButton Click=@OnClose Icon="close" aria-label="@CloseAriaLabel" Variant="Variant.Text" ButtonStyle="@GetCloseButtonStyle()" Shade="@GetCloseButtonShade()" Size="@GetCloseButtonSize()" />
}
</div>
}

View File

@@ -126,6 +126,15 @@ namespace Radzen.Blazor
[Parameter]
public AlertSize Size { get; set; } = AlertSize.Medium;
private string? closeAriaLabel;
/// <summary>
/// Gets or sets the aria-label of the close button.
/// </summary>
/// <value>The close button aria-label.</value>
[Parameter]
public string CloseAriaLabel { get => closeAriaLabel ?? Localize(nameof(RadzenStrings.Alert_CloseAriaLabel)); set => closeAriaLabel = value; }
ButtonSize GetCloseButtonSize()
{
return Size == AlertSize.ExtraSmall ? ButtonSize.ExtraSmall : ButtonSize.Small;

View File

@@ -46,7 +46,7 @@
hasItems = true;
itemIndex++;
<li role="option" id="@ItemId(itemIndex)" class="rz-autocomplete-list-item" tabindex="-1"
aria-selected="@(object.Equals(item, SelectedItem) ? "true" : "false")"
aria-selected="@(selectedIndex >= 0 ? (itemIndex == selectedIndex ? "true" : "false") : (object.Equals(item, SelectedItem) ? "true" : "false"))"
@onclick="@(() => OnSelectItem(item))" onmousedown="Radzen.activeElement = null">
<span>
@if (Template != null)

View File

@@ -160,6 +160,7 @@ namespace Radzen.Blazor
string? customSearchText;
int selectedIndex = -1;
bool popupOpened;
/// <summary>
/// Handles the FilterKeyPress event.
@@ -195,19 +196,16 @@ namespace Radzen.Blazor
selectedIndex = -1;
}
if (key == "Tab" && JSRuntime != null)
if (key == "Tab")
{
await JSRuntime.InvokeVoidAsync("Radzen.closePopup", PopupID);
await ClosePopup();
}
}
else if (key == "Escape")
{
selectedIndex = -1;
if (JSRuntime != null)
{
await JSRuntime.InvokeVoidAsync("Radzen.closePopup", PopupID);
}
await ClosePopup();
}
else
{
@@ -230,7 +228,8 @@ namespace Radzen.Blazor
if (value.Length < MinLength && !OpenOnFocus)
{
await JSRuntime.InvokeVoidAsync("Radzen.closePopup", PopupID);
await ClosePopup();
await InvokeAsync(() => { StateHasChanged(); });
return;
}
@@ -260,14 +259,48 @@ namespace Radzen.Blazor
private string? ActiveDescendantId => selectedIndex >= 0 ? ItemId(selectedIndex) : null;
private bool IsPopupOpen => OpenOnFocus || (!string.IsNullOrEmpty(searchText) || !string.IsNullOrEmpty(customSearchText));
private bool IsPopupOpen => popupOpened;
private async Task OnSelectItem(object item)
/// <summary>
/// Invoked from client-side code when the suggestion popup opens.
/// </summary>
[JSInvokable]
public async Task OnPopupOpen()
{
if (!popupOpened)
{
popupOpened = true;
await InvokeAsync(StateHasChanged);
}
}
/// <summary>
/// Invoked from client-side code when the suggestion popup closes.
/// </summary>
[JSInvokable]
public async Task OnPopupClose()
{
if (popupOpened || selectedIndex != -1)
{
popupOpened = false;
selectedIndex = -1;
await InvokeAsync(StateHasChanged);
}
}
private async Task ClosePopup()
{
popupOpened = false;
if (JSRuntime != null)
{
await JSRuntime.InvokeVoidAsync("Radzen.closePopup", PopupID);
}
}
private async Task OnSelectItem(object item)
{
await ClosePopup();
await SelectItem(item);
}
@@ -421,7 +454,7 @@ namespace Radzen.Blazor
}
_jsRef = await JSRuntime.InvokeAsync<IJSObjectReference>(
"Radzen.createAutoComplete", Element, PopupID, OpenOnFocus);
"Radzen.createAutoComplete", Element, PopupID, OpenOnFocus, Reference, nameof(OnPopupOpen), nameof(OnPopupClose));
}
}
@@ -459,6 +492,8 @@ namespace Radzen.Blazor
if (shouldClose && !firstRender && JSRuntime != null)
{
popupOpened = false;
selectedIndex = -1;
await JSRuntime.InvokeVoidAsync("Radzen.destroyPopup", PopupID);
}
}

View File

@@ -1,13 +1,17 @@
@inherits RadzenComponentWithChildren
@inherits RadzenComponentWithChildren
<CascadingValue Value="@Template">
@if (Visible)
{
<nav class="@GetCssClass()" style="@Style" @attributes="@Attributes" id="@GetId()" aria-label="@AriaLabel">
@if (ChildContent != null)
{
@ChildContent
}
<ol class="rz-breadcrumb-list">
<CascadingValue Value="this">
@if (ChildContent != null)
{
@ChildContent
}
</CascadingValue>
</ol>
</nav>
}
</CascadingValue>

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Components;
using System.Collections.Generic;
namespace Radzen.Blazor
{
@@ -52,6 +53,39 @@ namespace Radzen.Blazor
{
return "rz-breadcrumb";
}
readonly List<RadzenBreadCrumbItem> items = new List<RadzenBreadCrumbItem>();
internal void AddItem(RadzenBreadCrumbItem item)
{
if (!items.Contains(item))
{
var previousLast = items.Count > 0 ? items[items.Count - 1] : null;
items.Add(item);
previousLast?.Refresh();
}
}
internal void RemoveItem(RadzenBreadCrumbItem item)
{
if (items.Remove(item) && !disposed && items.Count > 0)
{
try
{
items[items.Count - 1].Refresh();
}
catch
{
}
}
}
internal bool IsLastItem(RadzenBreadCrumbItem item)
{
return items.Count > 0 && items[items.Count - 1] == item;
}
}
}

View File

@@ -1,8 +1,8 @@
@inherits RadzenComponent
@inherits RadzenComponent
@if (Visible)
{
<div class="@GetCssClass()" id="@GetId()" style="@Style" @attributes="@Attributes">
<li class="@GetCssClass()" id="@GetId()" style="@Style" @attributes="@Attributes" aria-current="@ItemAriaCurrent">
@if (ChildContent != null)
{
@ChildContent
@@ -32,5 +32,5 @@
<span class="rz-label" aria-current="page">@Text</span>
}
}
</div>
</li>
}

View File

@@ -13,6 +13,12 @@ namespace Radzen.Blazor
[CascadingParameter]
public RenderFragment<RadzenBreadCrumbItem>? Template { get; set; }
/// <summary>
/// The <see cref="RadzenBreadCrumb"/> component this item belongs to. Set via cascading value.
/// </summary>
[CascadingParameter]
public RadzenBreadCrumb? BreadCrumb { get; set; }
/// <summary>
/// The Displayed Text
/// </summary>
@@ -50,5 +56,30 @@ namespace Radzen.Blazor
{
return "rz-breadcrumb-item";
}
string? ItemAriaCurrent => ChildContent == null && Template == null && string.IsNullOrWhiteSpace(Path)
? null
: BreadCrumb?.IsLastItem(this) == true ? "page" : null;
internal void Refresh()
{
StateHasChanged();
}
/// <inheritdoc/>
protected override void OnInitialized()
{
base.OnInitialized();
BreadCrumb?.AddItem(this);
}
/// <inheritdoc/>
public override void Dispose()
{
BreadCrumb?.RemoveItem(this);
base.Dispose();
}
}
}

View File

@@ -25,7 +25,7 @@
{
@if (!string.IsNullOrEmpty(@Icon))
{
<i class="notranslate rz-button-icon-left rzi" style="@(!string.IsNullOrEmpty(IconColor) ? $"color:{IconColor}" : null)">@Icon</i>
<i class="notranslate rz-button-icon-left rzi" aria-hidden="true" style="@(!string.IsNullOrEmpty(IconColor) ? $"color:{IconColor}" : null)">@Icon</i>
}
@if (!string.IsNullOrEmpty(Image))
{

View File

@@ -4,26 +4,30 @@
<CascadingValue Value=this>
@if (Visible)
{
<section @ref=@Element style=@Style @attributes=@Attributes class=@GetCssClass() id=@GetId() tabindex="0">
<section @ref=@Element style=@Style @attributes=@Attributes class=@GetCssClass() id=@GetId() tabindex="0"
aria-roledescription="carousel" aria-label="@AriaLabel"
@onmouseenter="@OnCarouselMouseEnter" @onmouseleave="@OnCarouselMouseLeave"
@onfocusin="@OnCarouselFocusIn" @onfocusout="@OnCarouselFocusOut"
@onkeydown="@OnCarouselKeyDown" @onkeydown:preventDefault=preventKeyPress>
@if(AllowPaging && (PagerPosition == PagerPosition.Top || PagerPosition == PagerPosition.TopAndBottom))
{
<RadzenStack class="rz-carousel-pager rz-carousel-pager-top" Orientation="Orientation.Horizontal" AlignItems="AlignItems.Center" JustifyContent="JustifyContent.Center" Wrap="FlexWrap.Wrap" role="list">
<RadzenStack class="rz-carousel-pager rz-carousel-pager-top" Orientation="Orientation.Horizontal" AlignItems="AlignItems.Center" JustifyContent="JustifyContent.Center" Wrap="FlexWrap.Wrap">
@for (var p = 0; p < PageCount; p++)
{
var pageIndex = p;
var itemIndex = pageIndex * ItemsPerPage;
<button type="button" role="listitem" @onclick="@(args => Navigate(itemIndex))" class="rz-carousel-pager-button @(pageIndex == ActivePage ? "rz-state-active" : "")" aria-label="@string.Format(PagerButtonAriaLabelFormat, pageIndex + 1)"></button>
<button type="button" @onclick="@(args => Navigate(itemIndex))" class="rz-carousel-pager-button @(pageIndex == ActivePage ? "rz-state-active" : "")" aria-label="@string.Format(PagerButtonAriaLabelFormat, pageIndex + 1)" aria-current="@(pageIndex == ActivePage ? "true" : null)"></button>
}
</RadzenStack>
}
@if(AllowNavigation)
{
<RadzenButton class="rz-carousel-prev" Icon="@PrevIcon" Text="@PrevText" ButtonStyle="@ButtonStyle" Size="@ButtonSize" Variant="@ButtonVariant" Shade="@ButtonShade"
Click=@Prev />
aria-label="@(string.IsNullOrEmpty(PrevText) ? PrevAriaLabel : null)" Click=@Prev />
<RadzenButton class="rz-carousel-next" Icon="@NextIcon" Text="@NextText" ButtonStyle="@ButtonStyle" Size="@ButtonSize" Variant="@ButtonVariant" Shade="@ButtonShade"
Click=@Next/>
aria-label="@(string.IsNullOrEmpty(NextText) ? NextAriaLabel : null)" Click=@Next/>
}
<ul @ref="itemsElement" class="rz-carousel-items @(AllowScroll ? "" : "rz-carousel-no-scroll")"
<ul @ref="itemsElement" role="presentation" class="rz-carousel-items @(AllowScroll ? "" : "rz-carousel-no-scroll")"
@ontouchstart="OnTouchStart"
@ontouchend="OnTouchEnd"
@ontouchcancel="OnTouchCancel">
@@ -31,12 +35,12 @@
</ul>
@if(AllowPaging && (PagerPosition == PagerPosition.Bottom || PagerPosition == PagerPosition.TopAndBottom))
{
<RadzenStack class="rz-carousel-pager rz-carousel-pager-bottom" Orientation="Orientation.Horizontal" AlignItems="AlignItems.Center" JustifyContent="JustifyContent.Center" Wrap="FlexWrap.Wrap" role="list">
<RadzenStack class="rz-carousel-pager rz-carousel-pager-bottom" Orientation="Orientation.Horizontal" AlignItems="AlignItems.Center" JustifyContent="JustifyContent.Center" Wrap="FlexWrap.Wrap">
@for (var p = 0; p < PageCount; p++)
{
var pageIndex = p;
var itemIndex = pageIndex * ItemsPerPage;
<button type="button" role="listitem" @onclick="@(args => Navigate(itemIndex))" class="rz-carousel-pager-button @(pageIndex == ActivePage ? "rz-state-active" : "")" aria-label="@string.Format(PagerButtonAriaLabelFormat, pageIndex + 1)"></button>
<button type="button" @onclick="@(args => Navigate(itemIndex))" class="rz-carousel-pager-button @(pageIndex == ActivePage ? "rz-state-active" : "")" aria-label="@string.Format(PagerButtonAriaLabelFormat, pageIndex + 1)" aria-current="@(pageIndex == ActivePage ? "true" : null)"></button>
}
</RadzenStack>
}

View File

@@ -16,7 +16,7 @@ namespace Radzen.Blazor
/// RadzenCarousel displays one item at a time with automatic or manual advancement and various navigation options.
/// Perfect for image galleries, product showcases, hero sections, or any content that benefits from sequential presentation.
/// Features automatic advancement with configurable interval, Previous/Next buttons with customizable icons and text, dot indicators or page numbers for direct item selection,
/// infinite loop for continuous cycling from last to first item, keyboard control (Arrow keys for navigation, Page Up/Down for first/last), swipe gestures on touch devices,
/// infinite loop for continuous cycling from last to first item, keyboard control (Left/Right Arrow keys for navigation), swipe gestures on touch devices,
/// and customization of button styles, pager position (top/bottom/overlay), and navigation visibility.
/// Items are defined using RadzenCarouselItem components. Each item can contain images, text, or complex layouts. Use Auto property to enable automatic cycling, and Interval to control slide duration.
/// </summary>
@@ -98,7 +98,14 @@ namespace Radzen.Blazor
{
if (Auto)
{
await Reset();
if (hovered || focused)
{
Stop();
}
else
{
await Reset();
}
}
await GoTo(index);
@@ -228,6 +235,106 @@ namespace Radzen.Blazor
[Parameter]
public string PagerButtonAriaLabelFormat { get => pagerButtonAriaLabelFormat ?? Localize(nameof(RadzenStrings.Carousel_PagerButtonAriaLabelFormat)); set => pagerButtonAriaLabelFormat = value; }
private string? prevAriaLabel;
/// <summary>
/// Gets or sets the aria-label of the previous button.
/// </summary>
[Parameter]
public string PrevAriaLabel { get => prevAriaLabel ?? Localize(nameof(RadzenStrings.Carousel_PrevAriaLabel)); set => prevAriaLabel = value; }
private string? nextAriaLabel;
/// <summary>
/// Gets or sets the aria-label of the next button.
/// </summary>
[Parameter]
public string NextAriaLabel { get => nextAriaLabel ?? Localize(nameof(RadzenStrings.Carousel_NextAriaLabel)); set => nextAriaLabel = value; }
private string? slideAriaLabelFormat;
/// <summary>
/// Gets or sets the slide aria-label format. Use {0} for the 1-based slide index and {1} for the total slide count.
/// </summary>
[Parameter]
public string SlideAriaLabelFormat { get => slideAriaLabelFormat ?? Localize(nameof(RadzenStrings.Carousel_SlideAriaLabelFormat)); set => slideAriaLabelFormat = value; }
/// <summary>
/// Gets or sets the aria-label of the carousel container.
/// </summary>
[Parameter]
public string? AriaLabel { get; set; }
internal string GetSlideAriaLabel(RadzenCarouselItem item)
{
return string.Format(System.Globalization.CultureInfo.CurrentCulture, SlideAriaLabelFormat, items.IndexOf(item) + 1, items.Count);
}
bool hovered;
bool focused;
bool preventKeyPress;
void UpdateAutoCycle()
{
if (!Auto)
{
return;
}
if (hovered || focused)
{
Stop();
}
else
{
Start();
}
}
void OnCarouselMouseEnter()
{
hovered = true;
UpdateAutoCycle();
}
void OnCarouselMouseLeave()
{
hovered = false;
UpdateAutoCycle();
}
void OnCarouselFocusIn()
{
focused = true;
UpdateAutoCycle();
}
void OnCarouselFocusOut()
{
focused = false;
UpdateAutoCycle();
}
async Task OnCarouselKeyDown(KeyboardEventArgs args)
{
var key = args.Code != null ? args.Code : args.Key;
if (key == "ArrowLeft")
{
preventKeyPress = true;
await Prev();
}
else if (key == "ArrowRight")
{
preventKeyPress = true;
await Next();
}
else
{
preventKeyPress = false;
}
}
/// <inheritdoc />
public override async Task SetParametersAsync(ParameterView parameters)
{

View File

@@ -2,7 +2,7 @@
@implements IDisposable
@inject IJSRuntime JSRuntime
<li @ref="element" @attributes=@Attributes class=@Class style=@ItemStyle tabindex="@(IsActive ? "0" : "-1")" inert="@(!IsActive)">
<li @ref="element" role="group" aria-roledescription="slide" aria-label="@(Carousel?.GetSlideAriaLabel(this))" @attributes=@Attributes class=@Class style=@ItemStyle tabindex="@(IsActive ? "0" : "-1")" inert="@(!IsActive)">
@ChildContent
<div class="rz-carousel-snapper"></div>
</li>

View File

@@ -6,10 +6,10 @@
@if (Visible)
{
<div @ref="@Element" @attributes="Attributes" class="@GetCssClass()" @onkeypress=@OnKeyPress @onkeypress:preventDefault @onclick=@Toggle @onclick:preventDefault style="@Style"
role="checkbox" aria-checked="@CheckBoxAriaChecked" aria-disabled="@(Disabled || ReadOnly ? "true" : "false")"
role="checkbox" aria-checked="@CheckBoxAriaChecked" aria-label="@AriaLabel" aria-disabled="@(Disabled || ReadOnly ? "true" : "false")"
tabindex="@(Disabled || ReadOnly ? "-1" : $"{TabIndex}")" id="@GetId()">
<div class="rz-helper-hidden-accessible">
<input type="checkbox" @onchange=@Toggle value=@CheckBoxValue name=@Name id=@Name checked=@CheckBoxChecked aria-checked="@((Value as bool? == true).ToString().ToLowerInvariant())" @attributes="InputAttributes"
<input type="checkbox" @onchange=@Toggle value=@CheckBoxValue name=@Name id=@Name checked=@CheckBoxChecked aria-hidden="true" @attributes="InputAttributes"
tabindex="-1" readonly="@ReadOnly">
</div>
<div class=@BoxClass>

View File

@@ -37,6 +37,13 @@ namespace Radzen.Blazor
[Parameter]
public IReadOnlyDictionary<string, object>? InputAttributes { get; set; }
/// <summary>
/// Gets or sets the aria-label attribute of the checkbox.
/// </summary>
/// <value>The aria-label attribute.</value>
[Parameter]
public string? AriaLabel { get; set; }
/// <summary>
/// Gets or sets whether the checkbox supports three states: checked (true), unchecked (false), and indeterminate (null).
/// When enabled, clicking cycles through all three states. Use with nullable boolean (<c>bool?</c>) values.

View File

@@ -7,7 +7,7 @@
{
<div @ref=@Element style=@Style @onclick=@Toggle @attributes=@Attributes class=@GetCssClass() id=@GetId()
role="button" aria-haspopup="dialog" aria-expanded="@isPopupOpen.ToString().ToLowerInvariant()" aria-controls="@($"{GetId()}-popup")" aria-disabled="@(Disabled.ToString().ToLowerInvariant())"
tabindex="@(Disabled ? -1 : TabIndex)" @onkeypress="@(args => OnKeyPress(args, Toggle()))" @onkeypress:preventDefault=preventKeyPress @onkeypress:stopPropagation="stopKeypressPropagation">
tabindex="@(Disabled ? -1 : TabIndex)" @onkeydown="@OnTriggerKeyDown" @onkeydown:preventDefault=preventKeyPress @onkeydown:stopPropagation="stopKeypressPropagation">
@if (Icon != null)
{
<i class="notranslate rzi" style="@(!string.IsNullOrEmpty(IconColor) ? $"color:{IconColor}" : null)">@Icon</i>
@@ -23,7 +23,9 @@
@if (ShowHSV)
{
<Draggable class="rz-saturation-picker rz-colorpicker-section" style=@($"background-color: hsl({(HueHandleLeft * 360).ToInvariantString()}, 100%, 50%)") Drag=@OnSaturationMove
id=@(GetId() + "hsl" ) tabindex="@(Disabled ? -1 : 0)" @onkeydown="@(args => OnHslKeyPress(args))">
id=@(GetId() + "hsl" ) tabindex="@(Disabled ? -1 : 0)" @onkeydown="@(args => OnHslKeyPress(args))"
role="slider" aria-roledescription="2D slider" aria-label="@SaturationAriaLabel"
aria-valuemin="0" aria-valuemax="100" aria-valuenow="@SaturationPercent" aria-valuetext="@SaturationValueText">
<div class="rz-saturation-white">
<div class="rz-saturation-black"></div>
<div class="rz-saturation-handle" style=@($"top: {(SaturationHandleTop * 100).ToInvariantString()}%; left: {(SaturationHandleLeft * 100).ToInvariantString()}%")></div>
@@ -31,12 +33,16 @@
</Draggable>
<div class="rz-colorpicker-preview-area rz-colorpicker-section">
<div class="rz-hue-and-alpha">
<Draggable class="rz-hue-picker" Drag=@OnHueMove
id=@(GetId() + "hue" ) tabindex="@(Disabled ? -1 : 0)" @onkeydown="@(args => OnHueKeyPress(args))">
<Draggable class="rz-hue-picker" Drag=@OnHueMove
id=@(GetId() + "hue" ) tabindex="@(Disabled ? -1 : 0)" @onkeydown="@(args => OnHueKeyPress(args))"
role="slider" aria-orientation="horizontal" aria-label="@HueAriaLabel"
aria-valuemin="0" aria-valuemax="360" aria-valuenow="@HuePercent">
<div class="rz-hue-handle" style=@($"top: 0; left: {(HueHandleLeft * 100).ToInvariantString()}%")></div>
</Draggable>
<Draggable style=@($"background-image: linear-gradient(to right, {AlphaGradientStart} 0%, {AlphaGradientEnd} 100%)") class="rz-alpha-picker" Drag=@OnAlphaMove
id=@(GetId() + "alpha" ) tabindex="@(Disabled ? -1 : 0)" @onkeydown="@(args => OnAlphaKeyPress(args))">
id=@(GetId() + "alpha" ) tabindex="@(Disabled ? -1 : 0)" @onkeydown="@(args => OnAlphaKeyPress(args))"
role="slider" aria-orientation="horizontal" aria-label="@AlphaAriaLabel"
aria-valuemin="0" aria-valuemax="100" aria-valuenow="@Alpha">
<div class="rz-alpha-handle" style=@($"top: 0; left: {(AlphaHandleLeft * 100).ToInvariantString()}%")></div>
</Draggable>
</div>

View File

@@ -126,6 +126,43 @@ namespace Radzen.Blazor
[Parameter]
public string AlphaText { get => alphaText ?? Localize(nameof(RadzenStrings.ColorPicker_AlphaText)); set => alphaText = value; }
private string? hueAriaLabel;
/// <summary>
/// Gets or sets the aria label text of the hue slider.
/// </summary>
/// <value>The aria label text of the hue slider.</value>
[Parameter]
public string HueAriaLabel { get => hueAriaLabel ?? Localize(nameof(RadzenStrings.ColorPicker_HueAriaLabel)); set => hueAriaLabel = value; }
private string? alphaAriaLabel;
/// <summary>
/// Gets or sets the aria label text of the alpha slider.
/// </summary>
/// <value>The aria label text of the alpha slider.</value>
[Parameter]
public string AlphaAriaLabel { get => alphaAriaLabel ?? Localize(nameof(RadzenStrings.ColorPicker_AlphaAriaLabel)); set => alphaAriaLabel = value; }
private string? saturationAriaLabel;
/// <summary>
/// Gets or sets the aria label text of the saturation and brightness area.
/// </summary>
/// <value>The aria label text of the saturation and brightness area.</value>
[Parameter]
public string SaturationAriaLabel { get => saturationAriaLabel ?? Localize(nameof(RadzenStrings.ColorPicker_SaturationAriaLabel)); set => saturationAriaLabel = value; }
private string? saturationValueTextFormat;
/// <summary>
/// Gets or sets the format string used to build the aria-valuetext of the saturation and brightness area.
/// The first argument is the saturation percent and the second one is the brightness percent.
/// </summary>
/// <value>The aria-valuetext format string of the saturation and brightness area.</value>
[Parameter]
public string SaturationValueTextFormat { get => saturationValueTextFormat ?? Localize(nameof(RadzenStrings.ColorPicker_SaturationValueTextFormat)); set => saturationValueTextFormat = value; }
private string? buttonText;
/// <summary>
@@ -175,6 +212,14 @@ namespace Radzen.Blazor
}
}
double HuePercent => Math.Round(HueHandleLeft * 360);
double SaturationPercent => Math.Round(SaturationHandleLeft * 100);
double BrightnessPercent => Math.Round((1 - SaturationHandleTop) * 100);
string SaturationValueText => string.Format(System.Globalization.CultureInfo.CurrentCulture, SaturationValueTextFormat, SaturationPercent, BrightnessPercent);
double Red
{
get
@@ -632,16 +677,16 @@ namespace Radzen.Blazor
bool preventKeyPress;
bool stopKeypressPropagation;
async Task OnKeyPress(KeyboardEventArgs args, Task task)
async Task OnTriggerKeyDown(KeyboardEventArgs args)
{
var key = args.Code != null ? args.Code : args.Key;
if (key == "Space" || key == "Enter")
if (key == "Space" || key == "Enter" || key == "NumpadEnter")
{
preventKeyPress = true;
stopKeypressPropagation = true;
await task;
await Toggle();
}
else if (key == "Escape")
{

View File

@@ -2,4 +2,4 @@
<div class="rz-colorpicker-item @(isSelected ? "selected" : "")" style="background-color: @Background" @onmousedown:preventDefault @onclick=@OnClick
role="button" aria-label="@Value" aria-disabled="@(ColorPicker?.Disabled == true ? "true" : "false")"
tabindex="@(ColorPicker?.Disabled == true ? -1 : 0)" @onkeypress="@(args => OnKeyPress(args, OnClick()))" @onkeypress:preventDefault=preventKeyPress @onkeypress:stopPropagation="stopKeypressPropagation"></div>
tabindex="@(ColorPicker?.Disabled == true ? -1 : 0)" @onkeydown="@OnKeyDown" @onkeydown:preventDefault=preventKeyPress @onkeydown:stopPropagation="stopKeypressPropagation"></div>

View File

@@ -79,16 +79,16 @@ namespace Radzen.Blazor
bool preventKeyPress;
bool stopKeypressPropagation;
async Task OnKeyPress(KeyboardEventArgs args, Task task)
async Task OnKeyDown(KeyboardEventArgs args)
{
var key = args.Code != null ? args.Code : args.Key;
if (key == "Space" || key == "Enter")
if (key == "Space" || key == "Enter" || key == "NumpadEnter")
{
preventKeyPress = true;
stopKeypressPropagation = true;
await task;
await OnClick();
}
else if (key == "Escape")
{

View File

@@ -338,6 +338,33 @@ namespace Radzen.Blazor
[Parameter]
public string CustomText { get => customText ?? Localize(nameof(RadzenStrings.DataFilter_CustomText)); set => customText = value; }
private string? propertyAriaLabel;
/// <summary>
/// Gets or sets the aria-label of the filter property dropdown.
/// </summary>
/// <value>The aria-label of the filter property dropdown.</value>
[Parameter]
public string PropertyAriaLabel { get => propertyAriaLabel ?? Localize(nameof(RadzenStrings.DataFilter_PropertyAriaLabel)); set => propertyAriaLabel = value; }
private string? filterOperatorAriaLabel;
/// <summary>
/// Gets or sets the aria-label of the filter operator dropdown.
/// </summary>
/// <value>The aria-label of the filter operator dropdown.</value>
[Parameter]
public string FilterOperatorAriaLabel { get => filterOperatorAriaLabel ?? Localize(nameof(RadzenStrings.DataFilter_FilterOperatorAriaLabel)); set => filterOperatorAriaLabel = value; }
private string? filterValueAriaLabel;
/// <summary>
/// Gets or sets the aria-label of the filter value editor.
/// </summary>
/// <value>The aria-label of the filter value editor.</value>
[Parameter]
public string FilterValueAriaLabel { get => filterValueAriaLabel ?? Localize(nameof(RadzenStrings.DataFilter_FilterValueAriaLabel)); set => filterValueAriaLabel = value; }
/// <summary>
/// Gets or sets a value indicating whether the columns can be filtered.
/// </summary>

View File

@@ -29,6 +29,7 @@ else if (Filter != null)
{
<RadzenDropDown @bind-Value="@Filter.Property" Data="@(DataFilter?.properties)" TextProperty="Title" ValueProperty="Property" TValue="string"
DisabledProperty="@(DataFilter?.UniqueFilters == true ? nameof(property.IsSelected) : null)"
InputAttributes="@(new Dictionary<string,object>(){ { "aria-label", $"{DataFilter?.PropertyAriaLabel} {property?.Title ?? property?.Property}".Trim() }})"
Change="@OnPropertyChange" AllowFiltering="@(DataFilter?.AllowColumnFiltering ?? false)" FilterCaseSensitivity="FilterCaseSensitivity.CaseInsensitive" class="rz-datafilter-property">
<Template>
@{
@@ -40,6 +41,7 @@ else if (Filter != null)
if (property != null)
{
<RadzenDropDown @onclick:preventDefault="true" Data="@(property.GetFilterOperators().Select(t => new DropDownItem<FilterOperator> { Text = property.GetFilterOperatorText(t), Value = t }))"
InputAttributes="@(new Dictionary<string,object>(){ { "aria-label", $"{DataFilter?.FilterOperatorAriaLabel} {(Filter.FilterOperator != null ? property.GetFilterOperatorText(Filter.FilterOperator.Value) : "")}".Trim() }})"
TextProperty="Text" ValueProperty="Value" TValue="FilterOperator?" @bind-Value="@Filter.FilterOperator" Change="@OnOperatorChange" class="rz-datafilter-operator" />
@if (property.FilterTemplate != null)
{
@@ -50,6 +52,7 @@ else if (Filter != null)
else if (PropertyAccess.IsNullableEnum(property.FilterPropertyType) || PropertyAccess.IsEnum(property.FilterPropertyType))
{
<RadzenDropDown Disabled="@IsOperatorNullOrEmpty()" AllowClear="false" AllowFiltering="false" TValue="@object" class="rz-datafilter-editor"
InputAttributes="@(new Dictionary<string,object>(){ { "aria-label", $"{DataFilter?.FilterValueAriaLabel} {Filter.FilterValue}".Trim() }})"
@bind-Value=@Filter.FilterValue Multiple="false" Placeholder="@(DataFilter?.EnumFilterSelectText ?? "")" TextProperty="Text" ValueProperty="Value"
Data=@EnumExtensions.EnumAsKeyValuePair(property.FilterPropertyType != null ? (Nullable.GetUnderlyingType(property.FilterPropertyType!)! ?? property.FilterPropertyType) : typeof(object)) Change=@(args => { _ = InvokeAsync(ApplyFilter); }) />
}
@@ -60,15 +63,17 @@ else if (Filter != null)
else if (PropertyAccess.IsDate(property.FilterPropertyType))
{
<RadzenDatePicker Disabled="@IsOperatorNullOrEmpty()" @bind-Value=@Filter.FilterValue TValue="@object" ShowTime="true" class="rz-datafilter-editor"
InputAttributes="@(new Dictionary<string,object>(){ { "aria-label", $"{DataFilter?.FilterValueAriaLabel} {Filter.FilterValue}".Trim() }})"
ShowTimeOkButton="true" DateFormat="@getFilterDateFormat()" Change=@(args => InvokeAsync(ApplyFilter)) />
}
else if (property.FilterPropertyType == typeof(bool) || property.FilterPropertyType == typeof(bool?))
{
<RadzenCheckBox Disabled="@IsOperatorNullOrEmpty()" TriState="true" TValue="@object" @bind-Value=@Filter.FilterValue Change=@(args => InvokeAsync(ApplyFilter)) class="rz-datafilter-check" />
<RadzenCheckBox Disabled="@IsOperatorNullOrEmpty()" TriState="true" TValue="@object" @bind-Value=@Filter.FilterValue Change=@(args => InvokeAsync(ApplyFilter)) class="rz-datafilter-check"
InputAttributes="@(new Dictionary<string,object>(){ { "aria-label", $"{DataFilter?.FilterValueAriaLabel} {Filter.FilterValue}".Trim() }})" />
}
else
{
<RadzenTextBox Disabled="@IsOperatorNullOrEmpty()" class="rz-datafilter-editor" Value="@($"{Filter.FilterValue}")" Change=@(args => { Filter.FilterValue = args; InvokeAsync(ApplyFilter); }) />
<RadzenTextBox Disabled="@IsOperatorNullOrEmpty()" class="rz-datafilter-editor" aria-label="@($"{DataFilter?.FilterValueAriaLabel} {Filter.FilterValue}".Trim())" Value="@($"{Filter.FilterValue}")" Change=@(args => { Filter.FilterValue = args; InvokeAsync(ApplyFilter); }) />
}
<RadzenButton title="@(DataFilter?.RemoveFilterText ?? "")" class="rz-datafilter-item-clear" Icon="clear" Click="@RemoveFilter" Variant="Variant.Text" Size="ButtonSize.Small" ButtonStyle="ButtonStyle.Dark"/>
@@ -356,6 +361,8 @@ else if (Filter != null)
}
}
builder.AddAttribute(5, "InputAttributes", new Dictionary<string, object>() { { "aria-label", $"{DataFilter?.FilterValueAriaLabel} {Filter?.FilterValue}".Trim() } });
builder.CloseComponent();
};
}

View File

@@ -13,7 +13,9 @@
var visibleColumns = columns.Where(c => c.GetVisible()).ToList();
<div @ref=@Element style="@Style" @attributes="Attributes" class="rz-data-grid @GetCssClass()" id="@GetId()"
role="grid"
role="@(LoadChildData.HasDelegate ? "treegrid" : "grid")"
aria-multiselectable="@(SelectionMode == DataGridSelectionMode.Multiple ? "true" : null)"
aria-rowcount="@GridAriaRowCount()"
aria-activedescendant="@GetActiveDescendantId()"
@onkeydown="@OnKeyDown" @onkeydown:preventDefault="preventKeyDown" @onkeydown:stopPropagation="stopKeydownPropagation" tabindex="@TabIndex"
@onblur=@OnBlur @oncontextmenu=OnContextMenu @oncontextmenu:preventDefault="@this.ContextMenu.HasDelegate" @oncontextmenu:stopPropagation="@this.ContextMenu.HasDelegate">
@@ -672,6 +674,7 @@
{
<span class="rz-cell-toggle">
<button type="button" tabindex="-1" id="@(GetId() + "exp")" aria-label="@ExpandChildItemAriaLabel"
aria-expanded="@(this.IsRowExpanded(Item) ? "true" : "false")"
class="rz-button rz-button-sm rz-button-icon-only rz-variant-text rz-base rz-shade-default"
style="@(getExpandIconStyle(this, Item, rowArgs.Item1.Expandable))"
@onclick="_ => this.ExpandItem(Item)" @onclick:stopPropagation="true">

View File

@@ -177,6 +177,8 @@ namespace Radzen.Blazor
List<TItem> virtualDataItems = new List<TItem>();
int virtualItemsStartIndex;
/// <summary>
/// Clears the internal data cache and refreshes the DataGrid, reloading data from the source.
/// When virtualization is enabled, this method refreshes the Virtualize component. Otherwise, it triggers a standard reload.
@@ -234,6 +236,8 @@ namespace Radzen.Blazor
virtualDataItems = (LoadData.HasDelegate ? (itemsToInsert.Count > 0 ? itemsToInsert.ToList().Concat(Data ?? Enumerable.Empty<TItem>()) : Data) : itemsToInsert.Count > 0 ? itemsToInsert.ToList().Concat(view.Skip(request.StartIndex).Take(top)) : view.Skip(request.StartIndex).Take(top))?.ToList() ?? new List<TItem>();
virtualItemsStartIndex = request.StartIndex;
return new Microsoft.AspNetCore.Components.Web.Virtualization.ItemsProviderResult<TItem>(virtualDataItems, totalItemsCount);
}
@@ -600,14 +604,16 @@ namespace Radzen.Blazor
internal Func<bool>? HasActiveDescendant { get; set; }
bool hasActiveRow;
internal string? GetActiveDescendantId()
{
if (HasActiveDescendant != null && !HasActiveDescendant())
if (HasActiveDescendant != null)
{
return null;
return HasActiveDescendant() ? $"{GetId()}-active-item" : null;
}
return $"{GetId()}-active-item";
return hasActiveRow ? $"{GetId()}-active-item" : null;
}
internal string? GridId()
@@ -632,6 +638,7 @@ namespace Radzen.Blazor
var result = await JSRuntime.InvokeAsync<int[]>("Radzen.focusTableRow", UniqueID, key, focusedIndex, focusedCellIndex, IsVirtualizationAllowed());
focusedIndex = result[0];
focusedCellIndex = result[1];
hasActiveRow = true;
}
}
catch (Exception)
@@ -709,7 +716,7 @@ namespace Radzen.Blazor
await FocusRow(key);
}
else if (IsVirtualizationAllowed() && (key == "PageUp" || key == "PageDown" || key == "Home" || key == "End"))
else if (key == "PageUp" || key == "PageDown" || key == "Home" || key == "End")
{
preventKeyDown = true;
stopKeydownPropagation = true;
@@ -2781,6 +2788,36 @@ namespace Radzen.Blazor
return selectedItems.Keys.Any(i => ItemEquals(i, item)) ? "true" : "false";
}
int HeaderRowCount()
{
return (ShowHeader ? deepestChildColumnLevel + 1 : 0) + (FilterRowActive ? 1 : 0);
}
internal string? GridAriaRowCount()
{
return IsVirtualizationAllowed() && Groups.Count == 0 ? (Count + HeaderRowCount()).ToString(CultureInfo.InvariantCulture) : null;
}
internal string? RowAriaRowIndex(int index)
{
return IsVirtualizationAllowed() && Groups.Count == 0 ? (virtualItemsStartIndex + index + HeaderRowCount() + 1).ToString(CultureInfo.InvariantCulture) : null;
}
internal string? RowAriaLevel(TItem item)
{
if (!LoadChildData.HasDelegate)
{
return null;
}
var child = childData.Count > 0 ? childData.Where(c => c.Value?.Data?.Contains(item) == true).FirstOrDefault() :
default(KeyValuePair<TItem, DataGridChildData<TItem>>);
var level = !object.Equals(child, default(KeyValuePair<TItem, DataGridChildData<TItem>>)) ? child.Value.Level : 0;
return (level + 1).ToString(CultureInfo.InvariantCulture);
}
internal Tuple<Radzen.RowRenderEventArgs<TItem>, IReadOnlyDictionary<string, object>> RowAttributes(TItem item, int index)
{
var args = new Radzen.RowRenderEventArgs<TItem>() { Data = item, Index = index, Expandable = Template != null || LoadChildData.HasDelegate };

View File

@@ -9,7 +9,7 @@
{
<th role="columnheader" rowspan="@(Column.GetRowSpan())" colspan="@(Column.GetColSpan())" @attributes="@Attributes" class="@CssClass"
scope="col" style="@GetStyle()" data-column-index="@ColumnIndex" @onmouseup=@(args => Grid.EndColumnReorder(args, ColumnIndex))
aria-sort="@(Grid.AllowSorting && Column.Sortable ? (Column.GetSortOrder() == SortOrder.Ascending ? "ascending" : Column.GetSortOrder() == SortOrder.Descending ? "descending" : "none") : null)">
aria-sort="@(Grid.AllowSorting && Column.Sortable ? (Column.GetSortOrder() == SortOrder.Ascending ? "ascending" : Column.GetSortOrder() == SortOrder.Descending ? "descending" : null) : null)">
<div @onclick='@((args) => Grid.OnSort(args, Column))'>
@if ((Grid.AllowColumnReorder && Column.Reorderable || Grid.AllowGrouping && Column.Groupable))
{

View File

@@ -7,7 +7,7 @@
@{var firstLevel = Grid?.AllowCompositeDataCells == true ? 0 : (Grid?.deepestChildColumnLevel ?? 0); }
@for(var i = firstLevel; (Grid?.AllowCompositeDataCells == true ? i <= (Grid?.deepestChildColumnLevel ?? 0) + 1 : i < (Grid?.deepestChildColumnLevel ?? 0) + 1); i++)
{
<tr role="row" aria-selected="@(Grid?.RowAriaSelected(Item, Index))" class="@(Grid?.RowStyle(Item, Index) ?? "")" @attributes="@(rowArgs?.Item2)">
<tr role="row" aria-selected="@(Grid?.RowAriaSelected(Item, Index))" aria-level="@(Grid?.RowAriaLevel(Item))" aria-rowindex="@(Grid?.RowAriaRowIndex(Index))" class="@(Grid?.RowStyle(Item, Index) ?? "")" @attributes="@(rowArgs?.Item2)">
@if (Grid?.ShowGroupExpandColumn == true)
{
@foreach(var g in Grid.Groups)

View File

@@ -14,7 +14,9 @@
{
@if (!WrapItems)
{
<ul class="rz-datalist-data">
@DrawDataListRows()
</ul>
}
else
{

View File

@@ -139,7 +139,7 @@ namespace Radzen.Blazor
{
builder.OpenComponent(0, typeof(Microsoft.AspNetCore.Components.Web.Virtualization.Virtualize<TItem>));
builder.AddAttribute(1, "ItemsProvider", new Microsoft.AspNetCore.Components.Web.Virtualization.ItemsProviderDelegate<TItem>(LoadItems));
builder.AddAttribute(2, "ChildContent", (RenderFragment<TItem>)((context) =>
{
return (RenderFragment)((b) =>
@@ -148,6 +148,11 @@ namespace Radzen.Blazor
});
}));
if (!WrapItems)
{
builder.AddAttribute(3, "SpacerElement", "li");
}
builder.AddComponentReferenceCapture(4, c => { virtualize = (Microsoft.AspNetCore.Components.Web.Virtualization.Virtualize<TItem>)c; });
builder.CloseComponent();

View File

@@ -2,18 +2,16 @@
@if (DataList != null && !DataList.WrapItems)
{
<ul class="rz-datalist-data">
<li>
@if (DataList.Template != null)
{
@DataList.Template(Item!)
}
else
{
<span>Template</span>
}
</li>
</ul>
<li>
@if (DataList.Template != null)
{
@DataList.Template(Item!)
}
else
{
<span>Template</span>
}
</li>
}
else
{

View File

@@ -764,7 +764,9 @@ namespace Radzen.Blazor
try
{
var newDate = Culture.Calendar.AddMonths(FocusedDate, key == "PageUp" ? -1 : 1);
var newDate = args.ShiftKey
? Culture.Calendar.AddYears(FocusedDate, key == "PageUp" ? -1 : 1)
: Culture.Calendar.AddMonths(FocusedDate, key == "PageUp" ? -1 : 1);
FocusedDate = newDate;
CurrentDate = newDate;
shouldFocusDay = true;

View File

@@ -234,6 +234,7 @@
builder.CloseComponent(); // Close CascadingValue
});
isSideDialogOpen = true;
sideDialogJsOpened = false;
StateHasChanged();
}
@@ -247,6 +248,8 @@
private bool sideDialogClosing = false;
private bool sideDialogJsOpened = false;
private async Task OnSideCloseAsync()
{
sideDialogClosing = true;
@@ -257,9 +260,18 @@
isSideDialogOpen = false;
sideDialogClosing = false;
sideDialogJsOpened = false;
StateHasChanged();
try
{
await JSRuntime.InvokeVoidAsync("Radzen.closeSideDialog");
}
catch (Exception ex) when (ex is JSDisconnectedException || ex is OperationCanceledException || ex is JSException)
{
}
Service?.OnSideCloseComplete();
}
@@ -313,7 +325,11 @@
if (isSideDialogOpen)
{
await JSRuntime.InvokeAsync<string>("Radzen.openSideDialog", sideDialogOptions);
if (!sideDialogJsOpened)
{
sideDialogJsOpened = true;
await JSRuntime.InvokeAsync<string>("Radzen.openSideDialog", sideDialogOptions, Service?.Reference);
}
if (sideDialogOptions is { Resizable: true } && resizeBarElement.HasValue)
{

View File

@@ -8,12 +8,12 @@
@if (Visible)
{
<div @ref="@Element" aria-label="@(!Multiple && selectedItem != null ? GetItemOrValueFromProperty(selectedItem, TextProperty ?? string.Empty) : EmptyAriaLabel)" @attributes="Attributes" class="@GetCssClass()"
<div @ref="@Element" aria-label="@SelectedAriaLabel" @attributes="Attributes" class="@GetCssClass()"
role="combobox" aria-haspopup="listbox" aria-autocomplete="@(AllowFiltering ? "list" : "none")" aria-expanded="@isPopupOpen.ToString().ToLowerInvariant()" aria-controls="@ListId" aria-activedescendant="@ActiveDescendantId" aria-disabled="@(Disabled.ToString().ToLowerInvariant())"
@onclick="@(args => OpenPopup("ArrowDown", false, true))" @onclick:preventDefault @onclick:stopPropagation style="@Style" tabindex="@(Disabled ? "-1" : $"{TabIndex}")"
@onkeydown="@((args) => OnKeyPress(args))" @onkeydown:preventDefault="@preventKeydown" id="@GetId()" @onfocus=@this.AsNonRenderingEventHandler(OnFocus)>
<div class="rz-helper-hidden-accessible">
<input disabled="@Disabled" aria-haspopup="listbox" readonly="" type="text" tabindex="-1"
<input disabled="@Disabled" aria-haspopup="listbox" aria-hidden="true" readonly="" type="text" tabindex="-1"
name="@Name" value="@(internalValue != null ? internalValue : "")" id="@Name"
aria-label="@(!Multiple && internalValue != null ? internalValue : EmptyAriaLabel)" @attributes="InputAttributes"/>
</div>
@@ -59,7 +59,7 @@
@GetItemOrValueFromProperty(item, TextProperty ?? string.Empty)
}
</span>
<button tabindex="0"
<button tabindex="-1"
title="@(RemoveChipTitle + " " + GetItemOrValueFromProperty(item, TextProperty ?? ""))"
aria-label="@(RemoveChipTitle + " " + GetItemOrValueFromProperty(item, TextProperty ?? ""))"
type=button class="rz-button rz-button-xs rz-variant-text rz-base rz-shade-default rz-button-icon-only @(Disabled ?"rz-state-disabled":"")"

View File

@@ -5,6 +5,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Web;
using System.Collections.Generic;
using System;
using System.Linq;
using Radzen.Blazor.Rendering;
namespace Radzen.Blazor
@@ -286,6 +287,29 @@ namespace Radzen.Blazor
[Parameter]
public string SelectAllText { get; set; } = string.Empty;
internal string? SelectedAriaLabel
{
get
{
if (!Multiple)
{
return selectedItem != null ? $"{GetItemOrValueFromProperty(selectedItem, TextProperty ?? string.Empty)}" : EmptyAriaLabel;
}
if (selectedItems.Count == 0)
{
return EmptyAriaLabel;
}
if (selectedItems.Count < MaxSelectedLabels)
{
return string.Join(Separator, selectedItems.Select(i => $"{GetItemOrValueFromProperty(i, TextProperty ?? string.Empty)}"));
}
return $"{selectedItems.Count} {SelectedItemsText}";
}
}
/// <summary>
/// Callback for when a dropdown is opened.
/// </summary>

View File

@@ -8,12 +8,12 @@
@if (Visible)
{
<div @ref="@Element" aria-label="@(!Multiple && internalValue != null ? PropertyAccess.GetItemOrValueFromProperty(internalValue, TextProperty) : EmptyAriaLabel)" @attributes="Attributes" class="@GetCssClass()" @onfocus=@this.AsNonRenderingEventHandler(OnFocus)
role="combobox" aria-haspopup="grid" aria-expanded="@isPopupOpen.ToString().ToLowerInvariant()" aria-controls="@PopupID" aria-disabled="@(Disabled.ToString().ToLowerInvariant())" aria-activedescendant="@ActiveDescendantId"
<div @ref="@Element" aria-label="@SelectedAriaLabel" @attributes="Attributes" class="@GetCssClass()" @onfocus=@this.AsNonRenderingEventHandler(OnFocus)
role="combobox" aria-haspopup="grid" aria-expanded="@isPopupOpen.ToString().ToLowerInvariant()" aria-controls="@GridID" aria-disabled="@(Disabled.ToString().ToLowerInvariant())" aria-activedescendant="@ActiveDescendantId"
style="@Style" tabindex="@(Disabled ? "-1" : $"{TabIndex}")" id="@GetId()" @onclick="@(args => OpenPopup("ArrowDown", false, true))" @onclick:preventDefault @onclick:stopPropagation
@onkeydown="@((args) => OnKeyPress(args))" @onkeydown:preventDefault="@preventKeydown">
<div class="rz-helper-hidden-accessible">
<input tabindex="-1" disabled="@Disabled" style="width:100%" aria-haspopup="listbox" readonly="" type="text" id="@Name"
<input tabindex="-1" disabled="@Disabled" style="width:100%" aria-haspopup="grid" aria-hidden="true" readonly="" type="text" id="@Name"
name="@Name" aria-label="@(!Multiple && internalValue != null ? PropertyAccess.GetItemOrValueFromProperty(internalValue, TextProperty) : EmptyAriaLabel)" @attributes="InputAttributes" />
</div>
@@ -62,7 +62,7 @@
@GetItemOrValueFromProperty(item!, TextProperty ?? string.Empty)
}
</span>
<button tabindex="0"
<button tabindex="-1"
title="@(RemoveChipTitle + " " + GetItemOrValueFromProperty(item!, TextProperty ?? string.Empty))"
aria-label="@(RemoveChipTitle + " " + GetItemOrValueFromProperty(item!, TextProperty ?? string.Empty))"
type="button" class="rz-button rz-button-xs rz-variant-text rz-base rz-shade-default rz-button-icon-only @(Disabled ?"rz-state-disabled":"")"
@@ -135,7 +135,7 @@
<div class="rz-lookup-search">
<div class="rz-lookup-search-input-container">
<input aria-label="@SearchAriaLabel" class="rz-lookup-search-input" id="@SearchID" @ref="@search" tabindex="0" placeholder="@(SearchTextPlaceholder ?? string.Empty)"
role="combobox" aria-controls="@PopupID" aria-haspopup="grid" aria-expanded="@isPopupOpen.ToString().ToLowerInvariant()" aria-autocomplete="list" aria-activedescendant="@ActiveDescendantId"
role="combobox" aria-controls="@GridID" aria-haspopup="grid" aria-expanded="@isPopupOpen.ToString().ToLowerInvariant()" aria-autocomplete="list" aria-activedescendant="@ActiveDescendantId"
@onchange="@OnFilter" @onkeydown="@OnFilterKeyPress" value="@(searchText ?? string.Empty)" style="@(ShowSearch ? "" : "margin-right:0px;")"
@oninput="@OnFilterInput" />
@if (!string.IsNullOrEmpty(searchText))
@@ -163,7 +163,7 @@
}
@if (Template != null)
{
<RadzenDataGrid TabIndex="-1" GridLines=@GridLines EmptyTemplate=@EmptyTemplate FooterTemplate=@FooterTemplate AllowVirtualization="@IsVirtualizationAllowed()" VirtualizationOverscanCount="@GetVirtualizationOverscanCount()" AllowRowSelectOnRowClick="@AllowRowSelectOnRowClick" PagerHorizontalAlign="@PagerHorizontalAlign" PagerAlwaysVisible="@PagerAlwaysVisible" Responsive="@Responsive" AllowColumnResize="@AllowColumnResize" AllowColumnPicking="@AllowColumnPicking" AllowColumnReorder="@AllowColumnReorder" ColumnWidth="@ColumnWidth" @ref="grid" Data="@(LoadData.HasDelegate ? (Data != null ? Data.Cast<object>() : Enumerable.Empty<object>()) : pagedData)" Count="@(LoadData.HasDelegate ? Count : count)" LoadData="@OnLoadData" LoadChildData="@LoadChildData"
<RadzenDataGrid id="@GridID" TabIndex="-1" GridLines=@GridLines EmptyTemplate=@EmptyTemplate FooterTemplate=@FooterTemplate AllowVirtualization="@IsVirtualizationAllowed()" VirtualizationOverscanCount="@GetVirtualizationOverscanCount()" AllowRowSelectOnRowClick="@AllowRowSelectOnRowClick" PagerHorizontalAlign="@PagerHorizontalAlign" PagerAlwaysVisible="@PagerAlwaysVisible" Responsive="@Responsive" AllowColumnResize="@AllowColumnResize" AllowColumnPicking="@AllowColumnPicking" AllowColumnReorder="@AllowColumnReorder" ColumnWidth="@ColumnWidth" @ref="grid" Data="@(LoadData.HasDelegate ? (Data != null ? Data.Cast<object>() : Enumerable.Empty<object>()) : pagedData)" Count="@(LoadData.HasDelegate ? Count : count)" LoadData="@OnLoadData" LoadChildData="@LoadChildData"
TItem="object" ColumnReordered="@ColumnReordered" ColumnResized="@ColumnResized" ColumnReordering="@ColumnReordering" ContextMenu="@ContextMenuDataGrid"
CellRender="@OnCellRender" RowRender="@OnRowRender" ShowPagingSummary="@ShowPagingSummary" PagingSummaryFormat="@PagingSummaryFormat" PageSize="@PageSize" PageSizeOptions="@PageSizeOptions" PageNumbersCount="@PageNumbersCount" AllowPaging="@(!IsVirtualizationAllowed())" AllowSorting="@AllowSorting" RowSelect="@OnRowSelect" Style="@(IsVirtualizationAllowed() ? "height:285px" : "")" Density="@Density" IsLoading="@IsLoading" FirstPageTitle="@FirstPageTitle" FirstPageAriaLabel="@FirstPageAriaLabel" PrevPageAriaLabel="@PrevPageAriaLabel" PrevPageTitle="@PrevPageTitle" NextPageAriaLabel="@NextPageAriaLabel" NextPageTitle="@NextPageTitle" LastPageAriaLabel="@LastPageAriaLabel" LastPageTitle="@LastPageTitle" PageAriaLabelFormat="@PageAriaLabelFormat" PageTitleFormat="@PageTitleFormat">
<Columns>
@@ -179,7 +179,7 @@
{
if (!string.IsNullOrEmpty(TextProperty) || Columns != null)
{
<RadzenDataGrid TabIndex="-1" GridLines=@GridLines EmptyTemplate=@EmptyTemplate FooterTemplate=@FooterTemplate AllowVirtualization="@IsVirtualizationAllowed()" VirtualizationOverscanCount="@GetVirtualizationOverscanCount()" AllowRowSelectOnRowClick="@AllowRowSelectOnRowClick" PagerHorizontalAlign="@PagerHorizontalAlign" PagerAlwaysVisible="@PagerAlwaysVisible" Responsive="@Responsive" AllowColumnResize="@AllowColumnResize" AllowColumnPicking="@AllowColumnPicking" AllowColumnReorder="@AllowColumnReorder" ColumnWidth="@ColumnWidth" EmptyText="@EmptyText" @ref="grid" Data="@(LoadData.HasDelegate ? (Data != null ? Data.Cast<object>() : Enumerable.Empty<object>()) : pagedData)" Count="@(LoadData.HasDelegate? Count : count)" LoadData="@OnLoadData" LoadChildData="@LoadChildData"
<RadzenDataGrid id="@GridID" TabIndex="-1" GridLines=@GridLines EmptyTemplate=@EmptyTemplate FooterTemplate=@FooterTemplate AllowVirtualization="@IsVirtualizationAllowed()" VirtualizationOverscanCount="@GetVirtualizationOverscanCount()" AllowRowSelectOnRowClick="@AllowRowSelectOnRowClick" PagerHorizontalAlign="@PagerHorizontalAlign" PagerAlwaysVisible="@PagerAlwaysVisible" Responsive="@Responsive" AllowColumnResize="@AllowColumnResize" AllowColumnPicking="@AllowColumnPicking" AllowColumnReorder="@AllowColumnReorder" ColumnWidth="@ColumnWidth" EmptyText="@EmptyText" @ref="grid" Data="@(LoadData.HasDelegate ? (Data != null ? Data.Cast<object>() : Enumerable.Empty<object>()) : pagedData)" Count="@(LoadData.HasDelegate? Count : count)" LoadData="@OnLoadData" LoadChildData="@LoadChildData"
TItem="object" ColumnReordered="@ColumnReordered" ColumnResized="@ColumnResized" ColumnReordering="@ColumnReordering" ContextMenu="@ContextMenuDataGrid"
CellRender="@OnCellRender" RowRender="@OnRowRender" ShowPagingSummary="@ShowPagingSummary" PagingSummaryFormat="@PagingSummaryFormat" PageSize="@PageSize" PageSizeOptions="@PageSizeOptions" PageNumbersCount="@PageNumbersCount" AllowPaging="@(!IsVirtualizationAllowed())" AllowSorting="@AllowSorting" RowSelect="@OnRowSelect" Style="@(IsVirtualizationAllowed() ? "height:285px" : "")" Density="@Density" IsLoading="@IsLoading" FirstPageTitle="@FirstPageTitle" FirstPageAriaLabel="@FirstPageAriaLabel" PrevPageAriaLabel="@PrevPageAriaLabel" PrevPageTitle="@PrevPageTitle" NextPageAriaLabel="@NextPageAriaLabel" NextPageTitle="@NextPageTitle" LastPageAriaLabel="@LastPageAriaLabel" LastPageTitle="@LastPageTitle" PageAriaLabelFormat="@PageAriaLabelFormat" PageTitleFormat="@PageTitleFormat">
<Columns>
@@ -196,7 +196,7 @@
}
else
{
<RadzenDataGrid TabIndex="-1" GridLines=@GridLines EmptyTemplate=@EmptyTemplate FooterTemplate=@FooterTemplate AllowVirtualization="@IsVirtualizationAllowed()" VirtualizationOverscanCount="@GetVirtualizationOverscanCount()" AllowRowSelectOnRowClick="@AllowRowSelectOnRowClick" PagerHorizontalAlign="@PagerHorizontalAlign" PagerAlwaysVisible="@PagerAlwaysVisible" Responsive="@Responsive" AllowColumnResize="@AllowColumnResize" AllowColumnPicking="@AllowColumnPicking" AllowColumnReorder="@AllowColumnReorder" ColumnWidth="@ColumnWidth" EmptyText="@EmptyText" @ref="grid" Data="@(LoadData.HasDelegate ? (Data != null ? Data.Cast<object>() : Enumerable.Empty<object>()) : pagedData)" Count="@(LoadData.HasDelegate? Count : count)" LoadData="@OnLoadData" LoadChildData="@LoadChildData"
<RadzenDataGrid id="@GridID" TabIndex="-1" GridLines=@GridLines EmptyTemplate=@EmptyTemplate FooterTemplate=@FooterTemplate AllowVirtualization="@IsVirtualizationAllowed()" VirtualizationOverscanCount="@GetVirtualizationOverscanCount()" AllowRowSelectOnRowClick="@AllowRowSelectOnRowClick" PagerHorizontalAlign="@PagerHorizontalAlign" PagerAlwaysVisible="@PagerAlwaysVisible" Responsive="@Responsive" AllowColumnResize="@AllowColumnResize" AllowColumnPicking="@AllowColumnPicking" AllowColumnReorder="@AllowColumnReorder" ColumnWidth="@ColumnWidth" EmptyText="@EmptyText" @ref="grid" Data="@(LoadData.HasDelegate ? (Data != null ? Data.Cast<object>() : Enumerable.Empty<object>()) : pagedData)" Count="@(LoadData.HasDelegate? Count : count)" LoadData="@OnLoadData" LoadChildData="@LoadChildData"
TItem="object" ColumnReordered="@ColumnReordered" ColumnResized="@ColumnResized" ColumnReordering="@ColumnReordering" ContextMenu="@ContextMenuDataGrid"
CellRender="@OnCellRender" RowRender="@OnRowRender" ShowPagingSummary="@ShowPagingSummary" PagingSummaryFormat="@PagingSummaryFormat" PageSize="@PageSize" PageSizeOptions="@PageSizeOptions" PageNumbersCount="@PageNumbersCount" AllowPaging="@(!IsVirtualizationAllowed())" AllowSorting="@AllowSorting" RowSelect="@OnRowSelect" Style="@(IsVirtualizationAllowed() ? "height:285px" : "")" Density="@Density" IsLoading="@IsLoading" FirstPageTitle="@FirstPageTitle" FirstPageAriaLabel="@FirstPageAriaLabel" PrevPageAriaLabel="@PrevPageAriaLabel" PrevPageTitle="@PrevPageTitle" NextPageAriaLabel="@NextPageAriaLabel" NextPageTitle="@NextPageTitle" LastPageAriaLabel="@LastPageAriaLabel" LastPageTitle="@LastPageTitle" PageAriaLabelFormat="@PageAriaLabelFormat" PageTitleFormat="@PageTitleFormat">
<Columns>

View File

@@ -523,6 +523,33 @@ namespace Radzen.Blazor
/// <returns>The active row identifier or null.</returns>
internal string? ActiveDescendantId => selectedIndex >= 0 && grid != null ? $"{grid.GridId()}-active-item" : null;
internal string GridID => $"{PopupID}-grid";
internal string? SelectedAriaLabel
{
get
{
if (!Multiple)
{
return internalValue != null ? $"{PropertyAccess.GetItemOrValueFromProperty(internalValue, TextProperty)}" : EmptyAriaLabel;
}
var itemsToUse = SelectedValue is IEnumerable && SelectedValue is not string ? ((IEnumerable)SelectedValue).Cast<object>().ToHashSet() : selectedItems;
if (itemsToUse.Count == 0)
{
return EmptyAriaLabel;
}
if (itemsToUse.Count < MaxSelectedLabels)
{
return string.Join(Separator, itemsToUse.Select(i => $"{PropertyAccess.GetItemOrValueFromProperty(i, TextProperty)}"));
}
return $"{itemsToUse.Count} {SelectedItemsText}";
}
}
/// <summary>
/// Gets or sets the number of maximum selected labels.
/// </summary>
@@ -1100,6 +1127,12 @@ namespace Radzen.Blazor
await OnSelectItem(null, true);
}
if (Multiple && !isFilter && (selectedItems.Count > 0 || SelectedValue is IEnumerable && SelectedValue is not string))
{
selectedIndex = -1;
await Clear();
}
if (AllowFiltering && isFilter)
{
Debounce(DebounceFilter, FilterDelay);

View File

@@ -2,5 +2,5 @@
@if (Visible)
{
<i @ref="@Element" style="@getStyle()" @attributes="Attributes" class="@GetCssClass()" id="@GetId()">@Icon</i>
<i @ref="@Element" style="@getStyle()" aria-hidden="@getAriaHidden()" @attributes="Attributes" class="@GetCssClass()" id="@GetId()">@Icon</i>
}

View File

@@ -64,5 +64,10 @@ namespace Radzen.Blazor
{
return $"{(!string.IsNullOrEmpty(IconColor) ? $"color:{IconColor};" : null)}{(!string.IsNullOrEmpty(Style) ? Style : null)}";
}
string? getAriaHidden()
{
return Attributes != null && (Attributes.ContainsKey("aria-label") || Attributes.ContainsKey("role")) ? null : "true";
}
}
}

View File

@@ -6,10 +6,9 @@
@inherits DropDownBase<TValue>
@if (Visible)
{
<div @ref="@Element" style="@Style" @attributes="Attributes" class="@GetCssClass()" tabindex="@(Disabled ? "-1" : $"{TabIndex}")" @onkeydown="@OnKeyDown" id="@GetId()"
role="listbox" aria-label="@ListboxAriaLabel" aria-multiselectable="@(Multiple ? "true" : "false")" aria-disabled="@(Disabled ? "true" : "false")" aria-activedescendant="@ActiveDescendantId">
<div @ref="@Element" style="@Style" @attributes="Attributes" class="@GetCssClass()" @onkeydown="@OnKeyDown" id="@GetId()">
<div class="rz-helper-hidden-accessible">
<input name="@Name" readonly="@ReadOnly" disabled="@Disabled" type="text" aria-label="@(!Multiple && internalValue != null ? internalValue : EmptyAriaLabel)" @attributes=@InputAttributes />
<input name="@Name" readonly="@ReadOnly" disabled="@Disabled" type="text" tabindex="-1" aria-hidden="true" aria-label="@(!Multiple && internalValue != null ? internalValue : EmptyAriaLabel)" @attributes=@InputAttributes />
</div>
@if (View != null && (AllowFiltering || (Multiple && AllowSelectAll) || HeaderTemplate != null))
{
@@ -44,7 +43,7 @@
@if (AllowFiltering)
{
<div class="rz-listbox-filter-container">
<input id="@SearchID" @ref="@search" disabled="@Disabled" class="rz-inputtext" role="textbox" type="text" placeholder="@(Placeholder ?? string.Empty)"
<input id="@SearchID" @ref="@search" disabled="@Disabled" class="rz-inputtext" type="text" placeholder="@(Placeholder ?? string.Empty)"
@onchange="@OnFilter" @onkeydown="@OnFilterKeyDown" value="@(searchText ?? string.Empty)" @onkeydown:stopPropagation="stopKeydownPropagation"
@oninput="@OnFilterInput" aria-label="@SearchAriaLabel" />
<span class="notranslate rz-listbox-filter-icon rzi rzi-search"></span>
@@ -56,7 +55,9 @@
@if (View != null)
{
<div class="rz-listbox-list-wrapper">
<ul @ref="list" id="@ListId" class="rz-listbox-list" role="presentation">
<ul @ref="list" id="@ListId" class="rz-listbox-list" role="listbox" tabindex="@(Disabled ? "-1" : $"{TabIndex}")"
aria-label="@ListboxAriaLabel" aria-labelledby="@ListboxAriaLabelledBy" aria-multiselectable="@(Multiple ? "true" : "false")"
aria-disabled="@(Disabled ? "true" : "false")" aria-activedescendant="@ActiveDescendantId">
@RenderItems()
</ul>
@if (!View.Cast<object>().Any())

View File

@@ -125,15 +125,20 @@ namespace Radzen.Blazor
internal string? ActiveDescendantId => selectedIndex >= 0 ? GetItemId(selectedIndex) : null;
/// <summary>
/// Gets the accessible name applied to the listbox, falling back to the empty selection label
/// unless the consumer supplies an explicit aria-label or aria-labelledby attribute.
/// Gets the accessible name applied to the listbox. Uses the consumer supplied aria-label attribute when present,
/// falls back to the empty selection label unless the consumer supplies an aria-labelledby attribute.
/// </summary>
/// <returns>The accessible name or null when one is supplied via attributes.</returns>
/// <returns>The accessible name or null when aria-labelledby is supplied via attributes.</returns>
internal string? ListboxAriaLabel
{
get
{
if (Attributes != null && (Attributes.ContainsKey("aria-label") || Attributes.ContainsKey("aria-labelledby")))
if (Attributes != null && Attributes.TryGetValue("aria-label", out var ariaLabel))
{
return Convert.ToString(ariaLabel, Culture);
}
if (Attributes != null && Attributes.ContainsKey("aria-labelledby"))
{
return null;
}
@@ -142,6 +147,12 @@ namespace Radzen.Blazor
}
}
/// <summary>
/// Gets the consumer supplied aria-labelledby attribute value applied to the listbox, or null when not supplied.
/// </summary>
/// <returns>The labelling element identifier or null.</returns>
internal string? ListboxAriaLabelledBy => Attributes != null && Attributes.TryGetValue("aria-labelledby", out var ariaLabelledBy) ? Convert.ToString(ariaLabelledBy, Culture) : null;
/// <summary>
/// Handles the key down event.
/// </summary>

View File

@@ -8,7 +8,7 @@
@onfocus=@OnFocus>
@if (Responsive)
{
<li class="rz-menu-toggle-item">
<li class="rz-menu-toggle-item" role="none">
<button type="button" aria-label=@ToggleAriaLabel class="rz-menu-toggle" @onclick=@OnToggle
aria-haspopup="menu" aria-expanded="@IsOpen.ToString().ToLowerInvariant()" aria-controls="@GetId()">
@if (IsOpen)

View File

@@ -12,11 +12,10 @@
{ "role", "button" },
{ "tabindex", "0" }
} : null;
<div aria-live="polite" class="rz-notification-item-wrapper rz-open @classes.Item1" style="@(clickable ? "cursor: pointer;" : "") @Style"
@attributes="wrapperAttributes" @onclick="NotificationClicked" @onkeydown="OnKeyDown">
<div role="@(Message?.Severity == NotificationSeverity.Error ? "alert" : null)" class="rz-notification-item-wrapper rz-open @classes.Item1" style="@(clickable ? "cursor: pointer;" : "") @Style">
<div class="rz-notification-item">
<div class="rz-notification-message-wrapper">
<span class="notranslate rzi rz-notification-icon @classes.Item2"></span>
<div class="rz-notification-message-wrapper" @attributes="wrapperAttributes" @onclick="NotificationClicked" @onkeydown="OnKeyDown">
<span class="notranslate rzi rz-notification-icon @classes.Item2" aria-hidden="true"></span>
<div class="rz-notification-message">
<div class="rz-notification-title">@if (Message?.SummaryContent != null && Service != null) { @Message.SummaryContent(Service) } else { @Message?.Summary }</div>
<div class="rz-notification-content">@if (Message?.DetailContent != null && Service != null) { @Message.DetailContent(Service) } else { @Message?.Detail }</div>

View File

@@ -2,9 +2,8 @@
@if (GetVisible())
{
<nav @ref="@Element" @attributes="Attributes" class="@GetCssClass()" style="@Style" id="@GetId()"
aria-label="@NavigationAriaLabel" aria-activedescendant="@ActiveDescendantId"
@onkeydown="@OnKeyDown" @onkeydown:preventDefault="preventKeyDown" @onkeydown:stopPropagation="stopKeydownPropagation" tabindex=0
@onfocus=@this.AsNonRenderingEventHandler(OnFocus)>
aria-label="@NavigationAriaLabel"
@onkeydown="@OnKeyDown" @onkeydown:preventDefault="preventKeyDown" @onkeydown:stopPropagation="stopKeydownPropagation">
@if(ShowPagingSummary)
{
<span class="rz-pager-summary">
@@ -20,13 +19,13 @@
}
<ul class="rz-pager-pages" role="list">
<li class="rz-pager-item">
<button id="@(GetId() + "fp")" type="button" tabindex="-1" class="rz-pager-first rz-pager-element @(skip > 0 ? "": "rz-state-disabled") @(focusedIndex == -2 ? "rz-state-focused": "")"
<button id="@(GetId() + "fp")" type="button" tabindex="@GetButtonTabIndex(-2)" class="rz-pager-first rz-pager-element @(skip > 0 ? "": "rz-state-disabled") @(focusedIndex == -2 ? "rz-state-focused": "")"
@onclick="@(() => OnFirstPageClick())" aria-label="@FirstPageAriaLabel" title="@FirstPageTitle" disabled="@(CurrentPage <= 0)" aria-disabled="@(CurrentPage <= 0 ? "true" : "false")">
<span class="notranslate rz-pager-icon rzi rzi-step-backward"></span>
</button>
</li>
<li class="rz-pager-item">
<button id="@(GetId() + "pp")" type="button" tabindex="-1" class="rz-pager-prev rz-pager-element @(skip > 0 ? "": "rz-state-disabled") @(focusedIndex == -1 ? "rz-state-focused": "")"
<button id="@(GetId() + "pp")" type="button" tabindex="@GetButtonTabIndex(-1)" class="rz-pager-prev rz-pager-element @(skip > 0 ? "": "rz-state-disabled") @(focusedIndex == -1 ? "rz-state-focused": "")"
@onclick="@(() => OnPrevPageClick())" aria-label="@PrevPageAriaLabel" title="@PrevPageTitle" disabled="@(CurrentPage <= 0)" aria-disabled="@(CurrentPage <= 0 ? "true" : "false")">
<span class="notranslate rz-pager-icon rzi rzi-caret-left"></span>
@if (PrevPageLabel != null)
@@ -38,13 +37,13 @@
@foreach (var i in Enumerable.Range(startPage, Math.Min(endPage + 1, PageNumbersCount)))
{
<li class="rz-pager-item">
<button id="@(GetId() + i.ToString() + "p")" type="button" tabindex="-1" class="rz-pager-page rz-pager-element @(i == CurrentPage ? "rz-state-active" : "") @(startPage + focusedIndex == i ? "rz-state-focused": "")"
<button id="@(GetId() + i.ToString() + "p")" type="button" tabindex="@GetButtonTabIndex(i - startPage)" class="rz-pager-page rz-pager-element @(i == CurrentPage ? "rz-state-active" : "") @(startPage + focusedIndex == i ? "rz-state-focused": "")"
@onclick="@(() => OnPageClick(i, startPage))" aria-current="@(i == CurrentPage ? "page" : null)" aria-label="@string.Format(PageAriaLabelFormat, (i + 1).ToString())"
title="@string.Format(PageTitleFormat, (i + 1).ToString())">@(i + 1)</button>
</li>
}
<li class="rz-pager-item">
<button id="@(GetId() + "np")" type="button" tabindex="-1" class="rz-pager-next rz-pager-element @((CurrentPage != numberOfPages - 1) ? "": "rz-state-disabled") @(focusedIndex == Math.Min(endPage + 1, PageNumbersCount)? "rz-state-focused": "")"
<button id="@(GetId() + "np")" type="button" tabindex="@GetButtonTabIndex(Math.Min(endPage + 1, PageNumbersCount))" class="rz-pager-next rz-pager-element @((CurrentPage != numberOfPages - 1) ? "": "rz-state-disabled") @(focusedIndex == Math.Min(endPage + 1, PageNumbersCount)? "rz-state-focused": "")"
@onclick="@(() => OnNextPageClick(endPage))" aria-label="@NextPageAriaLabel" title="@NextPageTitle" disabled="@(CurrentPage >= (numberOfPages - 1))" aria-disabled="@(CurrentPage >= (numberOfPages - 1) ? "true" : "false")">
@if (NextPageLabel != null)
{
@@ -54,7 +53,7 @@
</button>
</li>
<li class="rz-pager-item">
<button id="@(GetId() + "lp")" type="button" tabindex="-1" class="rz-pager-last rz-pager-element @((CurrentPage != numberOfPages - 1) ? "": "rz-state-disabled") @(focusedIndex == Math.Min(endPage + 1, PageNumbersCount) + 1 ? "rz-state-focused": "")"
<button id="@(GetId() + "lp")" type="button" tabindex="@GetButtonTabIndex(Math.Min(endPage + 1, PageNumbersCount) + 1)" class="rz-pager-last rz-pager-element @((CurrentPage != numberOfPages - 1) ? "": "rz-state-disabled") @(focusedIndex == Math.Min(endPage + 1, PageNumbersCount) + 1 ? "rz-state-focused": "")"
@onclick="@(() => OnLastPageClick(endPage))" aria-label="@LastPageAriaLabel" title="@LastPageTitle" disabled="@(CurrentPage >= (numberOfPages - 1))" aria-disabled="@(CurrentPage >= (numberOfPages - 1) ? "true" : "false")">
<span class="notranslate rz-pager-icon rzi rzi-step-forward"></span>
</button>
@@ -62,7 +61,7 @@
@if(AllowReload)
{
<li class="rz-pager-item">
<button id="@(GetId() + "rl")" type="button" tabindex="-1" class="rz-pager-reload rz-pager-element"
<button id="@(GetId() + "rl")" type="button" tabindex="@TabIndex" class="rz-pager-reload rz-pager-element"
@onclick="@OnReloadClick" aria-label="@ReloadAriaLabel" title="@ReloadTitle">
<span class="notranslate rz-pager-icon rz-pager-reload-icon rzi rzi-reload"></span>
</button>
@@ -72,7 +71,7 @@
@if(PageSizeOptions != null && PageSizeOptions.Any())
{
<RadzenDropDown TValue="int" Data="@PageSizeOptions" Value="@PageSize" Change="@OnPageSizeChanged" />
<RadzenDropDown TValue="int" Data="@PageSizeOptions" Value="@PageSize" Change="@OnPageSizeChanged" InputAttributes="@(new Dictionary<string, object>() { { "aria-label", PageSizeText } })" />
<span class="rz-pagesize-text">@PageSizeText</span>
}
</nav>

View File

@@ -420,6 +420,7 @@ namespace Radzen.Blazor
if (skip == 0)
{
focusedIndex = focusedIndex + 2;
shouldFocus = true;
}
}
@@ -432,6 +433,7 @@ namespace Radzen.Blazor
if (skip == 0)
{
focusedIndex++;
shouldFocus = true;
}
}
@@ -450,6 +452,7 @@ namespace Radzen.Blazor
if (CurrentPage == numberOfPages - 1)
{
focusedIndex--;
shouldFocus = true;
}
}
@@ -462,6 +465,7 @@ namespace Radzen.Blazor
if (CurrentPage == numberOfPages - 1)
{
focusedIndex = focusedIndex - 2;
shouldFocus = true;
}
}
@@ -550,35 +554,58 @@ namespace Radzen.Blazor
bool stopKeydownPropagation;
int focusedIndex = -3;
string? ActiveDescendantId
/// <summary>
/// Gets or sets the tabindex applied to the currently active pager button. All other pager buttons get tabindex <c>-1</c> following the roving tabindex pattern.
/// </summary>
/// <value>The tabindex of the active pager button. Default is <c>0</c>.</value>
[Parameter]
public int TabIndex { get; set; }
int RovingIndex
{
get
{
if (focusedIndex == -3)
{
return null;
var numberOfDisplayedPages = Math.Min(endPage + 1, PageNumbersCount);
return Math.Clamp(CurrentPage - startPage, 0, Math.Max(numberOfDisplayedPages - 1, 0));
}
var numberOfDisplayedPages = Math.Min(endPage + 1, PageNumbersCount);
return focusedIndex;
}
}
if (focusedIndex == -2)
string GetButtonTabIndex(int index)
{
return index == RovingIndex ? TabIndex.ToString(System.Globalization.CultureInfo.InvariantCulture) : "-1";
}
string ActiveButtonId
{
get
{
var numberOfDisplayedPages = Math.Min(endPage + 1, PageNumbersCount);
var index = RovingIndex;
if (index == -2)
{
return $"{GetId()}fp";
}
if (focusedIndex == -1)
if (index == -1)
{
return $"{GetId()}pp";
}
if (focusedIndex == numberOfDisplayedPages)
if (index == numberOfDisplayedPages)
{
return $"{GetId()}np";
}
if (focusedIndex == numberOfDisplayedPages + 1)
if (index == numberOfDisplayedPages + 1)
{
return $"{GetId()}lp";
}
return $"{GetId()}{startPage + focusedIndex}p";
return $"{GetId()}{startPage + index}p";
}
}
@@ -586,58 +613,29 @@ namespace Radzen.Blazor
/// Handles the key down event.
/// </summary>
/// <param name="args">The <see cref="KeyboardEventArgs"/> instance containing the event data.</param>
protected virtual async Task OnKeyDown(KeyboardEventArgs args)
protected virtual Task OnKeyDown(KeyboardEventArgs args)
{
ArgumentNullException.ThrowIfNull(args);
var key = args.Code != null ? args.Code : args.Key;
var numberOfDisplayedPages = Math.Min(endPage + 1, PageNumbersCount);
if (key == "ArrowLeft" || key == "ArrowRight")
if (key == "ArrowLeft" || key == "ArrowRight" || key == "Home" || key == "End")
{
preventKeyDown = true;
stopKeydownPropagation = true;
focusedIndex = Math.Clamp(focusedIndex + (key == "ArrowLeft" ? -1 : 1), -2, numberOfDisplayedPages + 1);
if (CurrentPage == 0 && focusedIndex < 0)
if (key == "Home")
{
focusedIndex = 0;
focusedIndex = -2;
}
else if (CurrentPage == numberOfPages - 1 && focusedIndex > numberOfDisplayedPages - 1)
else if (key == "End")
{
focusedIndex = numberOfDisplayedPages - 1;
focusedIndex = numberOfDisplayedPages + 1;
}
}
else if (key == "Space" || key == "Enter")
{
preventKeyDown = true;
stopKeydownPropagation = true;
if (focusedIndex == -2)
else
{
await FirstPage();
shouldFocus = true;
}
else if (focusedIndex == -1)
{
await PrevPage();
shouldFocus = true;
}
else if (focusedIndex == numberOfDisplayedPages)
{
await NextPage();
shouldFocus = true;
}
else if (focusedIndex == numberOfDisplayedPages + 1)
{
await LastPage();
shouldFocus = true;
}
else
{
await GoToPage(focusedIndex + startPage);
shouldFocus = true;
focusedIndex = Math.Clamp(RovingIndex + (key == "ArrowLeft" ? -1 : 1), -2, numberOfDisplayedPages + 1);
}
if (CurrentPage == 0 && focusedIndex < 0)
@@ -648,31 +646,20 @@ namespace Radzen.Blazor
{
focusedIndex = numberOfDisplayedPages - 1;
}
shouldFocus = true;
}
else
{
preventKeyDown = false;
stopKeydownPropagation = false;
shouldFocus = false;
}
return Task.CompletedTask;
}
bool shouldFocus;
void OnFocus()
{
focusedIndex = focusedIndex == -3 ? 0 : focusedIndex;
if (CurrentPage == 0 && focusedIndex < 0)
{
focusedIndex = 0;
}
else if (CurrentPage == numberOfPages - 1 && focusedIndex > numberOfPages - 1)
{
focusedIndex = numberOfPages - 1;
}
}
/// <inheritdoc />
protected override async Task OnAfterRenderAsync(bool firstRender)
{
@@ -681,8 +668,14 @@ namespace Radzen.Blazor
if (shouldFocus && JSRuntime != null)
{
shouldFocus = false;
await JSRuntime.InvokeVoidAsync("Radzen.focusElement", GetId());
}
try
{
await JSRuntime.InvokeVoidAsync("Radzen.focusElement", ActiveButtonId);
}
catch (Exception)
{
}
}
}
}
}

View File

@@ -5,7 +5,6 @@
{
<li @ref=@Element style=@Style @attributes=@Attributes class="@(GetCssClass() + ExpandableClass)" id=@GetId()
role="menuitem" tabindex="-1" aria-disabled="@(Disabled ? "true" : "false")"
aria-haspopup="@(ChildContent != null ? "menu" : null)"
aria-controls="@(RenderSubmenu ? $"{GetId()}-submenu" : null)"
aria-expanded="@(ChildContent != null ? expanded.ToString().ToLowerInvariant() : null)"
@onclick=@(this.AsNonRenderingEventHandler<MouseEventArgs>(OnClick)) @onclick:stopPropagation @onkeydown="OnKeyDown">
@@ -38,9 +37,7 @@
else
{
<div class="rz-navigation-item-link" style="@(Parent?.DisplayStyle == MenuItemDisplayStyle.Icon?"margin-inline-end:0px;":"")"
role="button" tabindex="-1" aria-expanded="@expanded.ToString().ToLowerInvariant()"
aria-controls="@(RenderSubmenu ? $"{GetId()}-submenu" : null)"
aria-haspopup="@(ChildContent != null ? "menu" : null)"
tabindex="-1"
@onclick="@Toggle" @onkeydown="OnToggleKeyDown">
@if (!string.IsNullOrEmpty(Icon) && Parent?.DisplayStyle != MenuItemDisplayStyle.Text)
{

View File

@@ -10,7 +10,9 @@
<RadzenStack class="rz-picklist-source-wrapper">
@if (ShowHeader && SourceHeader != null)
{
@SourceHeader
<div id="@(GetId() + "-source-header")">
@SourceHeader
</div>
}
<CascadingValue Value="@((EditContext?)null)" IsFixed=true>
<RadzenListBox @ref=sourceListBox Value=@selectedSourceItems ValueChanged="@(EventCallback.Factory.Create<object>(this, OnSelectedSourceItemsChanged))" Data="@source" Multiple="@Multiple" @bind-SearchText=@sourceSearchText
@@ -20,23 +22,27 @@
Disabled=@(Disabled || Source == null || (Source != null && !Source.Any()))
EmptyText="@(SourceEmptyText ?? EmptyText)" EmptyTemplate="@(SourceEmptyTemplate ?? EmptyTemplate)"
AllowVirtualization="@AllowVirtualization"
aria-label="@(ShowHeader && SourceHeader != null ? null : SourceAriaLabel)"
aria-labelledby="@(ShowHeader && SourceHeader != null ? GetId() + "-source-header" : null)"
/>
</CascadingValue>
</RadzenStack>
<RadzenStack Orientation="@(Orientation == Orientation.Vertical ? Orientation.Horizontal : Orientation.Vertical)" JustifyContent="@ButtonJustifyContent" Gap="@ButtonGap" class="rz-picklist-buttons">
<RadzenButton Icon="@SelectedSourceToTargetIcon" title="@SelectedSourceToTargetTitle" Click="@SelectedSourceToTarget" Disabled=@(Disabled || selectedSourceItems == null || (Multiple && (selectedSourceItems as IEnumerable)?.Cast<object>().Any() != true))
<RadzenButton Icon="@SelectedSourceToTargetIcon" title="@SelectedSourceToTargetTitle" aria-label="@SelectedSourceToTargetTitle" Click="@SelectedSourceToTarget" Disabled=@(Disabled || selectedSourceItems == null || (Multiple && (selectedSourceItems as IEnumerable)?.Cast<object>().Any() != true))
ButtonStyle="@ButtonStyle" Size="@ButtonSize" Variant="@ButtonVariant" Shade="@ButtonShade" />
<RadzenButton Icon="@SelectedTargetToSourceIcon" title="@SelectedTargetToSourceTitle" Click="@SelectedTargetToSource" Disabled=@(Disabled || selectedTargetItems == null || (Multiple && (selectedTargetItems as IEnumerable)?.Cast<object>().Any() != true))
<RadzenButton Icon="@SelectedTargetToSourceIcon" title="@SelectedTargetToSourceTitle" aria-label="@SelectedTargetToSourceTitle" Click="@SelectedTargetToSource" Disabled=@(Disabled || selectedTargetItems == null || (Multiple && (selectedTargetItems as IEnumerable)?.Cast<object>().Any() != true))
ButtonStyle="@ButtonStyle" Size="@ButtonSize" Variant="@ButtonVariant" Shade="@ButtonShade"/>
<RadzenButton Icon="@SourceToTargetIcon" title="@SourceToTargetTitle" Click="@SourceToTarget" Disabled=@(Disabled || source == null || (source != null && !source.Any()))
<RadzenButton Icon="@SourceToTargetIcon" title="@SourceToTargetTitle" aria-label="@SourceToTargetTitle" Click="@SourceToTarget" Disabled=@(Disabled || source == null || (source != null && !source.Any()))
ButtonStyle="@ButtonStyle" Size="@ButtonSize" Variant="@ButtonVariant" Shade="@ButtonShade" Visible=@(AllowMoveAll && AllowMoveAllSourceToTarget) />
<RadzenButton Icon="@TargetToSourceIcon" title="@TargetToSourceTitle" Click="@TargetToSource" Disabled=@(Disabled || target == null|| (target != null && !target.Any()))
<RadzenButton Icon="@TargetToSourceIcon" title="@TargetToSourceTitle" aria-label="@TargetToSourceTitle" Click="@TargetToSource" Disabled=@(Disabled || target == null|| (target != null && !target.Any()))
ButtonStyle="@ButtonStyle" Size="@ButtonSize" Variant="@ButtonVariant" Shade="@ButtonShade" Visible=@(AllowMoveAll && AllowMoveAllTargetToSource)/>
</RadzenStack>
<RadzenStack class="rz-picklist-target-wrapper">
@if (ShowHeader && TargetHeader != null)
{
@TargetHeader
<div id="@(GetId() + "-target-header")">
@TargetHeader
</div>
}
<CascadingValue Value="@((EditContext?)null)" IsFixed=true>
<RadzenListBox @ref=targetListBox Value=@selectedTargetItems ValueChanged="@(EventCallback.Factory.Create<object>(this, OnSelectedTargetItemsChanged))" Data="@target" Multiple="@Multiple" @bind-SearchText=@targetSearchText
@@ -46,6 +52,8 @@
Disabled=@(Disabled || Target == null|| (Target != null && !Target.Any()))
EmptyText="@(TargetEmptyText ?? EmptyText)" EmptyTemplate="@(TargetEmptyTemplate ?? EmptyTemplate)"
AllowVirtualization="@AllowVirtualization"
aria-label="@(ShowHeader && TargetHeader != null ? null : TargetAriaLabel)"
aria-labelledby="@(ShowHeader && TargetHeader != null ? GetId() + "-target-header" : null)"
/>
</CascadingValue>
</RadzenStack>

View File

@@ -188,6 +188,24 @@ namespace Radzen.Blazor
[Parameter]
public string EmptyText { get => emptyText ?? Localize(nameof(RadzenStrings.PickList_EmptyText)); set => emptyText = value; }
private string? sourceAriaLabel;
/// <summary>
/// Gets or sets the aria-label of the source list. Ignored when <see cref="SourceHeader"/> is rendered - the source list is labelled by the header instead.
/// </summary>
/// <value>The source list aria-label.</value>
[Parameter]
public string SourceAriaLabel { get => sourceAriaLabel ?? Localize(nameof(RadzenStrings.PickList_SourceAriaLabel)); set => sourceAriaLabel = value; }
private string? targetAriaLabel;
/// <summary>
/// Gets or sets the aria-label of the target list. Ignored when <see cref="TargetHeader"/> is rendered - the target list is labelled by the header instead.
/// </summary>
/// <value>The target list aria-label.</value>
[Parameter]
public string TargetAriaLabel { get => targetAriaLabel ?? Localize(nameof(RadzenStrings.PickList_TargetAriaLabel)); set => targetAriaLabel = value; }
/// <summary>
/// Gets or sets the empty text shown when the source list has no items. Overrides <see cref="EmptyText"/>.
/// </summary>

View File

@@ -6,7 +6,7 @@
role="presentation" tabindex="-1">
<li class="rz-navigation-item" role="presentation">
<div @ref="@toggleElement" class="rz-navigation-item-wrapper" role="button" tabindex="0" aria-haspopup="menu"
aria-label="@ToggleAriaLabel" aria-activedescendant="@ActiveDescendantId"
aria-label="@ToggleAriaLabel"
aria-expanded="@((!Collapsed).ToString().ToLowerInvariant())" aria-controls="@($"{GetId()}-menu")"
@onkeydown="@OnKeyPress" @onkeydown:preventDefault=preventKeyPress @onkeydown:stopPropagation="stopKeydownPropagation">
<div class="rz-navigation-item-link">
@@ -24,7 +24,8 @@
</div>
<ul @ref="@menuElement" id="@($"{GetId()}-menu")" class="rz-navigation-menu" role="menu" style="@contentStyle"
tabindex="-1" aria-label="@ToggleAriaLabel" aria-hidden="@(Collapsed ? "true" : "false")"
@onkeydown:stopPropagation="stopGuardKeydownPropagation" @onkeydown="OnGuardKeyDown">
aria-activedescendant="@ActiveDescendantId"
@onkeydown="@OnKeyPress" @onkeydown:preventDefault=preventKeyPress @onkeydown:stopPropagation="stopKeydownPropagation">
<CascadingValue Value=this>
@ChildContent
</CascadingValue>

View File

@@ -41,6 +41,25 @@ namespace Radzen.Blazor
_jsRef = await JSRuntime.InvokeAsync<IJSObjectReference>(
"Radzen.createProfileMenu", Element);
}
if (shouldFocusMenu)
{
shouldFocusMenu = false;
try
{
await menuElement.FocusAsync(preventScroll: true);
}
catch (InvalidOperationException)
{
}
catch (JSDisconnectedException)
{
}
catch (JSException)
{
}
}
}
/// <inheritdoc />
@@ -134,6 +153,8 @@ namespace Radzen.Blazor
internal int focusedIndex = -1;
bool shouldFocusMenu;
bool preventKeyPress = true;
bool stopKeydownPropagation;
async Task OnKeyPress(KeyboardEventArgs args)
@@ -150,6 +171,8 @@ namespace Radzen.Blazor
await Toggle(new MouseEventArgs());
focusedIndex = key == "ArrowUp" ? items.Count - 1 : 0;
shouldFocusMenu = true;
}
else if (items.Count > 0)
{
@@ -165,6 +188,8 @@ namespace Radzen.Blazor
if (Collapsed)
{
await Toggle(new MouseEventArgs());
shouldFocusMenu = true;
}
focusedIndex = key == "Home" ? 0 : items.Count - 1;
@@ -194,6 +219,8 @@ namespace Radzen.Blazor
if (!Collapsed)
{
focusedIndex = focusedIndex != -1 ? focusedIndex : 0;
shouldFocusMenu = true;
}
}
}

View File

@@ -4,7 +4,7 @@
@if (Visible)
{
<div @ref="@Element" style="@($"--rz-progressbar-value: {(NormalizedValue * 100).ToInvariantString()}%;{Style}")" aria-valuemax="@Max" aria-valuemin="@Min" role="progressbar" @attributes="Attributes" class="@GetCssClass()"
aria-valuenow="@Value" aria-label="@AriaLabel" id="@GetId()">
aria-valuenow="@(Mode == ProgressBarMode.Indeterminate ? null : (double?)Value)" aria-label="@AriaLabel" id="@GetId()">
<div class="rz-progressbar-value"></div>
@if (ShowValue)
{

View File

@@ -4,7 +4,7 @@
@if (Visible)
{
<div @ref="@Element" style="@Style" aria-valuemax="@Max" aria-valuemin="@Min" role="progressbar" @attributes="Attributes" class="@GetCssClass()"
aria-valuenow="@Value" aria-label="@AriaLabel" id="@GetId()">
aria-valuenow="@(Mode == ProgressBarMode.Indeterminate ? null : (double?)Value)" aria-label="@AriaLabel" id="@GetId()">
<svg class="rz-progressbar-circular-viewbox" viewBox="-19 -19 38 38">
<circle class="rz-progressbar-circular-background" r="15.91549" fill="none" />
<circle class="rz-progressbar-circular-value" r="15.91549" fill="none" stroke-dashoffset="@(((1 - NormalizedValue) * 100).ToInvariantString())" />

View File

@@ -3,20 +3,20 @@
@inherits FormComponent<int>
@if (Visible)
{
<div @ref="@Element" style=@Style @attributes="Attributes" class="@GetCssClass()" id="@GetId()">
<div @ref="@Element" style=@Style @attributes="Attributes" class="@GetCssClass()" id="@GetId()" role="radiogroup" aria-label="@AriaLabel" aria-readonly="@(ReadOnly ? "true" : null)">
@if (!ReadOnly)
{
<button type="button" id="@(GetId() + "cl")" aria-label="@ClearAriaLabel" @onclick="@(args => SetValue(0))" class="rz-rating-cancel"
tabindex="@(Disabled ? "-1" : $"{TabIndex}")"
@onkeypress="@(args => OnKeyPress(args, SetValue(0)))" @onkeypress:preventDefault=preventKeyPress @onkeypress:stopPropagation="stopKeypressPropagation">
tabindex="@(Disabled ? "-1" : $"{TabIndex}")">
<span class="notranslate rz-rating-icon rzi rzi-ban"></span>
</button>
}
@foreach (var index in Enumerable.Range(1, Stars))
{
<button type="button" id="@(GetId() + index.ToString() + "r")" aria-label="@RateAriaLabel" @onclick="@(args => SetValue(index))"
tabindex="@(Disabled ? "-1" : $"{TabIndex}")"
@onkeypress="@(args => OnKeyPress(args, SetValue(index)))" @onkeypress:preventDefault=preventKeyPress @onkeypress:stopPropagation="stopKeypressPropagation">
<button type="button" id="@(GetId() + index.ToString() + "r")" role="radio" aria-checked="@(index == Value ? "true" : "false")"
aria-posinset="@index" aria-setsize="@Stars" aria-label="@($"{RateAriaLabel} {index}")" @onclick="@(args => SetValue(index))"
tabindex="@(Disabled ? "-1" : index == FocusableStar ? $"{TabIndex}" : "-1")"
@onkeydown="@(args => OnKeyDown(args, index))" @onkeydown:preventDefault=preventKeyDown @onkeydown:stopPropagation=preventKeyDown>
<span class="notranslate rz-rating-icon rzi @(index <= Value ? "rzi-star": "rzi-star-o")"></span>
</button>
}

View File

@@ -1,5 +1,7 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.JSInterop;
using System;
using System.Threading.Tasks;
namespace Radzen.Blazor
@@ -42,6 +44,16 @@ namespace Radzen.Blazor
[Parameter]
public int Stars { get; set; } = 5;
private string? ariaLabel;
/// <summary>
/// Gets or sets the accessible label text for the rating radio group.
/// Used by screen readers to announce the purpose of the component.
/// </summary>
/// <value>The ARIA label for the rating group. Default is "Rating".</value>
[Parameter]
public string AriaLabel { get => ariaLabel ?? Localize(nameof(RadzenStrings.Rating_AriaLabel)); set => ariaLabel = value; }
private string? clearAriaLabel;
/// <summary>
@@ -83,23 +95,35 @@ namespace Radzen.Blazor
}
}
bool preventKeyPress = true;
bool stopKeypressPropagation;
async Task OnKeyPress(KeyboardEventArgs args, Task task)
int FocusableStar => Value >= 1 && Value <= Stars ? Value : 1;
bool preventKeyDown;
async Task OnKeyDown(KeyboardEventArgs args, int index)
{
var key = args.Code != null ? args.Code : args.Key;
if (key == "Space" || key == "Enter")
if (key == "ArrowRight" || key == "ArrowUp" || key == "ArrowLeft" || key == "ArrowDown")
{
preventKeyPress = true;
stopKeypressPropagation = true;
preventKeyDown = true;
await task;
if (Disabled || ReadOnly)
{
return;
}
var next = key == "ArrowRight" || key == "ArrowUp" ? Math.Min(index + 1, Stars) : Math.Max(index - 1, 1);
await SetValue(next);
if (JSRuntime != null)
{
await JSRuntime.InvokeVoidAsync("Radzen.focusElement", GetId() + next.ToString(System.Globalization.CultureInfo.InvariantCulture) + "r");
}
}
else
{
preventKeyPress = false;
stopKeypressPropagation = false;
preventKeyDown = false;
}
}
}

View File

@@ -20,18 +20,18 @@
<div class="rz-scheduler-nav-prev-next">
@if (ShowNavigationButtons)
{
<button type="button" tabindex="0" class="rz-button rz-prev" @onclick=@OnPrev title="@PrevText"><RadzenIcon Icon="chevron_left" /></button>
<button type="button" tabindex="0" class="rz-button rz-next" @onclick=@OnNext title="@NextText"><RadzenIcon Icon="chevron_right" /></button>
<button type="button" tabindex="0" class="rz-button rz-prev" @onclick=@OnPrev title="@PrevText" aria-label="@PrevText"><RadzenIcon Icon="chevron_left" /></button>
<button type="button" tabindex="0" class="rz-button rz-next" @onclick=@OnNext title="@NextText" aria-label="@NextText"><RadzenIcon Icon="chevron_right" /></button>
}
@if (ShowTodayButton)
{
<button type="button" tabindex="0" class="rz-button rz-today" @onclick=@OnToday title="@TodayText">@TodayText</button>
<button type="button" tabindex="0" class="rz-button rz-today" @onclick=@OnToday title="@TodayText" aria-label="@TodayText">@TodayText</button>
}
</div>
}
@if (ShowDateTitle)
{
<div class="rz-scheduler-nav-title">@SelectedView?.Title</div>
<div class="rz-scheduler-nav-title" aria-live="polite">@SelectedView?.Title</div>
}
<div class="rz-scheduler-nav-views">
@if (NavigationTemplate != null)
@@ -42,7 +42,7 @@
{
@foreach (var view in Views)
{
<RadzenButton Click=@(args => OnChangeView(view)) Icon=@view.Icon Text=@view.Text class="@($"{(IsSelected(view) ? " rz-state-active" : "")}")" />
<RadzenButton Click=@(args => OnChangeView(view)) Icon=@view.Icon Text=@view.Text aria-pressed="@(IsSelected(view) ? "true" : "false")" class="@($"{(IsSelected(view) ? " rz-state-active" : "")}")" />
}
}
</div>

View File

@@ -4,7 +4,7 @@
@inherits FormComponent<string>
@if (Visible)
{
<div @ref="@Element" style=@Style @attributes="Attributes" class="@GetCssClass()" id="@GetId()">
<div @ref="@Element" style=@Style @attributes="Attributes" class="@GetCssClass()" id="@GetId()" role="group" aria-label="@AriaLabel">
<div class="rz-helper-hidden-accessible">
<input disabled="@Disabled" type="hidden" name="@Name">
</div>
@@ -13,15 +13,15 @@
{
@if (Type == SecurityCodeType.Password)
{
<RadzenPassword Value="@ElementAt(index - 1)" Disabled="@Disabled" autocomplete="one-time-code" MaxLength="1" size="1" class="rz-security-code-input" />
<RadzenPassword Value="@ElementAt(index - 1)" Disabled="@Disabled" autocomplete="one-time-code" MaxLength="1" size="1" class="rz-security-code-input" aria-label="@GetInputAriaLabel(index)" />
}
else if (Type == SecurityCodeType.Numeric)
{
<RadzenTextBox inputmode="numeric" pattern="[0-9]*" Value="@ElementAt(index - 1)" Disabled="@Disabled" autocomplete="one-time-code" MaxLength="1" size="1" class="rz-security-code-input" />
<RadzenTextBox inputmode="numeric" pattern="[0-9]*" Value="@ElementAt(index - 1)" Disabled="@Disabled" autocomplete="one-time-code" MaxLength="1" size="1" class="rz-security-code-input" aria-label="@GetInputAriaLabel(index)" />
}
else
{
<RadzenTextBox Value="@ElementAt(index - 1)" Disabled="@Disabled" autocomplete="one-time-code" MaxLength="1" size="1" class="rz-security-code-input" />
<RadzenTextBox Value="@ElementAt(index - 1)" Disabled="@Disabled" autocomplete="one-time-code" MaxLength="1" size="1" class="rz-security-code-input" aria-label="@GetInputAriaLabel(index)" />
}
}
</RadzenStack>

View File

@@ -44,6 +44,30 @@ namespace Radzen.Blazor
[Parameter]
public string? Gap { get; set; }
private string? ariaLabel;
/// <summary>
/// Gets or sets the accessible label text of the security code group.
/// </summary>
/// <value>The ARIA label of the security code group. Default is "Security code".</value>
[Parameter]
public string AriaLabel { get => ariaLabel ?? Localize(nameof(RadzenStrings.SecurityCode_AriaLabel)); set => ariaLabel = value; }
private string? inputAriaLabelFormat;
/// <summary>
/// Gets or sets the format string used to build the accessible label of each input.
/// The first argument is the input position and the second one is the total number of inputs.
/// </summary>
/// <value>The ARIA label format of each input. Default is "Character {0} of {1}".</value>
[Parameter]
public string InputAriaLabelFormat { get => inputAriaLabelFormat ?? Localize(nameof(RadzenStrings.SecurityCode_InputAriaLabelFormat)); set => inputAriaLabelFormat = value; }
string GetInputAriaLabel(int index)
{
return string.Format(System.Globalization.CultureInfo.CurrentCulture, InputAriaLabelFormat, index, Count);
}
IJSObjectReference? _jsRef;
bool firstRender;

View File

@@ -304,16 +304,26 @@ namespace Radzen.Blazor
var direction = key == "ArrowLeft" || key == "ArrowUp" ? -1 : 1;
var start = focusedIndex < 0 ? 0 : focusedIndex;
var next = start + direction;
var next = start;
while (next >= 0 && next < navigableItems.Count && navigableItems[next].Disabled)
for (var step = 0; step < navigableItems.Count; step++)
{
next += direction;
next = (next + direction + navigableItems.Count) % navigableItems.Count;
if (!navigableItems[next].Disabled)
{
break;
}
}
if (next >= 0 && next < navigableItems.Count)
if (!navigableItems[next].Disabled)
{
focusedIndex = next;
if (!Multiple && !IsSelected(navigableItems[focusedIndex]))
{
await SelectItem(navigableItems[focusedIndex]);
}
}
}
else if (key == "Home" || key == "End")
@@ -337,6 +347,11 @@ namespace Radzen.Blazor
focusedIndex = index;
}
}
if (!Multiple && focusedIndex >= 0 && focusedIndex < navigableItems.Count && !navigableItems[focusedIndex].Disabled && !IsSelected(navigableItems[focusedIndex]))
{
await SelectItem(navigableItems[focusedIndex]);
}
}
else if (key == "Space" || key == "Enter")
{

View File

@@ -3,9 +3,8 @@
@inherits RadzenComponentWithChildren
@if (Visible)
{
<div @ref="@Element" style="@Style" @attributes="Attributes" class="@GetCssClass()" id="@GetId()"
tabindex="@(Disabled ? -1 : TabIndex)" role="button" aria-activedescendant="@ActiveDescendantId" aria-haspopup="menu" aria-controls="@PopupID" aria-expanded="@(IsOpen ? "true" : "false")" @onkeydown="@OnKeyPress" @onkeydown:preventDefault=preventKeyPress @onkeydown:stopPropagation="stopKeydownPropagation">
<button aria-label="@(ButtonAriaLabel ?? Text)" tabindex="-1" disabled="@IsDisabled" class=@ButtonClass type="@((Enum.GetName(typeof(ButtonType), ButtonType) ?? ButtonType.ToString()).ToLower())" @onclick="@OnClick">
<div @ref="@Element" style="@Style" @attributes="Attributes" class="@GetCssClass()" id="@GetId()">
<button aria-label="@(ButtonAriaLabel ?? Text)" tabindex="@(IsDisabled ? -1 : TabIndex)" disabled="@IsDisabled" class=@ButtonClass type="@((Enum.GetName(typeof(ButtonType), ButtonType) ?? ButtonType.ToString()).ToLower())" @onclick="@OnClick">
<span class="rz-button-box">
@if (ButtonContent != null)
{
@@ -25,7 +24,7 @@
{
@if (!string.IsNullOrEmpty(@Icon))
{
<i class="notranslate rz-button-icon-left rzi" style="@(!string.IsNullOrEmpty(IconColor) ? $"color:{IconColor}" : null)">@Icon</i>
<i class="notranslate rz-button-icon-left rzi" aria-hidden="true" style="@(!string.IsNullOrEmpty(IconColor) ? $"color:{IconColor}" : null)">@Icon</i>
}
@if (!string.IsNullOrEmpty(Image))
{
@@ -43,12 +42,14 @@
}
</span>
</button>
<button tabindex="-1" disabled="@IsDisabled" class=@PopupButtonClass type="button" @onclick="@OnToggleClick"
aria-label="@OpenAriaLabel">
<button id="@ToggleButtonId" tabindex="@(IsDisabled ? -1 : TabIndex)" disabled="@IsDisabled" class=@PopupButtonClass type="button" @onclick="@OnToggleClick"
aria-label="@OpenAriaLabel" aria-haspopup="menu" aria-controls="@PopupID" aria-expanded="@(IsOpen ? "true" : "false")"
@onkeydown="@OnToggleKeyDown" @onkeydown:preventDefault=preventKeyPress @onkeydown:stopPropagation="stopKeydownPropagation">
<RadzenIcon Icon="@DropDownIcon" />
</button>
<div id="@PopupID" class="@PopupMenuClass">
<ul class="rz-menu-list" role="menu" aria-orientation="vertical" aria-label="@OpenAriaLabel">
<ul id="@MenuId" class="rz-menu-list" role="menu" aria-orientation="vertical" aria-label="@OpenAriaLabel" tabindex="-1"
aria-activedescendant="@ActiveDescendantId" @onkeydown="@OnMenuKeyDown" @onkeydown:preventDefault=preventKeyPress @onkeydown:stopPropagation="stopKeydownPropagation">
<CascadingValue Value=this>
@ChildContent
</CascadingValue>

View File

@@ -207,6 +207,11 @@ namespace Radzen.Blazor
}
await JSRuntime.InvokeVoidAsync("Radzen.togglePopup", Element, PopupID);
if (IsOpen)
{
await JSRuntime.InvokeVoidAsync("Radzen.focusElement", MenuId);
}
}
else
{
@@ -219,7 +224,7 @@ namespace Radzen.Blazor
}
}
void OnToggleClick()
async Task OnToggleClick()
{
if (Disabled)
{
@@ -228,9 +233,17 @@ namespace Radzen.Blazor
IsOpen = !IsOpen;
if (IsOpen && focusedIndex == -1 && items.Count > 0)
if (IsOpen)
{
focusedIndex = 0;
if (focusedIndex == -1 && items.Count > 0)
{
focusedIndex = 0;
}
if (JSRuntime != null)
{
await JSRuntime.InvokeVoidAsync("Radzen.focusElement", MenuId);
}
}
}
@@ -259,6 +272,10 @@ namespace Radzen.Blazor
}
}
private string MenuId => $"{GetId()}-menu";
private string ToggleButtonId => $"{GetId()}-toggle";
string ButtonClass => ClassList.Create("rz-button")
.AddButtonSize(Size)
.AddVariant(Variant)
@@ -333,7 +350,7 @@ namespace Radzen.Blazor
}
internal int focusedIndex = -1;
bool preventKeyPress = true;
bool preventKeyPress;
bool stopKeydownPropagation;
async Task Open(int index)
@@ -345,41 +362,53 @@ namespace Radzen.Blazor
if (JSRuntime != null)
{
await JSRuntime.InvokeVoidAsync("Radzen.togglePopup", Element, PopupID);
await JSRuntime.InvokeVoidAsync("Radzen.focusElement", MenuId);
}
}
async Task OnKeyPress(KeyboardEventArgs args)
async Task OnToggleKeyDown(KeyboardEventArgs args)
{
var key = args.Code != null ? args.Code : args.Key;
if (!IsOpen && (key == "ArrowDown" || key == "ArrowUp") && items.Count > 0)
{
preventKeyPress = true;
stopKeydownPropagation = true;
await Open(key == "ArrowDown" ? 0 : items.Count - 1);
}
else if (IsOpen && key == "Escape")
{
preventKeyPress = true;
stopKeydownPropagation = true;
await CloseAndFocusToggle();
}
else
{
preventKeyPress = false;
stopKeydownPropagation = false;
}
}
async Task CloseAndFocusToggle()
{
Close();
if (JSRuntime != null)
{
await JSRuntime.InvokeVoidAsync("Radzen.focusElement", ToggleButtonId);
}
}
async Task OnMenuKeyDown(KeyboardEventArgs args)
{
var key = args.Code != null ? args.Code : args.Key;
if (!IsOpen)
{
if (key == "Enter" || key == "Space" || key == "NumpadEnter")
{
preventKeyPress = true;
stopKeydownPropagation = true;
await OnClick(new MouseEventArgs());
}
else if (key == "ArrowDown" && items.Count > 0)
{
preventKeyPress = true;
stopKeydownPropagation = true;
await Open(0);
}
else if (key == "ArrowUp" && items.Count > 0)
{
preventKeyPress = true;
stopKeydownPropagation = true;
await Open(items.Count - 1);
}
else
{
preventKeyPress = false;
stopKeydownPropagation = false;
}
preventKeyPress = false;
stopKeydownPropagation = false;
return;
}
@@ -410,9 +439,10 @@ namespace Radzen.Blazor
{
await items[focusedIndex].OnClick(new MouseEventArgs());
}
else
if (JSRuntime != null)
{
await OnClick(new MouseEventArgs());
await JSRuntime.InvokeVoidAsync("Radzen.focusElement", ToggleButtonId);
}
}
else if (key == "Escape")
@@ -420,12 +450,7 @@ namespace Radzen.Blazor
preventKeyPress = true;
stopKeydownPropagation = true;
Close();
if (JSRuntime != null)
{
await JSRuntime.InvokeVoidAsync("Radzen.focusElement", GetId());
}
await CloseAndFocusToggle();
}
else if (args.Key != null && args.Key.Length == 1 && !args.AltKey && !args.CtrlKey && !args.MetaKey && !char.IsControl(args.Key[0]))
{

View File

@@ -29,7 +29,7 @@
role="button"
aria-label="@CollapseAriaLabel"
aria-expanded="@AriaExpanded"
@onkeypress="@(args => OnKeyPress(args, false))" @onkeypress:preventDefault=preventKeyPress @onkeypress:stopPropagation=stopKeypressPropagation
@onkeydown="@(args => OnKeyPress(args, false))" @onkeydown:preventDefault=preventKeyPress @onkeydown:stopPropagation=stopKeypressPropagation
@onmousedown:preventDefault="true" @onmousedown:stopPropagation="true" @onmousedown=@(args => Splitter!.OnCollapse(Index))>
</span>
}
@@ -55,7 +55,7 @@
role="button"
aria-label="@ExpandAriaLabel"
aria-expanded="@AriaExpanded"
@onkeypress="@(args => OnKeyPress(args, true))" @onkeypress:preventDefault=preventKeyPress @onkeypress:stopPropagation=stopKeypressPropagation
@onkeydown="@(args => OnKeyPress(args, true))" @onkeydown:preventDefault=preventKeyPress @onkeydown:stopPropagation=stopKeypressPropagation
@onmousedown:preventDefault="true" @onmousedown:stopPropagation="true" @onmousedown=@(args => Splitter!.OnExpand(Index))>
</span>
}

View File

@@ -22,7 +22,7 @@
var index = i;
@if (step.Visible)
{
<li class="@step.GetItemCssClass()" @attributes="step.Attributes" style="@step.Style">
<li class="@step.GetItemCssClass()" role="presentation" @attributes="step.Attributes" style="@step.Style">
<button type="button" role="tab" id="@(GetId() + i.ToString() + "s")" title="@step.Title" aria-label="@step.AriaLabel"
aria-selected="@(IsSelected(i, step) ? "true" : "false")"
aria-controls="@($"{GetId()}-panel-{i}")"

View File

@@ -299,8 +299,12 @@ namespace Radzen.Blazor {
public static string AIChat_EmptyMessage { get { return ResourceManager.GetString("AIChat_EmptyMessage", resourceCulture); } }
public static string AIChat_UserAvatarText { get { return ResourceManager.GetString("AIChat_UserAvatarText", resourceCulture); } }
public static string AIChat_AssistantAvatarText { get { return ResourceManager.GetString("AIChat_AssistantAvatarText", resourceCulture); } }
public static string Alert_CloseAriaLabel { get { return ResourceManager.GetString("Alert_CloseAriaLabel", resourceCulture); } }
public static string Button_ImageAlternateText { get { return ResourceManager.GetString("Button_ImageAlternateText", resourceCulture); } }
public static string Carousel_PagerButtonAriaLabelFormat { get { return ResourceManager.GetString("Carousel_PagerButtonAriaLabelFormat", resourceCulture); } }
public static string Carousel_PrevAriaLabel { get { return ResourceManager.GetString("Carousel_PrevAriaLabel", resourceCulture); } }
public static string Carousel_NextAriaLabel { get { return ResourceManager.GetString("Carousel_NextAriaLabel", resourceCulture); } }
public static string Carousel_SlideAriaLabelFormat { get { return ResourceManager.GetString("Carousel_SlideAriaLabelFormat", resourceCulture); } }
public static string Chat_Placeholder { get { return ResourceManager.GetString("Chat_Placeholder", resourceCulture); } }
public static string Chat_EmptyMessage { get { return ResourceManager.GetString("Chat_EmptyMessage", resourceCulture); } }
public static string Chat_NewMessagesText { get { return ResourceManager.GetString("Chat_NewMessagesText", resourceCulture); } }
@@ -314,6 +318,10 @@ namespace Radzen.Blazor {
public static string ColorPicker_BlueText { get { return ResourceManager.GetString("ColorPicker_BlueText", resourceCulture); } }
public static string ColorPicker_AlphaText { get { return ResourceManager.GetString("ColorPicker_AlphaText", resourceCulture); } }
public static string ColorPicker_ButtonText { get { return ResourceManager.GetString("ColorPicker_ButtonText", resourceCulture); } }
public static string ColorPicker_HueAriaLabel { get { return ResourceManager.GetString("ColorPicker_HueAriaLabel", resourceCulture); } }
public static string ColorPicker_AlphaAriaLabel { get { return ResourceManager.GetString("ColorPicker_AlphaAriaLabel", resourceCulture); } }
public static string ColorPicker_SaturationAriaLabel { get { return ResourceManager.GetString("ColorPicker_SaturationAriaLabel", resourceCulture); } }
public static string ColorPicker_SaturationValueTextFormat { get { return ResourceManager.GetString("ColorPicker_SaturationValueTextFormat", resourceCulture); } }
public static string DataAnnotationValidator_MessageSeparator { get { return ResourceManager.GetString("DataAnnotationValidator_MessageSeparator", resourceCulture); } }
public static string DataFilter_FilterText { get { return ResourceManager.GetString("DataFilter_FilterText", resourceCulture); } }
public static string DataFilter_EnumFilterSelectText { get { return ResourceManager.GetString("DataFilter_EnumFilterSelectText", resourceCulture); } }
@@ -341,6 +349,9 @@ namespace Radzen.Blazor {
public static string DataFilter_IsEmptyText { get { return ResourceManager.GetString("DataFilter_IsEmptyText", resourceCulture); } }
public static string DataFilter_IsNotEmptyText { get { return ResourceManager.GetString("DataFilter_IsNotEmptyText", resourceCulture); } }
public static string DataFilter_CustomText { get { return ResourceManager.GetString("DataFilter_CustomText", resourceCulture); } }
public static string DataFilter_PropertyAriaLabel { get { return ResourceManager.GetString("DataFilter_PropertyAriaLabel", resourceCulture); } }
public static string DataFilter_FilterOperatorAriaLabel { get { return ResourceManager.GetString("DataFilter_FilterOperatorAriaLabel", resourceCulture); } }
public static string DataFilter_FilterValueAriaLabel { get { return ResourceManager.GetString("DataFilter_FilterValueAriaLabel", resourceCulture); } }
public static string DataGrid_FilterText { get { return ResourceManager.GetString("DataGrid_FilterText", resourceCulture); } }
public static string DataGrid_EnumFilterSelectText { get { return ResourceManager.GetString("DataGrid_EnumFilterSelectText", resourceCulture); } }
public static string DataGrid_EnumNullFilterText { get { return ResourceManager.GetString("DataGrid_EnumNullFilterText", resourceCulture); } }
@@ -519,6 +530,8 @@ namespace Radzen.Blazor {
public static string PickList_SelectedSourceToTargetTitle { get { return ResourceManager.GetString("PickList_SelectedSourceToTargetTitle", resourceCulture); } }
public static string PickList_TargetToSourceTitle { get { return ResourceManager.GetString("PickList_TargetToSourceTitle", resourceCulture); } }
public static string PickList_SelectedTargetToSourceTitle { get { return ResourceManager.GetString("PickList_SelectedTargetToSourceTitle", resourceCulture); } }
public static string PickList_SourceAriaLabel { get { return ResourceManager.GetString("PickList_SourceAriaLabel", resourceCulture); } }
public static string PickList_TargetAriaLabel { get { return ResourceManager.GetString("PickList_TargetAriaLabel", resourceCulture); } }
public static string PivotDataGrid_EmptyText { get { return ResourceManager.GetString("PivotDataGrid_EmptyText", resourceCulture); } }
public static string PivotDataGrid_FieldsPickerHeaderText { get { return ResourceManager.GetString("PivotDataGrid_FieldsPickerHeaderText", resourceCulture); } }
public static string PivotDataGrid_RowsText { get { return ResourceManager.GetString("PivotDataGrid_RowsText", resourceCulture); } }
@@ -555,6 +568,7 @@ namespace Radzen.Blazor {
public static string PivotDataGrid_CustomText { get { return ResourceManager.GetString("PivotDataGrid_CustomText", resourceCulture); } }
public static string ProfileMenu_ToggleAriaLabel { get { return ResourceManager.GetString("ProfileMenu_ToggleAriaLabel", resourceCulture); } }
public static string ProfileMenuItem_ImageAlternateText { get { return ResourceManager.GetString("ProfileMenuItem_ImageAlternateText", resourceCulture); } }
public static string Rating_AriaLabel { get { return ResourceManager.GetString("Rating_AriaLabel", resourceCulture); } }
public static string Rating_ClearAriaLabel { get { return ResourceManager.GetString("Rating_ClearAriaLabel", resourceCulture); } }
public static string Rating_RateAriaLabel { get { return ResourceManager.GetString("Rating_RateAriaLabel", resourceCulture); } }
public static string SankeyDiagram_ValueText { get { return ResourceManager.GetString("SankeyDiagram_ValueText", resourceCulture); } }
@@ -564,6 +578,8 @@ namespace Radzen.Blazor {
public static string Scheduler_TodayText { get { return ResourceManager.GetString("Scheduler_TodayText", resourceCulture); } }
public static string Scheduler_NextText { get { return ResourceManager.GetString("Scheduler_NextText", resourceCulture); } }
public static string Scheduler_PrevText { get { return ResourceManager.GetString("Scheduler_PrevText", resourceCulture); } }
public static string SecurityCode_AriaLabel { get { return ResourceManager.GetString("SecurityCode_AriaLabel", resourceCulture); } }
public static string SecurityCode_InputAriaLabelFormat { get { return ResourceManager.GetString("SecurityCode_InputAriaLabelFormat", resourceCulture); } }
public static string SidebarToggle_ToggleAriaLabel { get { return ResourceManager.GetString("SidebarToggle_ToggleAriaLabel", resourceCulture); } }
public static string SignaturePad_ClearAriaLabel { get { return ResourceManager.GetString("SignaturePad_ClearAriaLabel", resourceCulture); } }
public static string SpeechToTextButton_Title { get { return ResourceManager.GetString("SpeechToTextButton_Title", resourceCulture); } }

View File

@@ -795,6 +795,10 @@
<data name="AIChat_AssistantAvatarText" xml:space="preserve">
<value>KI</value>
</data>
<!-- Alert -->
<data name="Alert_CloseAriaLabel" xml:space="preserve">
<value>Schließen</value>
</data>
<!-- Button -->
<data name="Button_ImageAlternateText" xml:space="preserve">
<value>Schaltfläche</value>
@@ -803,6 +807,15 @@
<data name="Carousel_PagerButtonAriaLabelFormat" xml:space="preserve">
<value>Zu Folie {0} wechseln</value>
</data>
<data name="Carousel_PrevAriaLabel" xml:space="preserve">
<value>Zurück</value>
</data>
<data name="Carousel_NextAriaLabel" xml:space="preserve">
<value>Weiter</value>
</data>
<data name="Carousel_SlideAriaLabelFormat" xml:space="preserve">
<value>{0} von {1}</value>
</data>
<!-- Chat -->
<data name="Chat_Placeholder" xml:space="preserve">
<value>Geben Sie Ihre Nachricht ein...</value>
@@ -846,6 +859,18 @@
<data name="ColorPicker_ButtonText" xml:space="preserve">
<value>OK</value>
</data>
<data name="ColorPicker_HueAriaLabel" xml:space="preserve">
<value>Farbton</value>
</data>
<data name="ColorPicker_AlphaAriaLabel" xml:space="preserve">
<value>Alpha</value>
</data>
<data name="ColorPicker_SaturationAriaLabel" xml:space="preserve">
<value>Sättigung und Helligkeit</value>
</data>
<data name="ColorPicker_SaturationValueTextFormat" xml:space="preserve">
<value>Sättigung {0} % Helligkeit {1} %</value>
</data>
<!-- DataAnnotationValidator -->
<data name="DataAnnotationValidator_MessageSeparator" xml:space="preserve">
<value> und </value>
@@ -929,6 +954,15 @@
<data name="DataFilter_CustomText" xml:space="preserve">
<value>Benutzerdefiniert</value>
</data>
<data name="DataFilter_PropertyAriaLabel" xml:space="preserve">
<value>Filtereigenschaft</value>
</data>
<data name="DataFilter_FilterOperatorAriaLabel" xml:space="preserve">
<value>Filteroperator</value>
</data>
<data name="DataFilter_FilterValueAriaLabel" xml:space="preserve">
<value>Filterwert</value>
</data>
<!-- DataGrid -->
<data name="DataGrid_FilterText" xml:space="preserve">
<value>Filter</value>
@@ -1508,6 +1542,12 @@
<data name="PickList_SelectedTargetToSourceTitle" xml:space="preserve">
<value>Ausgewählte Zielelemente in die Quellsammlung verschieben</value>
</data>
<data name="PickList_SourceAriaLabel" xml:space="preserve">
<value>Quellelemente</value>
</data>
<data name="PickList_TargetAriaLabel" xml:space="preserve">
<value>Zielelemente</value>
</data>
<!-- PivotDataGrid -->
<data name="PivotDataGrid_EmptyText" xml:space="preserve">
<value>Keine Datensätze zum Anzeigen.</value>
@@ -1620,6 +1660,9 @@
<value>Bild</value>
</data>
<!-- Rating -->
<data name="Rating_AriaLabel" xml:space="preserve">
<value>Bewertung</value>
</data>
<data name="Rating_ClearAriaLabel" xml:space="preserve">
<value>Löschen</value>
</data>
@@ -1649,6 +1692,13 @@
<data name="Scheduler_PrevText" xml:space="preserve">
<value>Zurück</value>
</data>
<!-- SecurityCode -->
<data name="SecurityCode_AriaLabel" xml:space="preserve">
<value>Sicherheitscode</value>
</data>
<data name="SecurityCode_InputAriaLabelFormat" xml:space="preserve">
<value>Zeichen {0} von {1}</value>
</data>
<!-- SidebarToggle -->
<data name="SidebarToggle_ToggleAriaLabel" xml:space="preserve">
<value>Umschalten</value>

View File

@@ -795,6 +795,10 @@
<data name="AIChat_AssistantAvatarText" xml:space="preserve">
<value>IA</value>
</data>
<!-- Alert -->
<data name="Alert_CloseAriaLabel" xml:space="preserve">
<value>Cerrar</value>
</data>
<!-- Button -->
<data name="Button_ImageAlternateText" xml:space="preserve">
<value>botón</value>
@@ -803,6 +807,15 @@
<data name="Carousel_PagerButtonAriaLabelFormat" xml:space="preserve">
<value>Ir a la diapositiva {0}</value>
</data>
<data name="Carousel_PrevAriaLabel" xml:space="preserve">
<value>Anterior</value>
</data>
<data name="Carousel_NextAriaLabel" xml:space="preserve">
<value>Siguiente</value>
</data>
<data name="Carousel_SlideAriaLabelFormat" xml:space="preserve">
<value>{0} de {1}</value>
</data>
<!-- Chat -->
<data name="Chat_Placeholder" xml:space="preserve">
<value>Escriba su mensaje...</value>
@@ -846,6 +859,18 @@
<data name="ColorPicker_ButtonText" xml:space="preserve">
<value>Aceptar</value>
</data>
<data name="ColorPicker_HueAriaLabel" xml:space="preserve">
<value>Tono</value>
</data>
<data name="ColorPicker_AlphaAriaLabel" xml:space="preserve">
<value>Alfa</value>
</data>
<data name="ColorPicker_SaturationAriaLabel" xml:space="preserve">
<value>Saturación y brillo</value>
</data>
<data name="ColorPicker_SaturationValueTextFormat" xml:space="preserve">
<value>Saturación {0}% Brillo {1}%</value>
</data>
<!-- DataAnnotationValidator -->
<data name="DataAnnotationValidator_MessageSeparator" xml:space="preserve">
<value> y </value>
@@ -929,6 +954,15 @@
<data name="DataFilter_CustomText" xml:space="preserve">
<value>Personalizado</value>
</data>
<data name="DataFilter_PropertyAriaLabel" xml:space="preserve">
<value>Propiedad del filtro</value>
</data>
<data name="DataFilter_FilterOperatorAriaLabel" xml:space="preserve">
<value>Operador de filtro</value>
</data>
<data name="DataFilter_FilterValueAriaLabel" xml:space="preserve">
<value>Valor de filtro</value>
</data>
<!-- DataGrid -->
<data name="DataGrid_FilterText" xml:space="preserve">
<value>Filtrar</value>
@@ -1508,6 +1542,12 @@
<data name="PickList_SelectedTargetToSourceTitle" xml:space="preserve">
<value>Mover los elementos de destino seleccionados a la colección de origen</value>
</data>
<data name="PickList_SourceAriaLabel" xml:space="preserve">
<value>Elementos de origen</value>
</data>
<data name="PickList_TargetAriaLabel" xml:space="preserve">
<value>Elementos de destino</value>
</data>
<!-- PivotDataGrid -->
<data name="PivotDataGrid_EmptyText" xml:space="preserve">
<value>No hay registros para mostrar.</value>
@@ -1620,6 +1660,9 @@
<value>imagen</value>
</data>
<!-- Rating -->
<data name="Rating_AriaLabel" xml:space="preserve">
<value>Calificación</value>
</data>
<data name="Rating_ClearAriaLabel" xml:space="preserve">
<value>Borrar</value>
</data>
@@ -1649,6 +1692,13 @@
<data name="Scheduler_PrevText" xml:space="preserve">
<value>Anterior</value>
</data>
<!-- SecurityCode -->
<data name="SecurityCode_AriaLabel" xml:space="preserve">
<value>Código de seguridad</value>
</data>
<data name="SecurityCode_InputAriaLabelFormat" xml:space="preserve">
<value>Carácter {0} de {1}</value>
</data>
<!-- SidebarToggle -->
<data name="SidebarToggle_ToggleAriaLabel" xml:space="preserve">
<value>Alternar</value>

View File

@@ -795,6 +795,10 @@
<data name="AIChat_AssistantAvatarText" xml:space="preserve">
<value>IA</value>
</data>
<!-- Alert -->
<data name="Alert_CloseAriaLabel" xml:space="preserve">
<value>Fermer</value>
</data>
<!-- Button -->
<data name="Button_ImageAlternateText" xml:space="preserve">
<value>bouton</value>
@@ -803,6 +807,15 @@
<data name="Carousel_PagerButtonAriaLabelFormat" xml:space="preserve">
<value>Aller à la diapositive {0}</value>
</data>
<data name="Carousel_PrevAriaLabel" xml:space="preserve">
<value>Précédent</value>
</data>
<data name="Carousel_NextAriaLabel" xml:space="preserve">
<value>Suivant</value>
</data>
<data name="Carousel_SlideAriaLabelFormat" xml:space="preserve">
<value>{0} sur {1}</value>
</data>
<!-- Chat -->
<data name="Chat_Placeholder" xml:space="preserve">
<value>Saisissez votre message...</value>
@@ -846,6 +859,18 @@
<data name="ColorPicker_ButtonText" xml:space="preserve">
<value>OK</value>
</data>
<data name="ColorPicker_HueAriaLabel" xml:space="preserve">
<value>Teinte</value>
</data>
<data name="ColorPicker_AlphaAriaLabel" xml:space="preserve">
<value>Alpha</value>
</data>
<data name="ColorPicker_SaturationAriaLabel" xml:space="preserve">
<value>Saturation et luminosité</value>
</data>
<data name="ColorPicker_SaturationValueTextFormat" xml:space="preserve">
<value>Saturation {0} % Luminosité {1} %</value>
</data>
<!-- DataAnnotationValidator -->
<data name="DataAnnotationValidator_MessageSeparator" xml:space="preserve">
<value> et </value>
@@ -929,6 +954,15 @@
<data name="DataFilter_CustomText" xml:space="preserve">
<value>Personnalisé</value>
</data>
<data name="DataFilter_PropertyAriaLabel" xml:space="preserve">
<value>Propriété du filtre</value>
</data>
<data name="DataFilter_FilterOperatorAriaLabel" xml:space="preserve">
<value>Opérateur de filtre</value>
</data>
<data name="DataFilter_FilterValueAriaLabel" xml:space="preserve">
<value>Valeur du filtre</value>
</data>
<!-- DataGrid -->
<data name="DataGrid_FilterText" xml:space="preserve">
<value>Filtrer</value>
@@ -1508,6 +1542,12 @@
<data name="PickList_SelectedTargetToSourceTitle" xml:space="preserve">
<value>Déplacer les éléments cibles sélectionnés vers la collection source</value>
</data>
<data name="PickList_SourceAriaLabel" xml:space="preserve">
<value>Éléments source</value>
</data>
<data name="PickList_TargetAriaLabel" xml:space="preserve">
<value>Éléments cibles</value>
</data>
<!-- PivotDataGrid -->
<data name="PivotDataGrid_EmptyText" xml:space="preserve">
<value>Aucun enregistrement à afficher.</value>
@@ -1620,6 +1660,9 @@
<value>image</value>
</data>
<!-- Rating -->
<data name="Rating_AriaLabel" xml:space="preserve">
<value>Évaluation</value>
</data>
<data name="Rating_ClearAriaLabel" xml:space="preserve">
<value>Effacer</value>
</data>
@@ -1649,6 +1692,13 @@
<data name="Scheduler_PrevText" xml:space="preserve">
<value>Précédent</value>
</data>
<!-- SecurityCode -->
<data name="SecurityCode_AriaLabel" xml:space="preserve">
<value>Code de sécurité</value>
</data>
<data name="SecurityCode_InputAriaLabelFormat" xml:space="preserve">
<value>Caractère {0} sur {1}</value>
</data>
<!-- SidebarToggle -->
<data name="SidebarToggle_ToggleAriaLabel" xml:space="preserve">
<value>Basculer</value>

View File

@@ -795,6 +795,10 @@
<data name="AIChat_AssistantAvatarText" xml:space="preserve">
<value>AI</value>
</data>
<!-- Alert -->
<data name="Alert_CloseAriaLabel" xml:space="preserve">
<value>Chiudi</value>
</data>
<!-- Button -->
<data name="Button_ImageAlternateText" xml:space="preserve">
<value>pulsante</value>
@@ -803,6 +807,15 @@
<data name="Carousel_PagerButtonAriaLabelFormat" xml:space="preserve">
<value>Vai alla diapositiva {0}</value>
</data>
<data name="Carousel_PrevAriaLabel" xml:space="preserve">
<value>Precedente</value>
</data>
<data name="Carousel_NextAriaLabel" xml:space="preserve">
<value>Successivo</value>
</data>
<data name="Carousel_SlideAriaLabelFormat" xml:space="preserve">
<value>{0} di {1}</value>
</data>
<!-- Chat -->
<data name="Chat_Placeholder" xml:space="preserve">
<value>Scrivi il tuo messaggio...</value>
@@ -846,6 +859,18 @@
<data name="ColorPicker_ButtonText" xml:space="preserve">
<value>OK</value>
</data>
<data name="ColorPicker_HueAriaLabel" xml:space="preserve">
<value>Tonalità</value>
</data>
<data name="ColorPicker_AlphaAriaLabel" xml:space="preserve">
<value>Alfa</value>
</data>
<data name="ColorPicker_SaturationAriaLabel" xml:space="preserve">
<value>Saturazione e luminosità</value>
</data>
<data name="ColorPicker_SaturationValueTextFormat" xml:space="preserve">
<value>Saturazione {0}% Luminosità {1}%</value>
</data>
<!-- DataAnnotationValidator -->
<data name="DataAnnotationValidator_MessageSeparator" xml:space="preserve">
<value> e </value>
@@ -929,6 +954,15 @@
<data name="DataFilter_CustomText" xml:space="preserve">
<value>Personalizzato</value>
</data>
<data name="DataFilter_PropertyAriaLabel" xml:space="preserve">
<value>Proprietà del filtro</value>
</data>
<data name="DataFilter_FilterOperatorAriaLabel" xml:space="preserve">
<value>Operatore del filtro</value>
</data>
<data name="DataFilter_FilterValueAriaLabel" xml:space="preserve">
<value>Valore del filtro</value>
</data>
<!-- DataGrid -->
<data name="DataGrid_FilterText" xml:space="preserve">
<value>Filtra</value>
@@ -1508,6 +1542,12 @@
<data name="PickList_SelectedTargetToSourceTitle" xml:space="preserve">
<value>Sposta gli elementi di destinazione selezionati nella raccolta di origine</value>
</data>
<data name="PickList_SourceAriaLabel" xml:space="preserve">
<value>Elementi di origine</value>
</data>
<data name="PickList_TargetAriaLabel" xml:space="preserve">
<value>Elementi di destinazione</value>
</data>
<!-- PivotDataGrid -->
<data name="PivotDataGrid_EmptyText" xml:space="preserve">
<value>Nessun record da visualizzare.</value>
@@ -1620,6 +1660,9 @@
<value>immagine</value>
</data>
<!-- Rating -->
<data name="Rating_AriaLabel" xml:space="preserve">
<value>Valutazione</value>
</data>
<data name="Rating_ClearAriaLabel" xml:space="preserve">
<value>Cancella</value>
</data>
@@ -1649,6 +1692,13 @@
<data name="Scheduler_PrevText" xml:space="preserve">
<value>Precedente</value>
</data>
<!-- SecurityCode -->
<data name="SecurityCode_AriaLabel" xml:space="preserve">
<value>Codice di sicurezza</value>
</data>
<data name="SecurityCode_InputAriaLabelFormat" xml:space="preserve">
<value>Carattere {0} di {1}</value>
</data>
<!-- SidebarToggle -->
<data name="SidebarToggle_ToggleAriaLabel" xml:space="preserve">
<value>Attiva/disattiva</value>

View File

@@ -795,6 +795,10 @@
<data name="AIChat_AssistantAvatarText" xml:space="preserve">
<value>AI</value>
</data>
<!-- Alert -->
<data name="Alert_CloseAriaLabel" xml:space="preserve">
<value>閉じる</value>
</data>
<!-- Button -->
<data name="Button_ImageAlternateText" xml:space="preserve">
<value>ボタン</value>
@@ -803,6 +807,15 @@
<data name="Carousel_PagerButtonAriaLabelFormat" xml:space="preserve">
<value>スライド {0} へ移動</value>
</data>
<data name="Carousel_PrevAriaLabel" xml:space="preserve">
<value>前へ</value>
</data>
<data name="Carousel_NextAriaLabel" xml:space="preserve">
<value>次へ</value>
</data>
<data name="Carousel_SlideAriaLabelFormat" xml:space="preserve">
<value>{0} / {1}</value>
</data>
<!-- Chat -->
<data name="Chat_Placeholder" xml:space="preserve">
<value>メッセージを入力...</value>
@@ -846,6 +859,18 @@
<data name="ColorPicker_ButtonText" xml:space="preserve">
<value>OK</value>
</data>
<data name="ColorPicker_HueAriaLabel" xml:space="preserve">
<value>色相</value>
</data>
<data name="ColorPicker_AlphaAriaLabel" xml:space="preserve">
<value>アルファ</value>
</data>
<data name="ColorPicker_SaturationAriaLabel" xml:space="preserve">
<value>彩度と明度</value>
</data>
<data name="ColorPicker_SaturationValueTextFormat" xml:space="preserve">
<value>彩度 {0}% 明度 {1}%</value>
</data>
<!-- DataAnnotationValidator -->
<data name="DataAnnotationValidator_MessageSeparator" xml:space="preserve">
<value> および </value>
@@ -929,6 +954,15 @@
<data name="DataFilter_CustomText" xml:space="preserve">
<value>カスタム</value>
</data>
<data name="DataFilter_PropertyAriaLabel" xml:space="preserve">
<value>フィルタープロパティ</value>
</data>
<data name="DataFilter_FilterOperatorAriaLabel" xml:space="preserve">
<value>フィルター演算子</value>
</data>
<data name="DataFilter_FilterValueAriaLabel" xml:space="preserve">
<value>フィルター値</value>
</data>
<!-- DataGrid -->
<data name="DataGrid_FilterText" xml:space="preserve">
<value>フィルター</value>
@@ -1508,6 +1542,12 @@
<data name="PickList_SelectedTargetToSourceTitle" xml:space="preserve">
<value>選択した対象の項目を元のコレクションへ移動</value>
</data>
<data name="PickList_SourceAriaLabel" xml:space="preserve">
<value>元の項目</value>
</data>
<data name="PickList_TargetAriaLabel" xml:space="preserve">
<value>対象の項目</value>
</data>
<!-- PivotDataGrid -->
<data name="PivotDataGrid_EmptyText" xml:space="preserve">
<value>表示するレコードがありません。</value>
@@ -1620,6 +1660,9 @@
<value>画像</value>
</data>
<!-- Rating -->
<data name="Rating_AriaLabel" xml:space="preserve">
<value>評価</value>
</data>
<data name="Rating_ClearAriaLabel" xml:space="preserve">
<value>クリア</value>
</data>
@@ -1649,6 +1692,13 @@
<data name="Scheduler_PrevText" xml:space="preserve">
<value>前へ</value>
</data>
<!-- SecurityCode -->
<data name="SecurityCode_AriaLabel" xml:space="preserve">
<value>セキュリティコード</value>
</data>
<data name="SecurityCode_InputAriaLabelFormat" xml:space="preserve">
<value>{1}文字中{0}文字目</value>
</data>
<!-- SidebarToggle -->
<data name="SidebarToggle_ToggleAriaLabel" xml:space="preserve">
<value>切り替え</value>

View File

@@ -795,6 +795,10 @@
<data name="AIChat_AssistantAvatarText" xml:space="preserve">
<value>AI</value>
</data>
<!-- Alert -->
<data name="Alert_CloseAriaLabel" xml:space="preserve">
<value>Close</value>
</data>
<!-- Button -->
<data name="Button_ImageAlternateText" xml:space="preserve">
<value>button</value>
@@ -803,6 +807,15 @@
<data name="Carousel_PagerButtonAriaLabelFormat" xml:space="preserve">
<value>Go to slide {0}</value>
</data>
<data name="Carousel_PrevAriaLabel" xml:space="preserve">
<value>Previous</value>
</data>
<data name="Carousel_NextAriaLabel" xml:space="preserve">
<value>Next</value>
</data>
<data name="Carousel_SlideAriaLabelFormat" xml:space="preserve">
<value>{0} of {1}</value>
</data>
<!-- Chat -->
<data name="Chat_Placeholder" xml:space="preserve">
<value>Type your message...</value>
@@ -846,6 +859,18 @@
<data name="ColorPicker_ButtonText" xml:space="preserve">
<value>OK</value>
</data>
<data name="ColorPicker_HueAriaLabel" xml:space="preserve">
<value>Hue</value>
</data>
<data name="ColorPicker_AlphaAriaLabel" xml:space="preserve">
<value>Alpha</value>
</data>
<data name="ColorPicker_SaturationAriaLabel" xml:space="preserve">
<value>Saturation and brightness</value>
</data>
<data name="ColorPicker_SaturationValueTextFormat" xml:space="preserve">
<value>Saturation {0}% Brightness {1}%</value>
</data>
<!-- DataAnnotationValidator -->
<data name="DataAnnotationValidator_MessageSeparator" xml:space="preserve">
<value> and </value>
@@ -929,6 +954,15 @@
<data name="DataFilter_CustomText" xml:space="preserve">
<value>Custom</value>
</data>
<data name="DataFilter_PropertyAriaLabel" xml:space="preserve">
<value>Filter property</value>
</data>
<data name="DataFilter_FilterOperatorAriaLabel" xml:space="preserve">
<value>Filter operator</value>
</data>
<data name="DataFilter_FilterValueAriaLabel" xml:space="preserve">
<value>Filter value</value>
</data>
<!-- DataGrid -->
<data name="DataGrid_FilterText" xml:space="preserve">
<value>Filter</value>
@@ -1508,6 +1542,12 @@
<data name="PickList_SelectedTargetToSourceTitle" xml:space="preserve">
<value>Move selected target items to source collection</value>
</data>
<data name="PickList_SourceAriaLabel" xml:space="preserve">
<value>Source items</value>
</data>
<data name="PickList_TargetAriaLabel" xml:space="preserve">
<value>Target items</value>
</data>
<!-- PivotDataGrid -->
<data name="PivotDataGrid_EmptyText" xml:space="preserve">
<value>No records to display.</value>
@@ -1620,6 +1660,9 @@
<value>image</value>
</data>
<!-- Rating -->
<data name="Rating_AriaLabel" xml:space="preserve">
<value>Rating</value>
</data>
<data name="Rating_ClearAriaLabel" xml:space="preserve">
<value>Clear</value>
</data>
@@ -1649,6 +1692,13 @@
<data name="Scheduler_PrevText" xml:space="preserve">
<value>Previous</value>
</data>
<!-- SecurityCode -->
<data name="SecurityCode_AriaLabel" xml:space="preserve">
<value>Security code</value>
</data>
<data name="SecurityCode_InputAriaLabelFormat" xml:space="preserve">
<value>Character {0} of {1}</value>
</data>
<!-- SidebarToggle -->
<data name="SidebarToggle_ToggleAriaLabel" xml:space="preserve">
<value>Toggle</value>

View File

@@ -4,14 +4,14 @@
<CascadingValue Value=this>
@if (Visible)
{
<div @ref=@Element style=@Style @attributes=@Attributes class=@GetCssClass() id=@GetId()
role="tablist"
aria-orientation="@(IsVertical ? "vertical" : "horizontal")"
aria-label="@AriaLabel"
aria-labelledby="@AriaLabelledBy"
aria-activedescendant="@GetActiveDescendantId()"
tabindex=0 @onkeydown="@(args => OnKeyPress(args))" @onkeydown:preventDefault=preventKeyPress @onkeydown:stopPropagation="stopKeydownPropagation">
<ul role="presentation" class="rz-tabview-nav">
<div @ref=@Element style=@Style @attributes=@Attributes class=@GetCssClass() id=@GetId()>
<ul @ref=@tablistElement class="rz-tabview-nav"
role="tablist"
aria-orientation="@(IsVertical ? "vertical" : "horizontal")"
aria-label="@AriaLabel"
aria-labelledby="@AriaLabelledBy"
aria-activedescendant="@GetActiveDescendantId()"
tabindex=0 @onkeydown="@(args => OnKeyPress(args))" @onkeydown:preventDefault=preventKeyPress @onkeydown:stopPropagation="stopKeydownPropagation">
@Tabs
</ul>
<div class="rz-tabview-panels">

View File

@@ -129,6 +129,8 @@ namespace Radzen.Blazor
[Parameter]
public string? AriaLabelledBy { get; set; }
internal ElementReference tablistElement;
List<RadzenTabsItem> tabs = new List<RadzenTabsItem>();
internal List<RadzenTabsItem> NavigableTabs()
@@ -255,7 +257,7 @@ namespace Radzen.Blazor
try
{
await Element.FocusAsync(preventScroll: true);
await tablistElement.FocusAsync(preventScroll: true);
}
catch (JSDisconnectedException)
{
@@ -409,7 +411,7 @@ namespace Radzen.Blazor
try
{
await Element.FocusAsync(preventScroll: true);
await tablistElement.FocusAsync(preventScroll: true);
}
catch (JSDisconnectedException)
{

View File

@@ -31,7 +31,7 @@
{
@if (!string.IsNullOrEmpty(GetIcon()))
{
<i class="notranslate rz-button-icon-left rzi" style="@(!string.IsNullOrEmpty(IconColor) ? $"color:{IconColor}" : null)">@GetIcon()</i>
<i class="notranslate rz-button-icon-left rzi" aria-hidden="true" style="@(!string.IsNullOrEmpty(IconColor) ? $"color:{IconColor}" : null)">@GetIcon()</i>
}
@if (!string.IsNullOrEmpty(Image))
{

View File

@@ -9,7 +9,7 @@
role="tree" aria-multiselectable="@(AllowCheckBoxes ? "true" : null)"
aria-label="@AriaLabel" aria-labelledby="@AriaLabelledBy" aria-activedescendant="@ActiveDescendantId"
tabindex="0" @onkeydown="@OnKeyPress" @onkeydown:preventDefault=preventKeyPress @onkeydown:stopPropagation="stopKeydownPropagation">
<ul class="rz-tree-container">
<ul class="rz-tree-container" role="presentation">
<CascadingValue Value=this>
@ChildContent
</CascadingValue>

View File

@@ -2,7 +2,7 @@
@using Radzen.Blazor.Rendering
@implements IDisposable
@{var itemArgs = Tree?.ItemAttributes(this); }
<li class="rz-treenode" @attributes="@(itemArgs?.Item1.Attributes.Any() == true ? itemArgs.Item1.Attributes : Attributes)"
<li class="rz-treenode" role="presentation" @attributes="@(itemArgs?.Item1.Attributes.Any() == true ? itemArgs.Item1.Attributes : Attributes)"
@oncontextmenu="OnContextMenu" @oncontextmenu:preventDefault="@(Tree?.ItemContextMenu.HasDelegate ?? false)" @oncontextmenu:stopPropagation>
<div id="@ElementId" class=@ContentClass role="treeitem" tabindex="-1"
aria-level="@Level"
@@ -14,7 +14,7 @@
@onclick="@Select">
@if (ChildContent != null || HasChildren)
{
<span class=@IconClass role="button" tabindex="-1" aria-label="@Text" @onclick="@Toggle" @onclick:stopPropagation></span>
<span class=@IconClass aria-hidden="true" @onclick="@Toggle" @onclick:stopPropagation></span>
}
@if(Tree != null && Tree.AllowCheckBoxes)
{
@@ -31,7 +31,7 @@
@if (ChildContent != null && expanded)
{
<CascadingValue Value=this>
<Expander Expanded=@clientExpanded>
<Expander Expanded=@clientExpanded Role="presentation">
<ul class="rz-treenode-children" role="group" @onkeydown:stopPropagation="stopKeydownPropagation" @onkeydown="OnGuardKeyDown">
@ChildContent
</ul>

View File

@@ -23,7 +23,7 @@
ariaAttributes["aria-modal"] = "true";
}
}
<div @ref="dialog" class=@Class role="dialog" style=@Style @attributes="ariaAttributes">
<div @ref="dialog" class=@Class role="@(options?.Role ?? "dialog")" style=@Style @attributes="ariaAttributes">
@if (showTitle)
{
<div class="rz-dialog-titlebar">

View File

@@ -1,4 +1,4 @@
<div role="region" aria-hidden=@Hidden class=@Class @attributes=@Attributes>
<div role="@Role" aria-hidden=@Hidden class=@Class @attributes=@Attributes>
<div class="rz-expander-content">
@ChildContent
</div>

View File

@@ -28,6 +28,12 @@ public partial class Expander : ComponentBase
[Parameter]
public string CssClass { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the ARIA role rendered on the expander element.
/// </summary>
[Parameter]
public string Role { get; set; } = "region";
/// <summary>
/// Determines whether the content is visible.
/// </summary>

View File

@@ -4,8 +4,17 @@
align-items: center;
}
.rz-breadcrumb-list {
box-sizing: border-box;
display: flex;
align-items: center;
list-style: none;
margin: 0;
padding: 0;
}
.rz-breadcrumb-item + .rz-breadcrumb-item::before {
content: '»';
content: '»' / '';
display: inline-block;
padding-inline-start: 0.5rem;
padding-inline-end: 0.5rem;

View File

@@ -53,6 +53,10 @@ $listbox-header-icon-margin: 0 !default;
.rz-listbox-list {
margin: 0;
padding: 0;
&:focus {
outline: none;
}
}
.rz-listbox-item {

View File

@@ -35,17 +35,15 @@ $pager-button-size: "%button-md" !default;
gap: var(--rz-pager-gap);
flex-wrap: wrap;
&:focus {
outline: var(--rz-outline-normal);
}
.rz-pager-element,
.rz-pager-page {
&:focus {
outline: var(--rz-outline-normal);
}
&:focus-visible {
.rz-pager-element,
.rz-pager-page {
&.rz-state-focused {
outline: var(--rz-outline-focus);
outline-offset: var(--rz-outline-offset);
}
&:focus-visible {
outline: var(--rz-outline-focus);
outline-offset: var(--rz-outline-offset);
}
}

View File

@@ -48,6 +48,10 @@ ul.rz-profile-menu {
}
}
.rz-navigation-menu:focus {
outline: none;
}
.rz-navigation-item-icon-children {
color: var(--rz-profile-menu-toggle-button-color);

View File

@@ -7,7 +7,7 @@ $selectbar-selected-border: var(--rz-border-primary-lighter) !default;
$selectbar-border-radius: var(--rz-border-radius) !default;
$selectbar-focus-outline: var(--rz-outline-focus) !default;
$selectbar-focus-outline-offset: var(--rz-outline-offset) !default;
$selectbar-button-focus-outline: var(--rz-outline-width) solid var(--rz-text-color) !default;
$selectbar-button-focus-outline: var(--rz-outline-focus) !default;
$selectbar-button-focus-outline-offset: calc(-1 * var(--rz-outline-width)) !default;
$selectbar-sizes: xs, sm, md, lg;

View File

@@ -9,15 +9,6 @@ $splitbutton-sizes: xs, sm, md, lg;
display: inline-flex;
border-radius: var(--rz-button-border-radius);
&:focus {
outline: var(--rz-outline-normal);
}
&:focus-visible {
outline: var(--rz-button-focus-outline);
outline-offset: var(--rz-button-focus-outline-offset);
}
.rz-button-icon-only {
.rz-button-text {
display: none;
@@ -50,6 +41,10 @@ $splitbutton-sizes: xs, sm, md, lg;
list-style: none;
padding: 0;
margin: 0;
&:focus {
outline: none;
}
}
.rz-menuitem {

View File

@@ -32,26 +32,6 @@ $tabs-transition: var(--rz-transition-all) !default;
box-sizing: border-box;
display: flex;
&:focus {
outline: var(--rz-outline-normal);
}
&:focus-visible {
outline: var(--rz-outline-focus);
outline-offset: var(--rz-outline-offset);
.rz-tabview-nav {
.rz-state-focused {
&:not(.rz-tabview-selected) {
&:not(.rz-state-disabled) {
outline: var(--rz-tabs-tab-focus-outline);
outline-offset: var(--rz-tabs-tab-focus-outline-offset);
}
}
}
}
}
&.rz-tabview-top {
flex-direction: column;
}
@@ -83,6 +63,24 @@ $tabs-transition: var(--rz-transition-all) !default;
padding: 0;
margin: 0;
&:focus {
outline: var(--rz-outline-normal);
}
&:focus-visible {
outline: var(--rz-outline-focus);
outline-offset: var(--rz-outline-offset);
.rz-state-focused {
&:not(.rz-tabview-selected) {
&:not(.rz-state-disabled) {
outline: var(--rz-tabs-tab-focus-outline);
outline-offset: var(--rz-tabs-tab-focus-outline-offset);
}
}
}
}
li {
border: var(--rz-tabs-border);
background-color: var(--rz-tabs-tab-background-color);

View File

@@ -1506,6 +1506,25 @@ window.Radzen = {
table.nextSelectedIndex = rows.length - 1;
} else if (isVirtual && (key == 'PageUp' || key == 'Home')) {
table.nextSelectedIndex = 1;
} else if (!isVirtual && (key == 'PageDown' || key == 'PageUp' || key == 'Home' || key == 'End')) {
var firstDataIndex = thead && thead.rows && thead.rows.length && cellIndex != null ? thead.rows.length : 1;
if (firstDataIndex > rows.length - 1) {
firstDataIndex = rows.length - 1;
}
if (key == 'Home') {
table.nextSelectedIndex = firstDataIndex;
} else if (key == 'End') {
table.nextSelectedIndex = rows.length - 1;
} else {
var pageFactor = 10;
table.nextSelectedIndex = table.nextSelectedIndex + (key == 'PageDown' ? pageFactor : -pageFactor);
if (table.nextSelectedIndex > rows.length - 1) {
table.nextSelectedIndex = rows.length - 1;
}
if (table.nextSelectedIndex < firstDataIndex) {
table.nextSelectedIndex = firstDataIndex;
}
}
}
if (isVirtual && (key == 'ArrowDown' || key == 'ArrowUp')) {
@@ -1526,20 +1545,10 @@ window.Radzen = {
var prev = document.getElementById(activeId);
if (prev && prev !== el) {
prev.removeAttribute('id');
if (prev.hasAttribute('data-rz-active-selected')) {
prev.removeAttribute('data-rz-active-selected');
if (prev.hasAttribute('aria-selected')) {
prev.setAttribute('aria-selected', 'false');
}
}
}
if (el && el.id !== activeId) { el.id = activeId; }
if (el) {
grid.setAttribute('aria-activedescendant', activeId);
if (el.tagName === 'TR' && el.hasAttribute('aria-selected')) {
el.setAttribute('aria-selected', 'true');
el.setAttribute('data-rz-active-selected', '');
}
}
};
@@ -1565,7 +1574,7 @@ window.Radzen = {
}
setActiveDescendant(cell);
}
} else if (key == 'ArrowDown' || key == 'ArrowUp') {
} else if (key == 'ArrowDown' || key == 'ArrowUp' || (!isVirtual && (key == 'PageDown' || key == 'PageUp' || key == 'Home' || key == 'End'))) {
var highlighted = table.querySelectorAll('.rz-state-focused');
if (highlighted.length) {
for (var i = 0; i < highlighted.length; i++) {
@@ -2567,7 +2576,25 @@ window.Radzen = {
first.focus();
}
},
openSideDialog: function (options) {
openSideDialog: function (options, dialogService) {
if (dialogService) {
Radzen.sideDialogService = dialogService;
}
if (!Radzen.sideDialogInvokerElement) {
var invoker = document.activeElement;
if (invoker && invoker !== document.body && !invoker.closest('.rz-dialog-side')) {
Radzen.sideDialogInvokerElement = invoker;
}
}
var sideDialog = document.querySelector('.rz-dialog-side');
if (sideDialog) {
sideDialog.removeEventListener('keydown', Radzen.focusTrap);
if (options.showMask) {
sideDialog.addEventListener('keydown', Radzen.focusTrap);
}
sideDialog.removeEventListener('keydown', Radzen.closeSideDialogOnEsc);
sideDialog.addEventListener('keydown', Radzen.closeSideDialogOnEsc);
}
setTimeout(function () {
if (options.autoFocusFirstElement) {
var dialogs = document.querySelectorAll('.rz-dialog-side-content');
@@ -2577,6 +2604,29 @@ window.Radzen = {
}
}, 500);
},
closeSideDialog: function () {
delete Radzen.sideDialogService;
var invoker = Radzen.sideDialogInvokerElement;
Radzen.sideDialogInvokerElement = null;
if (invoker && typeof invoker.focus === 'function' && document.body.contains(invoker)) {
invoker.focus();
}
},
closeSideDialogOnEsc: function (e) {
e = e || window.event;
var isEscape = "key" in e ? (e.key === "Escape" || e.key === "Esc") : (e.keyCode === 27);
if (isEscape && Radzen.sideDialogService) {
var popups = document.querySelectorAll('.rz-popup,.rz-overlaypanel');
for (var i = 0; i < popups.length; i++) {
if (popups[i].style.display != 'none') {
return;
}
}
try {
Radzen.sideDialogService.invokeMethodAsync('DialogService.TryCloseSide', null);
} catch (ex) { }
}
},
createSideDialogResizer: function(handle, sideDialog, options) {
const normalizeDir = (value) => {
if (typeof value === 'string' && value.length) {
@@ -2666,6 +2716,15 @@ window.Radzen = {
document.body.classList.add('no-scroll');
}
var currentDialogs = document.querySelectorAll('.rz-dialog-content');
var currentDialog = currentDialogs.length ? currentDialogs[currentDialogs.length - 1] : null;
if (currentDialog) {
currentDialog.options = options;
var dialogRoot = currentDialog.closest('.rz-dialog') || currentDialog;
dialogRoot.removeEventListener('keydown', Radzen.focusTrap);
dialogRoot.addEventListener('keydown', Radzen.focusTrap);
}
setTimeout(function () {
var dialogs = document.querySelectorAll('.rz-dialog-content');
if (dialogs.length == 0) return;
@@ -2673,8 +2732,6 @@ window.Radzen = {
if (lastDialog) {
lastDialog.options = options;
lastDialog.removeEventListener('keydown', Radzen.focusTrap);
lastDialog.addEventListener('keydown', Radzen.focusTrap);
if (options.resizable) {
dialog.offsetWidth = lastDialog.parentElement.offsetWidth;
@@ -5806,13 +5863,21 @@ Radzen.chatScrollAfterRender = function(selector, delay) {
if (container) container.scrollTop = container.scrollHeight;
}, delay || 100);
};
Radzen.createAutoComplete = function(el, popupId, openOnFocus) {
Radzen.createAutoComplete = function(el, popupId, openOnFocus, instance, openCallback, closeCallback) {
if (!el) return null;
var input = el.querySelector('input.rz-autocomplete-input, textarea.rz-autocomplete-input');
var list = el.querySelector('.rz-autocomplete-list');
if (!input) return null;
function onInput() { Radzen.openPopup(input.parentNode, popupId, true); }
function onFocus() { Radzen.openPopup(input.parentNode, popupId, true); }
function open() {
var popup = document.getElementById(popupId);
var wasOpen = popup && popup.style.display == 'block';
Radzen.openPopup(input.parentNode, popupId, true, null, null, null, instance, closeCallback);
if (!wasOpen && instance && openCallback) {
try { suppressDisposed(instance.invokeMethodAsync(openCallback)); } catch { }
}
}
function onInput() { open(); }
function onFocus() { open(); }
function clearActive() { Radzen.activeElement = null; }
input.addEventListener('input', onInput);
input.addEventListener('blur', clearActive);

View File

@@ -14,7 +14,7 @@
<RadzenColumn Size="12" SizeLG="4">
<RadzenCard Variant="Variant.Outlined">
<RadzenStack Orientation="Orientation.Vertical" Gap="1rem">
<RadzenText TextStyle="TextStyle.Subtitle1">Date &amp; Time Options</RadzenText>
<RadzenText TextStyle="TextStyle.Subtitle1" TagName="TagName.P">Date &amp; Time Options</RadzenText>
<RadzenStack Orientation="Orientation.Horizontal" AlignItems="AlignItems.Center" Wrap="FlexWrap.Wrap">
<RadzenCheckBox @bind-Value="@showDateSeparator" TValue="bool" Name="showDateSeparator" />

View File

@@ -17,7 +17,7 @@
<RadzenRow>
<RadzenColumn Size="12">
<RadzenCard Variant="Variant.Outlined">
<RadzenText TextStyle="TextStyle.Subtitle1">AI Configuration</RadzenText>
<RadzenText TextStyle="TextStyle.Subtitle1" TagName="TagName.P">AI Configuration</RadzenText>
<RadzenStack Orientation="Orientation.Vertical" Gap="0.5rem">
<RadzenStack Orientation="Orientation.Horizontal" AlignItems="AlignItems.Center" Wrap="FlexWrap.Wrap">
<RadzenLabel Style="white-space: nowrap;">Model:</RadzenLabel>
@@ -50,7 +50,7 @@
</RadzenColumn>
<RadzenColumn Size="12">
<RadzenCard Variant="Variant.Outlined">
<RadzenText TextStyle="TextStyle.Subtitle1">Current Configuration</RadzenText>
<RadzenText TextStyle="TextStyle.Subtitle1" TagName="TagName.P">Current Configuration</RadzenText>
<RadzenStack Orientation="Orientation.Vertical" Gap="0.25rem">
<RadzenText TextStyle="TextStyle.Body2">
<strong>Model:</strong> @selectedModel
@@ -63,7 +63,7 @@
</RadzenColumn>
<RadzenColumn Size="12">
<RadzenCard Variant="Variant.Outlined">
<RadzenText TextStyle="TextStyle.Subtitle1">Available Models</RadzenText>
<RadzenText TextStyle="TextStyle.Subtitle1" TagName="TagName.P">Available Models</RadzenText>
<RadzenStack Orientation="Orientation.Vertical">
@foreach (var model in availableModels)
{

View File

@@ -17,14 +17,14 @@
<RadzenRow>
<RadzenColumn Size="12" SizeLG="12">
<RadzenCard Variant="Variant.Outlined">
<RadzenText TextStyle="TextStyle.Subtitle1">Memory Statistics</RadzenText>
<RadzenText TextStyle="TextStyle.Subtitle1" TagName="TagName.P">Memory Statistics</RadzenText>
<RadzenRow>
<RadzenColumn Size="6">
<RadzenText TextStyle="TextStyle.H4">@activeSessionsCount</RadzenText>
<RadzenText TextStyle="TextStyle.H4" TagName="TagName.P">@activeSessionsCount</RadzenText>
<RadzenText TextStyle="TextStyle.Caption">Active Sessions</RadzenText>
</RadzenColumn>
<RadzenColumn Size="6">
<RadzenText TextStyle="TextStyle.H4">@totalMessagesCount</RadzenText>
<RadzenText TextStyle="TextStyle.H4" TagName="TagName.P">@totalMessagesCount</RadzenText>
<RadzenText TextStyle="TextStyle.Caption">Total Messages</RadzenText>
</RadzenColumn>
</RadzenRow>
@@ -32,7 +32,7 @@
</RadzenColumn>
<RadzenColumn Size="12">
<RadzenCard Variant="Variant.Outlined">
<RadzenText TextStyle="TextStyle.Subtitle1">Session Management</RadzenText>
<RadzenText TextStyle="TextStyle.Subtitle1" TagName="TagName.P">Session Management</RadzenText>
<RadzenStack Orientation="Orientation.Vertical">
<RadzenStack Orientation="Orientation.Horizontal" Wrap="FlexWrap.Wrap">
<RadzenButton Text="New Session" Icon="add" ButtonStyle="ButtonStyle.Primary" Click="@OnNewSession" Variant="Variant.Flat" />
@@ -48,7 +48,7 @@
</RadzenColumn>
<RadzenColumn Size="12">
<RadzenCard Variant="Variant.Outlined">
<RadzenText TextStyle="TextStyle.Subtitle1">Session History</RadzenText>
<RadzenText TextStyle="TextStyle.Subtitle1" TagName="TagName.P">Session History</RadzenText>
<RadzenStack Orientation="Orientation.Vertical">
<RadzenDropDown TValue="string" Data="@sessionIds" @bind-Value="@selectedSessionId" Placeholder="Select a session to load" Style="width: 100%;" />
<RadzenButton Text="Load Selected Session" Icon="folder_open" ButtonStyle="ButtonStyle.Base" Variant="Variant.Flat"
@@ -60,10 +60,10 @@
</RadzenColumn>
<RadzenColumn Size="12">
<RadzenCard Variant="Variant.Outlined">
<RadzenText TextStyle="TextStyle.Subtitle1">Memory Features</RadzenText>
<RadzenText TextStyle="TextStyle.Subtitle1" TagName="TagName.P">Memory Features</RadzenText>
<RadzenRow>
<RadzenColumn Size="12" SizeMD="6">
<RadzenText TextStyle="TextStyle.H6">Conversation Memory</RadzenText>
<RadzenText TextStyle="TextStyle.H6" TagName="TagName.P">Conversation Memory</RadzenText>
<ul>
<li>The AI remembers all previous messages in the conversation</li>
<li>Context is maintained across multiple questions</li>
@@ -72,7 +72,7 @@
</ul>
</RadzenColumn>
<RadzenColumn Size="12" SizeMD="6">
<RadzenText TextStyle="TextStyle.H6">Session Management</RadzenText>
<RadzenText TextStyle="TextStyle.H6" TagName="TagName.P">Session Management</RadzenText>
<ul>
<li>Create new sessions for different conversations</li>
<li>Switch between different conversation contexts</li>

View File

@@ -22,7 +22,7 @@
<RadzenText Anchor="appearance-toggle#video-radzen-blazor-studio-config" TextStyle="TextStyle.H5" TagName="TagName.H2" class="rz-pt-12">
RadzenAppearanceToggle in Radzen Blazor Studio
</RadzenText>
<RadzenText TextStyle="TextStyle.Subtitle1" class="rz-mb-6">
<RadzenText TextStyle="TextStyle.Subtitle1" TagName="TagName.P" class="rz-mb-6">
Learn how to add and configure this built-in component
</RadzenText>
<RadzenCard Variant="Variant.Outlined" class="rz-p-7">

View File

@@ -21,7 +21,7 @@
<RadzenArcGaugeScaleValue Value=@value ShowValue=@showValue>
<Template Context="pointer">
<RadzenStack AlignItems="AlignItems.Center" Gap="0" Style="margin-top: -50%;">
<RadzenText TextStyle="TextStyle.H5" class="rz-m-0"><strong>@pointer.Value</strong></RadzenText>
<RadzenText TextStyle="TextStyle.H5" TagName="TagName.P" class="rz-m-0"><strong>@pointer.Value</strong></RadzenText>
<RadzenText TextStyle="TextStyle.Caption">km/h</RadzenText>
</RadzenStack>
</Template>

Some files were not shown because too many files have changed in this diff Show More