Compare commits

..

25 Commits

Author SHA1 Message Date
Atanas Korchev
7b96fe3cb4 Fix llms.txt generator: preserve @code blocks, fix @page regex, remove noise
- Add \b word boundary to directive regex so @page no longer matches
  @pageSizeOptions, @pageIndex etc., which was stripping attribute values
  and leaving broken Razor markup in the output.
- Stop stripping @code blocks from example files — they contain the C#
  code (methods, fields, event handlers) needed to understand examples.
- Exclude AccessibilityPage, GetStarted, and ThemeServicePage from
  generation (generic setup/WCAG content, not component documentation).
- Skip "Keyboard Navigation" sections (shortcuts rendered from C# data
  that the generator cannot extract, leaving empty placeholders).
- Skip "Radzen Blazor Studio" sections (IDE-specific, not component API).
2026-02-09 20:17:33 +02:00
Atanas Korchev
9a58c3a35a Exclude sub-component pages from llms.txt sections
Sub-component .razor files without @page directives were getting
standalone sections with rendered placeholder text instead of being
embedded as code snippets via <RadzenExample> in their parent pages.
2026-02-09 17:35:55 +02:00
Atanas Korchev
876c248a8e Cleanup llms.txt from marketing content and other noise. 2026-02-09 17:12:48 +02:00
Vladimir Enchev
0ce1c06178 Version updated 2026-02-09 15:44:41 +02:00
Vladimir Enchev
16f4f1839a Fixed EF Core keyless entities exception (SQL View) 2026-02-09 15:44:24 +02:00
Vladimir Enchev
b5e30cbfe0 changelog update with v9 rendering changes 2026-02-09 10:34:46 +02:00
Vladimir Enchev
604fdfb28a net6.0 support removed 2026-02-09 10:34:27 +02:00
Vladimir Enchev
d5b40d5e9b Version updated 2026-02-09 09:56:38 +02:00
yordanov
13e1a55ee8 Update premium themes 2026-02-09 09:52:09 +02:00
Vladimir Enchev
28e9090d45 Various accessibility issues fixes (#2423)
* accessibility issues fixes

Various localizable accessibility related properties added

tests updated

side dialog close button id fixed

more accessibility improvements

tests fixed

more accessibility fixes

missing ARIA attributes added

* Add rz-hidden-accessible styles

* Update RadzenRating styles

* RadzenSwitch fixed

* RadzenFabMenu fixed

* tests updated

* Update expand/collapse button styles in RadzenDataGrid

* Fix responsive pager styles

---------

Co-authored-by: yordanov <vasil@yordanov.info>
2026-02-09 09:25:34 +02:00
Vladimir Enchev
d03020062e Various possible memory leaks fixed 2026-02-09 09:07:29 +02:00
Atanas Korchev
fc6fe54635 Fix 302 redirect during SSR that hurt SEO for clean URLs
During server-side prerendering the ThemeChanged handler would call
NavigateTo which resulted in a 302 redirect (e.g. /datagrid → /datagrid?theme=material3).
This conflicted with the canonical tag pointing back to the clean URL,
confusing Google and causing ranking drops. Unsubscribe from ThemeChanged
during SSR so the redirect no longer occurs. Interactive theme switching
is unaffected since WebAssembly uses a separate scoped instance.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-07 21:33:34 +02:00
Vladimir Enchev
505ffa7fe4 Splitter ChangeStateOnResize added 2026-02-06 12:36:29 +02:00
Vladimir Enchev
4f82ac9599 Accessing sub properties of nullable types improved 2026-02-06 10:36:53 +02:00
Vladimir Enchev
5bbf7c1fde build fixed 2026-02-04 14:46:25 +02:00
Vladimir Enchev
e416cede62 DropDownBase possible null reference exception with browser autofil 2026-02-04 12:08:41 +02:00
Vladimir Enchev
270a7e0f80 DropDown, ListBox and DropDownDatGrid paste using context menu into search box not filtering
Fix #2437
2026-02-04 11:28:12 +02:00
Vladimir Enchev
1a12a75bde Version updated 2026-02-03 09:56:04 +02:00
Vladimir Enchev
d1917eac0c Fixed QRCode eyes with transparent background 2026-02-03 08:44:24 +02:00
Vladimir Enchev
09830f0ea2 DataGrid possible memory leak fixed 2026-02-03 07:46:32 +02:00
Vladimir Enchev
d34e0684fb DataGrid GroupRowRenderEventArgs Expandable property added 2026-02-02 12:09:40 +02:00
Vladimir Enchev
482eca3278 tests fixed 2026-01-30 12:23:10 +02:00
Vladimir Enchev
5c8ac16c83 Version updated 2026-01-30 12:09:54 +02:00
Vladimir Enchev
29382cf0f4 DataGrid QueryOnlyVisibleColumns property added 2026-01-30 12:09:34 +02:00
Vladimir Enchev
53204cc8d6 RadzenPager GoToPage() will not update page index 2026-01-30 09:53:52 +02:00
144 changed files with 2311 additions and 771 deletions

View File

@@ -123,7 +123,7 @@ namespace Radzen.Blazor.Tests
});
// Find and click the accordion header link to expand
var header = component.Find(".rz-accordion-header a");
var header = component.Find(".rz-accordion-header button");
header.Click();
Assert.True(expandRaised);
@@ -156,7 +156,7 @@ namespace Radzen.Blazor.Tests
});
// Find and click the accordion header link to collapse
var header = component.Find(".rz-accordion-header a");
var header = component.Find(".rz-accordion-header button");
header.Click();
Assert.True(collapseRaised);
@@ -184,7 +184,7 @@ namespace Radzen.Blazor.Tests
});
// Try to click the disabled item
var header = component.Find(".rz-accordion-header a");
var header = component.Find(".rz-accordion-header button");
header.Click();
// Event should not be raised for disabled item

View File

@@ -155,7 +155,7 @@ namespace Radzen.Blazor.Tests
parameters.Add<bool>(p => p.AllowClear, true);
});
Assert.Contains(@$"<i class=""notranslate rz-dropdown-clear-icon rzi rzi-times""", component.Markup);
Assert.Contains(@$"<button type=""button"" class=""notranslate rz-dropdown-clear-icon rzi rzi-times""", component.Markup);
}
[Fact]

View File

@@ -152,7 +152,7 @@ namespace Radzen.Blazor.Tests
parameters.Add(p => p.Collapse, args => { raised = true; });
});
component.Find("a").Click();
component.Find("legend button").Click();
Assert.True(raised);
@@ -160,7 +160,7 @@ namespace Radzen.Blazor.Tests
component.SetParametersAndRender(parameters => parameters.Add(p => p.Expand, args => { raised = true; }));
component.Find("a").Click();
component.Find("legend button").Click();
}
[Fact]

View File

@@ -119,7 +119,7 @@ namespace Radzen.Blazor.Tests
parameters.Add(p => p.AllowResetPassword, true);
});
Assert.Contains(@$"Forgot password?</a>", component.Markup);
Assert.Contains(@$"Forgot password?</button>", component.Markup);
}
[Fact]
@@ -134,7 +134,7 @@ namespace Radzen.Blazor.Tests
parameters.Add(p => p.ResetPasswordText, "Test");
});
Assert.Contains(@$"Test</a>", component.Markup);
Assert.Contains(@$"Test</button>", component.Markup);
}
[Fact]
@@ -215,7 +215,7 @@ namespace Radzen.Blazor.Tests
parameters.Add(p => p.ResetPassword, args => { clicked = true; });
});
component.Find("a").Click();
component.Find("button.rz-link").Click();
Assert.True(clicked);
}
@@ -234,7 +234,7 @@ namespace Radzen.Blazor.Tests
parameters.Add(p => p.ResetPassword, args => { clicked = true; });
});
component.Find("a").Click();
component.Find("button.rz-link").Click();
Assert.True(clicked);
}

View File

@@ -2,6 +2,7 @@ using Bunit;
using Bunit.JSInterop;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Xunit;
namespace Radzen.Blazor.Tests
@@ -54,7 +55,7 @@ namespace Radzen.Blazor.Tests
}
[Fact]
public async void RadzenPager_Renders_Summary() {
public async Task RadzenPager_Renders_Summary() {
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
@@ -64,7 +65,7 @@ namespace Radzen.Blazor.Tests
parameters.Add<int>(p => p.Count, 100);
parameters.Add<bool>(p => p.ShowPagingSummary, true);
});
await component.Instance.GoToPage(2);
await component.InvokeAsync(() => component.Instance.GoToPage(2));
component.Render();
Assert.Contains(@$"rz-pager-summary", component.Markup);
@@ -111,7 +112,7 @@ namespace Radzen.Blazor.Tests
}
[Fact]
public async void RadzenPager_First_And_Prev_Buttons_Are_Disabled_When_On_The_First_Page()
public async Task RadzenPager_First_And_Prev_Buttons_Are_Disabled_When_On_The_First_Page()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
@@ -123,18 +124,18 @@ namespace Radzen.Blazor.Tests
parameters.Add<bool>(p => p.ShowPagingSummary, true);
});
await component.Instance.GoToPage(0);
await component.InvokeAsync(() => component.Instance.GoToPage(0));
component.Render();
var firstPageButton = component.Find("a.rz-pager-first");
var firstPageButton = component.Find("button.rz-pager-first");
Assert.True(firstPageButton.HasAttribute("disabled"));
var prevPageButton = component.Find("a.rz-pager-prev");
var prevPageButton = component.Find("button.rz-pager-prev");
Assert.True(prevPageButton.HasAttribute("disabled"));
}
[Fact]
public async void RadzenPager_Last_And_Next_Buttons_Are_Disabled_When_On_The_Last_Page()
public async Task RadzenPager_Last_And_Next_Buttons_Are_Disabled_When_On_The_Last_Page()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
@@ -146,13 +147,13 @@ namespace Radzen.Blazor.Tests
parameters.Add<bool>(p => p.ShowPagingSummary, true);
});
await component.Instance.GoToPage(9);
await component.InvokeAsync(() => component.Instance.GoToPage(9));
component.Render();
var lastPageButton = component.Find("a.rz-pager-last");
var lastPageButton = component.Find("button.rz-pager-last");
Assert.True(lastPageButton.HasAttribute("disabled"));
var nextPageButton = component.Find("a.rz-pager-next");
var nextPageButton = component.Find("button.rz-pager-next");
Assert.True(nextPageButton.HasAttribute("disabled"));
}
}

View File

@@ -167,7 +167,7 @@ namespace Radzen.Blazor.Tests
parameters.Add(p => p.Collapse, args => { raised = true; });
});
component.Find("a").Click();
component.Find("button.rz-panel-titlebar-toggler").Click();
Assert.True(raised);
@@ -175,7 +175,7 @@ namespace Radzen.Blazor.Tests
component.SetParametersAndRender(parameters => parameters.Add(p => p.Expand, args => { raised = true; }));
component.Find("a").Click();
component.Find("button.rz-panel-titlebar-toggler").Click();
}
[Fact]

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using Xunit;
@@ -114,6 +114,11 @@ namespace Radzen.Blazor.Tests
public List<string> Values { get; set; }
}
public class Order
{
public DateTime? OrderDate { get; set; }
}
[Fact]
public void GetProperty_Should_Resolve_DescriptionProperty()
{
@@ -137,6 +142,14 @@ namespace Radzen.Blazor.Tests
Assert.NotNull(idProperty);
}
[Fact]
public void GetPropertyType_Resolves_NullableDateTime_Date()
{
var propertyType = PropertyAccess.GetPropertyType(typeof(Order), "OrderDate.Date");
Assert.Equal(typeof(DateTime), propertyType);
}
interface ISimpleInterface : ISimpleNestedInterface
{
string Description { get; set; }

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using Xunit;
using Radzen;
using System.Linq.Expressions;
namespace Radzen.Blazor.Tests
{
@@ -24,6 +25,11 @@ namespace Radzen.Blazor.Tests
public int Priority { get; set; }
}
private class Order
{
public DateTime? OrderDate { get; set; }
}
private List<TestItem> GetTestData()
{
return new List<TestItem>
@@ -36,6 +42,24 @@ namespace Radzen.Blazor.Tests
};
}
[Fact]
public void GetNestedPropertyExpression_Handles_NullableDateTime_Date()
{
var parameter = Expression.Parameter(typeof(Order), "x");
var expression = QueryableExtension.GetNestedPropertyExpression(parameter, "OrderDate.Date");
var getter = Expression.Lambda<Func<Order, DateTime>>(expression, parameter).Compile();
var order = new Order { OrderDate = new DateTime(2024, 2, 3, 14, 30, 0) };
var result = getter(order);
Assert.Equal(order.OrderDate.Value.Date, result);
var nullOrder = new Order { OrderDate = null };
var nullResult = getter(nullOrder);
Assert.Equal(default(DateTime), nullResult);
}
// OrderBy tests
[Fact]
public void OrderBy_SortsAscending_ByDefault()

View File

@@ -110,7 +110,7 @@ namespace Radzen.Blazor.Tests
component.SetParametersAndRender(parameters => parameters.Add(p => p.Change, args => { raised = true; newValue = args; }));
component.Find("div").Click();
component.Find("input[type=\"checkbox\"]").Click();
Assert.True(raised);
Assert.True(object.Equals(value, !(bool)newValue));
@@ -129,7 +129,7 @@ namespace Radzen.Blazor.Tests
component.SetParametersAndRender(parameters => parameters.Add(p => p.ValueChanged, args => { raised = true; newValue = args; }));
component.Find("div").Click();
component.Find("input[type=\"checkbox\"]").Click();
Assert.True(raised);
Assert.True(object.Equals(value, !(bool)newValue));

View File

@@ -55,7 +55,7 @@ namespace Radzen
/// <summary>
/// Persist the current theme in a cookie. Requires <see cref="ThemeService" /> to be registered in the DI container.
/// </summary>
public class CookieThemeService
public class CookieThemeService : IDisposable
{
private readonly CookieThemeServiceOptions options;
private readonly IJSRuntime jsRuntime;
@@ -121,6 +121,13 @@ namespace Radzen
_ = jsRuntime.InvokeVoidAsync("eval", $"document.cookie = \"{cookie}\"");
}
/// <inheritdoc />
public void Dispose()
{
themeService.ThemeChanged -= OnThemeChanged;
GC.SuppressFinalize(this);
}
}
/// <summary>

View File

@@ -9,6 +9,7 @@ namespace Radzen;
internal class Debouncer : IDisposable
{
private System.Timers.Timer? timer;
private System.Timers.ElapsedEventHandler? timerElapsedHandler;
private DateTime timerStarted { get; set; } = DateTime.UtcNow.AddYears(-1);
/// <summary>
@@ -18,11 +19,9 @@ internal class Debouncer : IDisposable
/// <param name="action">The action to debounce.</param>
public void Debounce(int interval, Func<Task> action)
{
timer?.Stop();
timer = null;
ClearTimer();
timer = new System.Timers.Timer() { Interval = interval, Enabled = false, AutoReset = false };
timer.Elapsed += (s, e) =>
timerElapsedHandler = (s, e) =>
{
if (timer == null)
{
@@ -41,6 +40,7 @@ internal class Debouncer : IDisposable
//
}
};
timer.Elapsed += timerElapsedHandler;
timer.Start();
}
@@ -52,9 +52,7 @@ internal class Debouncer : IDisposable
/// <param name="action">The action to throttle.</param>
public void Throttle(int interval, Func<Task> action)
{
timer?.Stop();
timer = null;
ClearTimer();
var curTime = DateTime.UtcNow;
if (curTime.Subtract(timerStarted).TotalMilliseconds < interval)
@@ -63,7 +61,7 @@ internal class Debouncer : IDisposable
}
timer = new System.Timers.Timer() { Interval = interval, Enabled = false, AutoReset = false };
timer.Elapsed += (s, e) =>
timerElapsedHandler = (s, e) =>
{
if (timer == null)
{
@@ -82,6 +80,7 @@ internal class Debouncer : IDisposable
//
}
};
timer.Elapsed += timerElapsedHandler;
timer.Start();
timerStarted = curTime;
@@ -90,11 +89,24 @@ internal class Debouncer : IDisposable
/// <inheritdoc />
public void Dispose()
{
if(timer != null)
ClearTimer();
}
private void ClearTimer()
{
if (timer == null)
{
timer.Stop();
timer.Dispose();
timer = null;
return;
}
if (timerElapsedHandler != null)
{
timer.Elapsed -= timerElapsedHandler;
timerElapsedHandler = null;
}
timer.Stop();
timer.Dispose();
timer = null;
}
}

View File

@@ -716,6 +716,42 @@ namespace Radzen
}
}
private string? ariaLabel = "Dialog";
/// <summary>
/// Gets or sets the dialog aria-label text when no title is rendered.
/// </summary>
/// <value>The dialog aria-label text.</value>
public string? AriaLabel
{
get => ariaLabel;
set
{
if (ariaLabel != value)
{
ariaLabel = value;
OnPropertyChanged(nameof(AriaLabel));
}
}
}
private string closeAriaLabel = "Close dialog";
/// <summary>
/// Gets or sets the close button aria-label text.
/// </summary>
/// <value>The close button aria-label text.</value>
public string CloseAriaLabel
{
get => closeAriaLabel;
set
{
if (closeAriaLabel != value)
{
closeAriaLabel = value;
OnPropertyChanged(nameof(CloseAriaLabel));
}
}
}
private string? width;
/// <summary>
/// Gets or sets the width of the dialog.

View File

@@ -254,6 +254,13 @@ namespace Radzen
[Parameter]
public string RemoveChipTitle { get; set; } = "Remove";
/// <summary>
/// Gets or sets the clear button aria label text.
/// </summary>
/// <value>The clear button aria label text.</value>
[Parameter]
public string ClearAriaLabel { get; set; } = "Clear";
/// <summary>
/// Gets or sets the search aria label text.
/// </summary>
@@ -558,6 +565,18 @@ namespace Radzen
}
}
/// <summary>
/// Gets the listbox identifier.
/// </summary>
/// <value>The listbox identifier.</value>
protected string ListId
{
get
{
return $"{GetId()}-list";
}
}
/// <summary>
/// Gets the search identifier.
/// </summary>
@@ -635,6 +654,8 @@ namespace Radzen
await JSRuntime.InvokeVoidAsync("Radzen.focusElement", isFilter ? UniqueID : SearchID);
}
isPopupOpen = true;
if (list != null && JSRuntime != null)
{
await JSRuntime.InvokeVoidAsync("Radzen.selectListItem", search, list, selectedIndex);
@@ -651,7 +672,11 @@ namespace Radzen
/// <param name="shouldSelectOnChange">Should select item on item change with keyboard.</param>
protected virtual async Task HandleKeyPress(Microsoft.AspNetCore.Components.Web.KeyboardEventArgs args, bool isFilter = false, bool? shouldSelectOnChange = null)
{
if (Disabled || Data == null || args == null)
ArgumentNullException.ThrowIfNull(args);
var key = args.Code != null ? args.Code : args.Key;
if (Disabled || Data == null || args == null || key == null)
return;
List<object> items = Enumerable.Empty<object>().ToList();
@@ -675,8 +700,6 @@ namespace Radzen
}
}
var key = args.Code != null ? args.Code : args.Key;
if (!args.AltKey && (key == "ArrowDown" || key == "ArrowLeft" || key == "ArrowUp" || key == "ArrowRight"))
{
preventKeydown = true;
@@ -841,8 +864,15 @@ namespace Radzen
{
await JSRuntime.InvokeVoidAsync("Radzen.closePopup", PopupID);
}
isPopupOpen = false;
}
/// <summary>
/// Gets a value indicating whether the popup is open.
/// </summary>
protected bool isPopupOpen;
int itemIndex;
string? previousKey;
@@ -936,6 +966,25 @@ namespace Radzen
}
}
/// <summary>
/// Handles filter input changes (e.g. paste).
/// </summary>
/// <param name="args">The <see cref="ChangeEventArgs"/> instance containing the event data.</param>
protected virtual async Task OnFilterInput(ChangeEventArgs args)
{
ArgumentNullException.ThrowIfNull(args);
searchText = $"{args.Value}";
await SearchTextChanged.InvokeAsync(searchText);
if (ResetSelectedIndexOnFilter)
{
selectedIndex = -1;
}
Debounce(DebounceFilter, FilterDelay);
}
/// <summary>
/// Gets the load data arguments.
/// </summary>
@@ -1269,9 +1318,25 @@ namespace Radzen
await Change.InvokeAsync(internalValue);
}
StateHasChanged();
}
/// <summary>
/// Handles keyboard activation for the select-all action.
/// </summary>
/// <param name="args">The <see cref="Microsoft.AspNetCore.Components.Web.KeyboardEventArgs"/> instance containing the event data.</param>
protected async Task OnSelectAllKeyDown(Microsoft.AspNetCore.Components.Web.KeyboardEventArgs args)
{
ArgumentNullException.ThrowIfNull(args);
var key = args.Code != null ? args.Code : args.Key;
if (key == "Enter" || key == "Space")
{
await SelectAll();
}
}
/// <inheritdoc />
public override object? GetValue()
{

View File

@@ -17,6 +17,12 @@ public class GroupRowRenderEventArgs
/// </summary>
public Group? Group { get; internal set; }
/// <summary>
/// Gets or sets a value indicating whether this group row is expandable.
/// </summary>
/// <value><c>true</c> if expandable; otherwise, <c>false</c>.</value>
public bool Expandable { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this group row is expanded.
/// </summary>

View File

@@ -152,6 +152,12 @@ namespace Radzen
/// <value>The summary content.</value>
public RenderFragment<NotificationService>? SummaryContent { get; set; }
/// <summary>
/// Gets or sets the close button aria-label.
/// </summary>
/// <value>The close button aria-label.</value>
public string CloseAriaLabel { get; set; } = "Close";
#region Implementation of IEquatable<NotificationMessage> and operators overloading

View File

@@ -133,11 +133,16 @@ namespace Radzen
{
if (_data != value)
{
if (_data != null && _data is INotifyCollectionChanged oldCollection)
{
oldCollection.CollectionChanged -= OnCollectionChanged;
}
_data = value;
if (_data != null && _data is INotifyCollectionChanged)
if (_data != null && _data is INotifyCollectionChanged newCollection)
{
((INotifyCollectionChanged)_data).CollectionChanged += OnCollectionChanged;
newCollection.CollectionChanged += OnCollectionChanged;
}
OnDataChanged();

View File

@@ -26,61 +26,79 @@ public static class PropertyAccess
if (propertyName.Contains('[', StringComparison.Ordinal))
{
var arg = Expression.Parameter(typeof(TItem));
return Expression.Lambda<Func<TItem, TValue>>(QueryableExtension.GetNestedPropertyExpression(arg, propertyName ?? string.Empty, type), arg).Compile();
var arg0 = Expression.Parameter(typeof(TItem), "x");
return Expression.Lambda<Func<TItem, TValue>>(
QueryableExtension.GetNestedPropertyExpression(arg0, propertyName, type),
arg0
).Compile();
}
else
var arg = Expression.Parameter(typeof(TItem), "x");
Expression body = arg;
if (type != null)
body = Expression.Convert(body, type);
Expression AccessNoInterface(Expression instance, string memberName)
{
var arg = Expression.Parameter(typeof(TItem));
Expression body = arg;
if (type != null)
if (instance.Type.IsInterface)
{
body = Expression.Convert(body, type);
var declaringType =
new[] { instance.Type }
.Concat(instance.Type.GetInterfaces())
.FirstOrDefault(t => t.GetProperty(memberName) != null);
if (declaringType == null)
throw new InvalidOperationException($"Member '{memberName}' not found on interface '{instance.Type}'.");
return Expression.Property(instance, declaringType, memberName);
}
foreach (var member in propertyName.Split("."))
try
{
if (body.Type.IsInterface)
{
body = Expression.Property(body,
new[] { body.Type }.Concat(body.Type.GetInterfaces()).FirstOrDefault(t => t.GetProperty(member) != null)!,
member
);
}
else
{
try
{
body = Expression.PropertyOrField(body, member);
}
catch (AmbiguousMatchException)
{
var property = body.Type.GetProperty(member, BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy);
if (property != null)
{
body = Expression.Property(body, property);
}
else
{
var field = body.Type.GetField(member, BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy);
if (field != null)
{
body = Expression.Field(body, field);
}
}
}
}
return Expression.PropertyOrField(instance, memberName);
}
catch (AmbiguousMatchException)
{
var prop = instance.Type.GetProperty(memberName,
BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy);
if (prop != null) return Expression.Property(instance, prop);
body = Expression.Convert(body, typeof(TValue));
var field = instance.Type.GetField(memberName,
BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy);
if (field != null) return Expression.Field(instance, field);
return Expression.Lambda<Func<TItem, TValue>>(body, arg).Compile();
throw;
}
}
Expression AccessWithNullPropagation(Expression instance, string memberName)
{
var underlying = Nullable.GetUnderlyingType(instance.Type);
if (underlying != null && memberName is not ("Value" or "HasValue"))
{
var hasValue = Expression.Property(instance, "HasValue");
var value = Expression.Property(instance, "Value");
var accessed = AccessNoInterface(value, memberName);
return Expression.Condition(hasValue, accessed, Expression.Default(accessed.Type));
}
if (!instance.Type.IsValueType)
{
var notNull = Expression.NotEqual(instance, Expression.Constant(null, instance.Type));
var accessed = AccessNoInterface(instance, memberName);
return Expression.Condition(notNull, accessed, Expression.Default(accessed.Type));
}
return AccessNoInterface(instance, memberName);
}
foreach (var member in propertyName.Split('.'))
body = AccessWithNullPropagation(body, member);
body = Expression.Convert(body, typeof(TValue));
return Expression.Lambda<Func<TItem, TValue>>(body, arg).Compile();
}
/// <summary>
@@ -402,7 +420,7 @@ public static class PropertyAccess
if (type != null)
{
return !type.IsInterface ?
type.GetProperty(property ?? "")?.PropertyType :
(Nullable.GetUnderlyingType(type) ?? type).GetProperty(property ?? "")?.PropertyType :
new Type[] { type }
.Concat(type.GetInterfaces())
.FirstOrDefault(t => t.GetProperty(property ?? "") != null)?

View File

@@ -81,7 +81,10 @@ namespace Radzen
}
catch (NotSupportedException)
{
// HttpNavigationManager does not support that
// HttpNavigationManager does not support RegisterLocationChangingHandler.
// This means we are server-side rendering. Unsubscribe from ThemeChanged to
// avoid calling NavigateTo which would cause a 302 redirect during prerendering.
themeService.ThemeChanged -= OnThemeChanged;
}
#endif
}

View File

@@ -1,4 +1,4 @@
using Radzen;
using Radzen;
using Radzen.Blazor;
using System;
using System.Collections;
@@ -41,6 +41,65 @@ namespace Radzen
return source.Provider.CreateQuery(selectExpression);
}
/// <summary>
/// Projects each element of a sequence into a collection of property values.
/// </summary>
public static IQueryable Select(this IQueryable source, IEnumerable<string> propertyNames)
{
ArgumentNullException.ThrowIfNull(source);
ArgumentNullException.ThrowIfNull(propertyNames);
var parameter = Expression.Parameter(source.ElementType, "x");
var bindings = new List<MemberBinding>();
var allProperties = source.ElementType.GetProperties();
foreach (var property in allProperties.Where(p => propertyNames.Contains(p.Name)))
{
bindings.Add(Expression.Bind(property, Expression.Property(parameter, property)));
}
var body = Expression.MemberInit(Expression.New(source.ElementType), bindings);
var delegateType = typeof(Func<,>).MakeGenericType(source.ElementType, source.ElementType);
var lambda = Expression.Lambda(delegateType, body, parameter);
var selectExpression = Expression.Call(typeof(Queryable),
nameof(Queryable.Select), [source.ElementType, source.ElementType], source.Expression,
Expression.Quote(lambda));
return source.Provider.CreateQuery(selectExpression);
}
/// <summary>
/// Projects each element of a sequence into a collection of property values.
/// </summary>
public static IQueryable<T> Select<T>(this IQueryable<T> source, IEnumerable<string> propertyNames)
{
ArgumentNullException.ThrowIfNull(source);
ArgumentNullException.ThrowIfNull(propertyNames);
var parameter = Expression.Parameter(typeof(T), "x");
var bindings = new List<MemberBinding>();
var allProperties = typeof(T).GetProperties();
foreach (var property in allProperties.Where(p => propertyNames.Contains(p.Name)))
{
bindings.Add(Expression.Bind(property, Expression.Property(parameter, property)));
}
var body = Expression.MemberInit(Expression.New(typeof(T)), bindings);
var lambda = Expression.Lambda<Func<T, T>>(body, parameter);
var selectExpression = Expression.Call(typeof(Queryable),
nameof(Queryable.Select), [typeof(T), typeof(T)], source.Expression,
Expression.Quote(lambda));
return source.Provider.CreateQuery<T>(selectExpression);
}
/// <summary>
/// Projects each element of a sequence to an IEnumerable and flattens the resulting sequences into one sequence.
@@ -419,79 +478,156 @@ namespace Radzen
internal static Expression GetNestedPropertyExpression(Expression expression, string property, Type? type = null)
{
ArgumentNullException.ThrowIfNull(expression);
if (string.IsNullOrWhiteSpace(property)) return Expression.Constant(null, typeof(object));
var parts = property.Split(separator, 2);
string currentPart = parts[0];
var currentPart = parts[0];
static Expression BuildIsNull(Expression expr)
{
var underlying = Nullable.GetUnderlyingType(expr.Type);
if (underlying != null)
{
return Expression.Not(Expression.Property(expr, "HasValue"));
}
if (!expr.Type.IsValueType)
{
return Expression.Equal(expr, Expression.Constant(null, expr.Type));
}
return Expression.Constant(false);
}
static Expression UnwrapNullableIfNeeded(Expression expr)
{
return Nullable.GetUnderlyingType(expr.Type) != null
? Expression.Property(expr, "Value")
: expr;
}
static Expression AccessMember(Expression instance, string memberName)
{
var t = instance.Type;
if (t.IsInterface)
{
var declaring =
new[] { t }.Concat(t.GetInterfaces())
.FirstOrDefault(i => i.GetProperty(memberName) != null);
if (declaring == null)
throw new InvalidOperationException($"Member '{memberName}' not found on interface '{t}'.");
return Expression.Property(instance, declaring, memberName);
}
try
{
return Expression.PropertyOrField(instance, memberName);
}
catch (AmbiguousMatchException)
{
var prop = t.GetProperty(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy);
if (prop != null) return Expression.Property(instance, prop);
var field = t.GetField(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy);
if (field != null) return Expression.Field(instance, field);
throw;
}
}
static Expression NullPropagate(Expression parent, Expression accessed)
{
var isNull = BuildIsNull(parent);
var whenNull = Expression.Default(accessed.Type);
return Expression.Condition(isNull, whenNull, accessed);
}
// Skip null propagation for ParameterExpression (the root LINQ parameter is never null).
// Adding a null check like `x == null ? default : x.Property` on the root parameter
// causes EF Core to fail for keyless entity types (e.g. SQL views) because it cannot
// translate `==` on entities without a primary key.
var shouldNullPropagate = expression is not ParameterExpression;
Expression member;
if (expression.Type.IsGenericType && typeof(IDictionary<,>).IsAssignableFrom(expression.Type.GetGenericTypeDefinition()) ||
typeof(IDictionary).IsAssignableFrom(expression.Type) || typeof(System.Data.DataRow).IsAssignableFrom(expression.Type))
var parentForAccess = UnwrapNullableIfNeeded(expression);
if ((parentForAccess.Type.IsGenericType && typeof(IDictionary<,>).IsAssignableFrom(parentForAccess.Type.GetGenericTypeDefinition())) ||
typeof(IDictionary).IsAssignableFrom(parentForAccess.Type) ||
typeof(System.Data.DataRow).IsAssignableFrom(parentForAccess.Type))
{
var key = currentPart.Split('"')[1];
var typeString = currentPart.Split('(')[0];
var indexer = typeof(System.Data.DataRow).IsAssignableFrom(expression.Type) ?
Expression.Property(expression, expression.Type.GetProperty("Item", new[] { typeof(string) })!, Expression.Constant(key)) :
Expression.Property(expression, expression.Type.GetProperty("Item")!, Expression.Constant(key));
member = Expression.Convert(
indexer,
parts.Length > 1 ? indexer.Type : type ?? Type.GetType(typeString.EndsWith('?') ? $"System.Nullable`1[System.{typeString.TrimEnd('?')}]" : $"System.{typeString}") ?? typeof(object));
var indexer =
typeof(System.Data.DataRow).IsAssignableFrom(parentForAccess.Type)
? Expression.Property(parentForAccess, parentForAccess.Type.GetProperty("Item", new[] { typeof(string) })!, Expression.Constant(key))
: Expression.Property(parentForAccess, parentForAccess.Type.GetProperty("Item")!, Expression.Constant(key));
var targetType =
parts.Length > 1
? indexer.Type
: type
?? Type.GetType(
typeString.EndsWith('?')
? $"System.Nullable`1[System.{typeString.TrimEnd('?')}]"
: $"System.{typeString}"
)
?? typeof(object);
member = Expression.Convert(indexer, targetType);
member = shouldNullPropagate ? NullPropagate(expression, member) : member;
}
else if (currentPart.Contains('[', StringComparison.Ordinal)) // Handle array or list indexing
else if (currentPart.Contains('[', StringComparison.Ordinal))
{
var indexStart = currentPart.IndexOf('[', StringComparison.Ordinal);
var propertyName = currentPart.Substring(0, indexStart);
var indexString = currentPart.Substring(indexStart + 1, currentPart.Length - indexStart - 2);
member = Expression.PropertyOrField(expression, propertyName);
if (int.TryParse(indexString, out int index))
var collection = AccessMember(parentForAccess, propertyName);
collection = shouldNullPropagate ? NullPropagate(expression, collection) : collection;
if (!int.TryParse(indexString, out var index))
throw new ArgumentException($"Invalid index format: {indexString}");
var underlyingCollection = Nullable.GetUnderlyingType(collection.Type) != null
? Expression.Property(collection, "Value")
: collection;
Expression indexed;
if (underlyingCollection.Type.IsArray)
{
if (member.Type.IsArray)
{
member = Expression.ArrayIndex(member, Expression.Constant(index));
}
else if (member.Type.IsGenericType &&
(member.Type.GetGenericTypeDefinition() == typeof(List<>) ||
typeof(IList<>).IsAssignableFrom(member.Type.GetGenericTypeDefinition())))
{
var itemProperty = member.Type.GetProperty("Item");
if (itemProperty != null)
{
member = Expression.Property(member, itemProperty, Expression.Constant(index));
}
}
indexed = Expression.ArrayIndex(underlyingCollection, Expression.Constant(index));
}
else
{
throw new ArgumentException($"Invalid index format: {indexString}");
var itemProp = underlyingCollection.Type.GetProperty("Item");
if (itemProp == null)
throw new InvalidOperationException($"Type '{underlyingCollection.Type}' has no indexer 'Item'.");
indexed = Expression.Property(underlyingCollection, itemProp, Expression.Constant(index));
}
}
else if (expression != null && expression.Type != null && expression.Type.IsInterface)
{
member = Expression.Property(expression,
new[] { expression.Type }.Concat(expression.Type.GetInterfaces()).FirstOrDefault(t => t.GetProperty(currentPart) != null)!,
currentPart
);
member = NullPropagate(collection, indexed);
}
else
{
if (expression == null || string.IsNullOrEmpty(currentPart))
{
return Expression.Constant(null, typeof(object));
var accessed = AccessMember(parentForAccess, currentPart);
member = shouldNullPropagate ? NullPropagate(expression, accessed) : accessed;
}
var p = expression.Type?.GetProperty(currentPart, BindingFlags.Public | BindingFlags.Instance);
member = p != null ? Expression.Property(expression, p) : Expression.PropertyOrField(expression, currentPart);
}
if (parts.Length > 1)
return GetNestedPropertyExpression(member, parts[1], type);
if (expression != null && expression.Type != null && expression.Type.IsValueType && Nullable.GetUnderlyingType(expression.Type) == null)
{
expression = Expression.Convert(expression, typeof(object));
}
return parts.Length > 1 ? GetNestedPropertyExpression(member, parts[1], type) :
(Nullable.GetUnderlyingType(member.Type) != null || member.Type == typeof(string)) ?
expression != null ? Expression.Condition(Expression.Equal(expression, Expression.Constant(null)), Expression.Constant(null, member.Type), member) : member :
member;
return member;
}
internal static Expression GetExpression<T>(ParameterExpression parameter, FilterDescriptor filter, FilterCaseSensitivity filterCaseSensitivity, Type type)

View File

@@ -4,14 +4,14 @@
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<NoWarn>BL9993;BL0007;BL0005</NoWarn>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<TargetFrameworks>net6.0;net7.0;net8.0;net9.0;net10.0</TargetFrameworks>
<TargetFrameworks>net7.0;net8.0;net9.0;net10.0</TargetFrameworks>
<RazorLangVersion>7.0</RazorLangVersion>
<LangVersion>latest</LangVersion>
<OutputType>Library</OutputType>
<IsPackable>true</IsPackable>
<PackageId>Radzen.Blazor</PackageId>
<Product>Radzen.Blazor</Product>
<Version>8.7.3</Version>
<Version>9.0.1</Version>
<Copyright>Radzen Ltd.</Copyright>
<Authors>Radzen Ltd.</Authors>
<Description>Radzen Blazor is the most sophisticated free UI component library for Blazor, featuring 100+ native components including DataGrid, Scheduler, Charts, and advanced theming with full support for Material Design and Fluent UI.</Description>

View File

@@ -1,4 +1,4 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.JSInterop;
using Radzen.Blazor.Rendering;
@@ -323,11 +323,13 @@ namespace Radzen.Blazor
return;
IsLoading = true;
var previousCts = cts;
#if NET8_0_OR_GREATER
await cts.CancelAsync();
await previousCts.CancelAsync();
#else
cts.Cancel();
previousCts.Cancel();
#endif
previousCts.Dispose();
cts = new CancellationTokenSource();
// Ensure we have a session ID

View File

@@ -19,9 +19,11 @@
if (!item.Visible)
continue;
var accordionId = $"{GetId()}-accordiontab-{items.IndexOf(item)}";
<div @ref="@item.Element" id="@item.GetItemId()" @attributes="item.Attributes" class="@item.GetItemCssClass()" style="@item.Style" @onkeydown:stopPropagation>
<a @onclick="@((args) => SelectItem(item))" aria-label="@ItemAriaLabel(i, item)" title="@ItemTitle(i, item)" @onclick:preventDefault="true" role="tab"
id="@($"rz-accordiontab-{items.IndexOf(item)}")" aria-controls="@($"rz-accordiontab-{items.IndexOf(item)}-content")" aria-expanded="true">
<button type="button" @onclick="@((args) => SelectItem(item))" aria-label="@ItemAriaLabel(i, item)" title="@ItemTitle(i, item)" role="tab"
id="@accordionId" aria-controls="@($"{accordionId}-content")" aria-expanded="@(item.GetSelected() ? "true" : "false")"
aria-selected="@(item.GetSelected() ? "true" : "false")" tabindex="@(item.GetSelected() ? "0" : "-1")">
<span class="@ToggleIconClass(item)">keyboard_arrow_down</span>
@if (!string.IsNullOrEmpty(item.Icon))
{
@@ -35,10 +37,10 @@
{
<span>@item.Text</span>
}
</a>
</button>
</div>
<Expander Expanded=@item.GetSelected() role="tabpanel"
id="@($"rz-accordiontab-{items.IndexOf(item)}-content")" aria-labelledby="@($"rz-accordiontab-{items.IndexOf(item)}")">
id="@($"{accordionId}-content")" aria-labelledby="@accordionId">
<div class="rz-accordion-content" @onkeydown:stopPropagation>
@item.ChildContent
</div>

View File

@@ -13,25 +13,27 @@
{
<textarea @ref="@search" @attributes="InputAttributes" @onkeydown="@OnFilterKeyPress" value="@Value" disabled="@Disabled"
oninput="@OpenScript()" tabindex="@(Disabled ? "-1" : $"{TabIndex}")" @onchange="@OnChange" onfocus="@(OpenOnFocus ? OpenScript() : null)"
aria-autocomplete="list" aria-haspopup="true" autocomplete="off" role="combobox"
aria-autocomplete="list" aria-haspopup="listbox" aria-controls="@ListId" aria-expanded="@(IsPopupOpen ? "true" : "false")" autocomplete="off" role="combobox"
class=@InputClass onblur="Radzen.activeElement = null"
id="@Name" aria-expanded="true" placeholder="@CurrentPlaceholder" maxlength="@MaxLength" />
id="@Name" placeholder="@CurrentPlaceholder" maxlength="@MaxLength" />
}
else
{
<input @ref="@search" @attributes="InputAttributes" @onkeydown="@OnFilterKeyPress" value="@Value" disabled="@Disabled"
oninput="@OpenScript()" tabindex="@(Disabled ? "-1" : $"{TabIndex}")" @onchange="@OnChange" onfocus="@(OpenOnFocus ? OpenScript() : null)"
aria-autocomplete="list" aria-haspopup="true" autocomplete="off" role="combobox"
aria-autocomplete="list" aria-haspopup="listbox" aria-controls="@ListId" aria-expanded="@(IsPopupOpen ? "true" : "false")" autocomplete="off" role="combobox"
class=@InputClass onblur="Radzen.activeElement = null"
type="@InputType" id="@Name" aria-expanded="true" placeholder="@CurrentPlaceholder" maxlength="@MaxLength" />
type="@InputType" id="@Name" placeholder="@CurrentPlaceholder" maxlength="@MaxLength" />
}
<div id="@PopupID" class="rz-autocomplete-panel" style="@PopupStyle">
<ul @ref="@list" class="rz-autocomplete-items rz-autocomplete-list" role="listbox">
<ul id="@ListId" @ref="@list" class="rz-autocomplete-items rz-autocomplete-list" role="listbox">
@if (OpenOnFocus || (!string.IsNullOrEmpty(searchText) || !string.IsNullOrEmpty(customSearchText)))
{
@foreach (var item in LoadData.HasDelegate ? Data != null ? Data : Enumerable.Empty<object>() : (View != null ? View : Enumerable.Empty<object>()))
{
<li role="option" class="rz-autocomplete-list-item" @onclick="@(() => OnSelectItem(item))" onmousedown="Radzen.activeElement = null">
<li role="option" class="rz-autocomplete-list-item" tabindex="-1"
aria-selected="@(object.Equals(item, SelectedItem) ? "true" : "false")"
@onclick="@(() => OnSelectItem(item))" onmousedown="Radzen.activeElement = null">
<span>
@if (Template != null)
{

View File

@@ -222,6 +222,10 @@ namespace Radzen.Blazor
}
}
private string ListId => $"{PopupID}-list";
private bool IsPopupOpen => OpenOnFocus || (!string.IsNullOrEmpty(searchText) || !string.IsNullOrEmpty(customSearchText));
private async Task OnSelectItem(object item)
{
if (JSRuntime != null)

View File

@@ -7,11 +7,11 @@
<section @ref=@Element style=@Style @attributes=@Attributes class=@GetCssClass() id=@GetId() tabindex="0">
@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">
<RadzenStack class="rz-carousel-pager rz-carousel-pager-top" Orientation="Orientation.Horizontal" AlignItems="AlignItems.Center" JustifyContent="JustifyContent.Center" Wrap="FlexWrap.Wrap" role="list">
@foreach (var item in items)
{
var index = items.IndexOf(item);
<a @onclick="@(args => Navigate(index))" class="rz-carousel-pager-button @(index == selectedIndex ? "rz-state-active" : "")"></a>
<button type="button" role="listitem" @onclick="@(args => Navigate(index))" class="rz-carousel-pager-button @(index == selectedIndex ? "rz-state-active" : "")" aria-label="@string.Format(PagerButtonAriaLabelFormat, index + 1)"></button>
}
</RadzenStack>
}
@@ -30,11 +30,11 @@
</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">
<RadzenStack class="rz-carousel-pager rz-carousel-pager-bottom" Orientation="Orientation.Horizontal" AlignItems="AlignItems.Center" JustifyContent="JustifyContent.Center" Wrap="FlexWrap.Wrap" role="list">
@foreach (var item in items)
{
var index = items.IndexOf(item);
<a @onclick="@(args => Navigate(index))" class="rz-carousel-pager-button @(index == selectedIndex ? "rz-state-active" : "")"></a>
<button type="button" role="listitem" @onclick="@(args => Navigate(index))" class="rz-carousel-pager-button @(index == selectedIndex ? "rz-state-active" : "")" aria-label="@string.Format(PagerButtonAriaLabelFormat, index + 1)"></button>
}
</RadzenStack>
}

View File

@@ -179,6 +179,12 @@ namespace Radzen.Blazor
[Parameter]
public EventCallback<int> Change { get; set; }
/// <summary>
/// Gets or sets the pager button aria-label format. Use {0} for the 1-based slide index.
/// </summary>
[Parameter]
public string PagerButtonAriaLabelFormat { get; set; } = "Go to slide {0}";
/// <inheritdoc />
public override async Task SetParametersAsync(ParameterView parameters)
{

View File

@@ -5,12 +5,14 @@
@inherits FormComponent<TValue>
@if (Visible)
{
<div @ref="@Element" @attributes="Attributes" class="@GetCssClass()" @onkeypress=@OnKeyPress @onkeypress:preventDefault style="@Style" tabindex="@(Disabled || ReadOnly ? "-1" : $"{TabIndex}")" id="@GetId()">
<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")"
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"
tabindex="-1" readonly="@ReadOnly">
</div>
<div class=@BoxClass @onclick=@Toggle @onclick:preventDefault>
<div class=@BoxClass>
<span class=@IconClass></span>
</div>
</div>

View File

@@ -59,6 +59,8 @@ namespace Radzen.Blazor
bool CheckBoxChecked => object.Equals(Value, true);
string CheckBoxAriaChecked => object.Equals(Value, true) ? "true" : Value == null ? "mixed" : "false";
/// <inheritdoc />
protected override string GetComponentCssClass() => GetClassList("rz-chkbox").ToString();

View File

@@ -15,23 +15,27 @@
@if (Visible)
{
<div @ref="@Element" style="@Style" @attributes="Attributes" class="@GetCssClass()" id="@GetId()"
role="checkboxgroup" @onfocus=@OnFocus @onblur=@OnBlur
role="checkboxgroup" aria-disabled="@(Disabled ? "true" : "false")" @onfocus=@OnFocus @onblur=@OnBlur
tabindex="@(Disabled ? "-1" : $"{TabIndex}")" @onkeydown="@(args => OnKeyPress(args))" @onkeydown:preventDefault=preventKeyPress @onkeydown:stopPropagation>
@if (AllowSelectAll)
{
<div class="rz-multiselect-header rz-helper-clearfix" @onclick:preventDefault>
<RadzenCheckBox ReadOnly="@ReadOnly" Disabled="@Disabled" Name="SelectAll" TValue="bool?" Value="@IsAllSelected()" Change="@SelectAll"
InputAttributes="@(new Dictionary<string,object>(){ { "aria-label", SelectAllText ?? "" }})" />
<span @onclick="@(() => SelectAll(!IsAllSelected() ?? true))" class="rz-chkbox-label" style="cursor:pointer">@SelectAllText</span>
<button type="button" @onclick="@(() => SelectAll(!IsAllSelected() ?? true))" class="rz-chkbox-label" disabled="@(Disabled || ReadOnly)">@SelectAllText</button>
</div>
}
<RadzenStack Orientation="@Orientation" JustifyContent="@JustifyContent" AlignItems="@AlignItems" Gap="@Gap" Wrap="@Wrap">
@foreach (var item in allItems.Where(i => i.Visible))
{
<div @ref="@item.Element" id="@item.GetItemId()" @onclick="@(args => SelectItem(item))" @attributes="item.Attributes" class="@item.GetItemCssClass()" style="@item.Style" role="checkbox" aria-checked=@(IsSelected(item)? "true" : "false") aria-label="@item.Text">
<div @ref="@item.Element" id="@item.GetItemId()" @onclick="@(args => SelectItem(item))" @onkeydown="@(args => OnItemKeyDown(args, item))"
@attributes="item.Attributes" class="@item.GetItemCssClass()" style="@item.Style"
role="checkbox" tabindex="@(Disabled || item.Disabled ? "-1" : "0")"
aria-checked=@(IsSelected(item)? "true" : "false") aria-label="@item.Text"
aria-disabled="@(Disabled || item.Disabled ? "true" : "false")">
<div class="rz-chkbox">
<div class="rz-helper-hidden-accessible">
<input type="checkbox" name="@Name" value="@item.Value" disabled="@Disabled" readonly="@ReadOnly" tabindex="-1" aria-label="@(item.Text + " " + item.Value)" aria-checked="@(IsSelected(item).ToString().ToLowerInvariant())">
<input type="checkbox" name="@Name" value="@item.Value" disabled="@(Disabled || item.Disabled)" readonly="@ReadOnly" tabindex="-1" aria-label="@(item.Text + " " + item.Value)" aria-checked="@(IsSelected(item).ToString().ToLowerInvariant())">
</div>
<div class=@ItemClass(item)>
<span class=@IconClass(item)></span>

View File

@@ -332,6 +332,15 @@ namespace Radzen.Blazor
StateHasChanged();
}
async Task OnItemKeyDown(KeyboardEventArgs args, RadzenCheckBoxListItem<TValue> item)
{
var key = args.Code != null ? args.Code : args.Key;
if (key == "Enter" || key == "Space")
{
await SelectItem(item);
}
}
bool focused;
int focusedIndex = -1;
bool preventKeyPress = true;

View File

@@ -5,14 +5,18 @@
@if (Visible)
{
<div @ref=@Element style=@Style @onclick=@Toggle @attributes=@Attributes class=@GetCssClass() id=@GetId() tabindex="@(Disabled ? -1 : TabIndex)" @onkeypress="@(args => OnKeyPress(args, Toggle()))" @onkeypress:preventDefault=preventKeyPress @onkeypress:stopPropagation>
<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>
@if (Icon != null)
{
<i class="notranslate rzi" style="@(!string.IsNullOrEmpty(IconColor) ? $"color:{IconColor}" : null)">@Icon</i>
}
<div class="rz-colorpicker-value" style="background-color: @Value" ></div>
<button aria-label="@ToggleAriaLabel" type="button" tabindex="-1" class="rz-colorpicker-trigger" disabled=@Disabled @onclick:preventDefault><i class="notranslate rzi" /></button>
<Popup Lazy=@(PopupRenderMode == PopupRenderMode.OnDemand) @ref=@Popup class="rz-colorpicker-popup" Close=@OnClosePopup Open=@Open>
<Popup id="@($"{GetId()}-popup")" Lazy=@(PopupRenderMode == PopupRenderMode.OnDemand) @ref=@Popup
role="dialog" aria-label="@PopupAriaLabel"
class="rz-colorpicker-popup" Close=@OnClosePopup Open=@OnPopupOpen>
@if (ShowHSV)
{
<Draggable class="rz-saturation-picker rz-colorpicker-section" style=@($"background-color: hsl({(HueHandleLeft * 360).ToInvariantString()}, 100%, 50%)") Drag=@OnSaturationMove

View File

@@ -42,6 +42,13 @@ namespace Radzen.Blazor
[Parameter]
public string ToggleAriaLabel { get; set; } = "Toggle";
/// <summary>
/// Gets or sets the popup aria label text.
/// </summary>
/// <value>The popup aria label text.</value>
[Parameter]
public string PopupAriaLabel { get; set; } = "Color picker";
/// <summary>
/// Gets or sets the open callback.
/// </summary>
@@ -113,6 +120,7 @@ namespace Radzen.Blazor
public string ButtonText { get; set; } = "OK";
Popup Popup { get; set; } = default!;
bool isPopupOpen;
internal event EventHandler<string>? SelectedColorChanged;
@@ -381,6 +389,7 @@ namespace Radzen.Blazor
async Task OnClosePopup()
{
isPopupOpen = false;
if (ShowButton)
{
SetInitialValue();
@@ -389,6 +398,12 @@ namespace Radzen.Blazor
await Close.InvokeAsync(null);
}
async Task OnPopupOpen()
{
isPopupOpen = true;
await Open.InvokeAsync(null);
}
/// <summary>
/// Gets or sets a value indicating whether button is shown.
/// </summary>

View File

@@ -1,4 +1,5 @@
@using Radzen.Blazor.Rendering
<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></div>

View File

@@ -35,9 +35,9 @@
{
<div class="rz-group-header-item">
<span class="rz-group-header-item-title">@gd.GetTitle()</span>
<a id="@(GetId() + "rg")" aria-label="@RemoveGroupAriaLabel" @onclick:preventDefault="true" @onclick=@(args => RemoveGroupAsync(gd)) role="button" class="rz-dialog-titlebar-icon rz-dialog-titlebar-close">
<button type="button" id="@(GetId() + "rg")" aria-label="@RemoveGroupAriaLabel" @onclick=@(args => RemoveGroupAsync(gd)) class="rz-dialog-titlebar-icon rz-dialog-titlebar-close">
<span class="notranslate rzi rzi-times"></span>
</a>
</button>
</div>
}
</div>
@@ -123,9 +123,12 @@
<span class="rz-column-title">
@if(ShowExpandAll && ExpandMode == DataGridExpandMode.Multiple)
{
<a title=@TitleAttribute() aria-label="@TitleAttribute()" @onclick:preventDefault="true" @onclick="_ => this.ToggleAllRowsExpand()" @onclick:stopPropagation="true">
<span class="@(allRowsExpanded ? "rzi-chevron-circle-down" : "rzi-chevron-circle-right")"></span>
</a>
<button type="button" title=@TitleAttribute() aria-label="@TitleAttribute()"
aria-expanded="@(allRowsExpanded.ToString().ToLowerInvariant())"
class="rz-button rz-button-sm rz-button-icon-only rz-variant-text rz-base rz-shade-default"
@onclick="_ => this.ToggleAllRowsExpand()" @onclick:stopPropagation="true">
<span class="@(allRowsExpanded ? "rzi-chevron-circle-down" : "rzi-chevron-circle-right")"></span>
</button>
}
else
{
@@ -329,7 +332,10 @@
{
if (filterMode == FilterMode.Simple)
{
<button aria-label="@FilterToggleAriaLabel" class="rz-button rz-button-md rz-button-icon-only rz-variant-flat rz-base rz-shade-default @(column.HasActiveFilter() ? "rz-grid-filter-active" : "")" onclick="@($"Radzen.togglePopup(this.parentNode, '{PopupID}{column.GetFilterProperty()}')")">
<button type="button" aria-label="@FilterToggleAriaLabel" aria-controls="@($"{PopupID}{column.GetFilterProperty()}")"
aria-haspopup="dialog" aria-expanded="false"
class="rz-button rz-button-md rz-button-icon-only rz-variant-flat rz-base rz-shade-default @(column.HasActiveFilter() ? "rz-grid-filter-active" : "")"
onclick="@($"Radzen.togglePopup(this.parentNode, '{PopupID}{column.GetFilterProperty()}')")">
<i class="notranslate rzi">date_range</i>
</button>
<span>
@@ -338,16 +344,16 @@
@if (filterValue != null && filters.Any(d => d.Property == column.GetFilterProperty()))
{
<span class="rz-current-filter">@string.Format("{0:" + getFilterDateFormat(column) + "}", filterValue)</span>
<i @onclick="@((args) => ClearFilter(column))" class="notranslate rzi rz-cell-filter-clear">close</i>
<button type="button" class="notranslate rzi rz-cell-filter-clear" aria-label="@ClearFilterText" @onclick="@((args) => ClearFilter(column))">close</button>
}
else if ((filterOperator == FilterOperator.IsNull || filterOperator == FilterOperator.IsNotNull) && filters.Any(d => d.Property == column.GetFilterProperty()))
{
<span class="rz-current-filter">@column.GetFilterOperatorText(filterOperator)</span>
<i @onclick="@((args) => ClearFilter(column))" class="notranslate rzi rz-cell-filter-clear">close</i>
<button type="button" class="notranslate rzi rz-cell-filter-clear" aria-label="@ClearFilterText" @onclick="@((args) => ClearFilter(column))">close</button>
}
</span>
<div id="@($"{PopupID}{column.GetFilterProperty()}")" class="rz-overlaypanel rz-grid-date-filter"
style="display:none;" tabindex="0">
style="display:none;" tabindex="0" role="dialog" aria-label="@FilterText">
<div class="rz-overlaypanel-content">
<div class="rz-date-filter">
@@ -357,62 +363,82 @@
<ul class="rz-listbox-list">
@if (column.GetFilterOperators().Contains(FilterOperator.Equals))
{
<li class="@(DateFilterOperatorStyle(column, FilterOperator.Equals))" @onclick="@((args) => ApplyDateFilterByFilterOperator(column, FilterOperator.Equals))" style="display: block;">
<span>@EqualsText</span>
<li role="presentation">
<button type="button" class="@(DateFilterOperatorStyle(column, FilterOperator.Equals)) rz-filter-menu-item" @onclick="@((args) => ApplyDateFilterByFilterOperator(column, FilterOperator.Equals))">
<span>@EqualsText</span>
</button>
</li>
}
@if (column.GetFilterOperators().Contains(FilterOperator.NotEquals))
{
<li class="@(DateFilterOperatorStyle(column, FilterOperator.NotEquals))" @onclick="@((args) => ApplyDateFilterByFilterOperator(column, FilterOperator.NotEquals))" style="display: block;">
<span>@NotEqualsText</span>
<li role="presentation">
<button type="button" class="@(DateFilterOperatorStyle(column, FilterOperator.NotEquals)) rz-filter-menu-item" @onclick="@((args) => ApplyDateFilterByFilterOperator(column, FilterOperator.NotEquals))">
<span>@NotEqualsText</span>
</button>
</li>
}
@if (column.GetFilterOperators().Contains(FilterOperator.LessThan))
{
<li class="@(DateFilterOperatorStyle(column, FilterOperator.LessThan))" @onclick="@((args) => ApplyDateFilterByFilterOperator(column, FilterOperator.LessThan))" style="display: block;">
<span>@LessThanText</span>
<li role="presentation">
<button type="button" class="@(DateFilterOperatorStyle(column, FilterOperator.LessThan)) rz-filter-menu-item" @onclick="@((args) => ApplyDateFilterByFilterOperator(column, FilterOperator.LessThan))">
<span>@LessThanText</span>
</button>
</li>
}
@if (column.GetFilterOperators().Contains(FilterOperator.LessThanOrEquals))
{
<li class="@(DateFilterOperatorStyle(column, FilterOperator.LessThanOrEquals))" @onclick="@((args) => ApplyDateFilterByFilterOperator(column, FilterOperator.LessThanOrEquals))" style="display: block;">
<span>@LessThanOrEqualsText</span>
<li role="presentation">
<button type="button" class="@(DateFilterOperatorStyle(column, FilterOperator.LessThanOrEquals)) rz-filter-menu-item" @onclick="@((args) => ApplyDateFilterByFilterOperator(column, FilterOperator.LessThanOrEquals))">
<span>@LessThanOrEqualsText</span>
</button>
</li>
}
@if (column.GetFilterOperators().Contains(FilterOperator.GreaterThan))
{
<li class="@(DateFilterOperatorStyle(column, FilterOperator.GreaterThan))" @onclick="@((args) => ApplyDateFilterByFilterOperator(column, FilterOperator.GreaterThan))" style="display: block;">
<span>@GreaterThanText</span>
<li role="presentation">
<button type="button" class="@(DateFilterOperatorStyle(column, FilterOperator.GreaterThan)) rz-filter-menu-item" @onclick="@((args) => ApplyDateFilterByFilterOperator(column, FilterOperator.GreaterThan))">
<span>@GreaterThanText</span>
</button>
</li>
}
@if (column.GetFilterOperators().Contains(FilterOperator.GreaterThanOrEquals))
{
<li class="@(DateFilterOperatorStyle(column, FilterOperator.GreaterThanOrEquals))" @onclick="@((args) => ApplyDateFilterByFilterOperator(column, FilterOperator.GreaterThanOrEquals))" style="display: block;">
<span>@GreaterThanOrEqualsText</span>
<li role="presentation">
<button type="button" class="@(DateFilterOperatorStyle(column, FilterOperator.GreaterThanOrEquals)) rz-filter-menu-item" @onclick="@((args) => ApplyDateFilterByFilterOperator(column, FilterOperator.GreaterThanOrEquals))">
<span>@GreaterThanOrEqualsText</span>
</button>
</li>
}
@if (column.GetFilterOperators().Contains(FilterOperator.IsEmpty))
{
<li class="@(DateFilterOperatorStyle(column, FilterOperator.IsEmpty))" @onclick="@((args) => ApplyDateFilterByFilterOperator(column, FilterOperator.IsEmpty))" style="display: block;">
<span>@IsEmptyText</span>
<li role="presentation">
<button type="button" class="@(DateFilterOperatorStyle(column, FilterOperator.IsEmpty)) rz-filter-menu-item" @onclick="@((args) => ApplyDateFilterByFilterOperator(column, FilterOperator.IsEmpty))">
<span>@IsEmptyText</span>
</button>
</li>
}
@if (column.GetFilterOperators().Contains(FilterOperator.IsNotEmpty))
{
<li class="@(DateFilterOperatorStyle(column, FilterOperator.IsNotEmpty))" @onclick="@((args) => ApplyDateFilterByFilterOperator(column, FilterOperator.IsNotEmpty))" style="display: block;">
<span>@IsNotEmptyText</span>
<li role="presentation">
<button type="button" class="@(DateFilterOperatorStyle(column, FilterOperator.IsNotEmpty)) rz-filter-menu-item" @onclick="@((args) => ApplyDateFilterByFilterOperator(column, FilterOperator.IsNotEmpty))">
<span>@IsNotEmptyText</span>
</button>
</li>
}
@if (column.GetFilterOperators().Contains(FilterOperator.IsNull))
{
<li class="@(DateFilterOperatorStyle(column, FilterOperator.IsNull))" @onclick="@((args) => ApplyDateFilterByFilterOperator(column, FilterOperator.IsNull))" style="display: block;">
<span>@IsNullText</span>
<li role="presentation">
<button type="button" class="@(DateFilterOperatorStyle(column, FilterOperator.IsNull)) rz-filter-menu-item" @onclick="@((args) => ApplyDateFilterByFilterOperator(column, FilterOperator.IsNull))">
<span>@IsNullText</span>
</button>
</li>
}
@if (column.GetFilterOperators().Contains(FilterOperator.IsNotNull))
{
<li class="@(DateFilterOperatorStyle(column, FilterOperator.IsNotNull))" @onclick="@((args) => ApplyDateFilterByFilterOperator(column, FilterOperator.IsNotNull))" style="display: block;">
<span>@IsNotNullText</span>
<li role="presentation">
<button type="button" class="@(DateFilterOperatorStyle(column, FilterOperator.IsNotNull)) rz-filter-menu-item" @onclick="@((args) => ApplyDateFilterByFilterOperator(column, FilterOperator.IsNotNull))">
<span>@IsNotNullText</span>
</button>
</li>
}
</ul>
@@ -504,7 +530,7 @@
<input autocomplete="off" aria-label=@(column.Title + FilterValueAriaLabel + column.GetFilterValue()) disabled=@(!column.CanSetFilterValue()) id="@(getFilterInputId(column))" @onchange="@((args) => OnFilter(args, column))" @onkeydown="@((args) => OnFilterKeyPress(args, column))" value="@column.GetFilterValue()" type="text" placeholder="@column.GetFilterPlaceholder()" class="rz-textbox" style="width: 100%;" />
@if (column.GetFilterValue() != null && filters.Any(d => d.Property == column.Property && $"{d.FilterProperty}" == $"{column.FilterProperty}"))
{
<i @onclick="@((args) => ClearFilter(column))" class="notranslate rzi rz-cell-filter-clear" style="position:absolute;inset-inline-end:10px;">close</i>
<button type="button" class="notranslate rzi rz-cell-filter-clear" style="position:absolute;inset-inline-end:10px;" aria-label="@ClearFilterText" @onclick="@((args) => ClearFilter(column))">close</button>
}
}
}
@@ -607,9 +633,12 @@
@if (this.LoadChildData.HasDelegate && this.ShowExpandColumn && this.allColumns.IndexOf(column) == 0)
{
<span class="rz-cell-toggle">
<a id="@(GetId() + "exp")" aria-label="@ExpandChildItemAriaLabel" class="@(getExpandIconCssClass(this, Item))" style="@(getExpandIconStyle(this, Item, rowArgs.Item1.Expandable))" @onclick:preventDefault="true" @onclick="_ => this.ExpandItem(Item)" @onclick:stopPropagation="true">
<span class="@(this.ExpandedItemStyle(Item))"></span>
</a>
<button type="button" id="@(GetId() + "exp")" aria-label="@ExpandChildItemAriaLabel"
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">
<span class="@(getExpandIconCssClass(this, Item)) @(this.ExpandedItemStyle(Item))"></span>
</button>
<span class="@column.GetCellClass()" @attributes="@spanAttributes">
@if (Item != null)
{

View File

@@ -10,6 +10,7 @@ using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Globalization;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Text.Json;
using System.Threading.Tasks;
@@ -892,7 +893,7 @@ namespace Radzen.Blazor
}
}
void ToggleColumns()
async Task ToggleColumns()
{
if (selectedColumns == null)
{
@@ -906,8 +907,13 @@ namespace Radzen.Blazor
c.SetVisible(selected.Contains(c));
}
PickedColumnsChanged.InvokeAsync(new DataGridPickedColumnsChangedEventArgs<TItem>() { Columns = selected });
await PickedColumnsChanged.InvokeAsync(new DataGridPickedColumnsChangedEventArgs<TItem>() { Columns = selected });
SaveSettings();
if (QueryOnlyVisibleColumns)
{
await Reload();
}
}
/// <summary>
@@ -1627,6 +1633,13 @@ namespace Radzen.Blazor
[Parameter]
public int ColumnsPickerMaxSelectedLabels { get; set; } = 2;
/// <summary>
/// Gets or sets a value indicating whether only visible columns are included in the query.
/// </summary>
/// <value><c>true</c> if only visible columns are included; otherwise, <c>false</c>.</value>
[Parameter]
public bool QueryOnlyVisibleColumns { get; set; }
/// <summary>
/// Gets or sets the column picker all columns text.
/// </summary>
@@ -2016,7 +2029,8 @@ namespace Radzen.Blazor
}
}
return view;
return QueryOnlyVisibleColumns ? view
.Select(allColumns.Where(c => c.GetVisible() && !string.IsNullOrEmpty(c.Property)).Select(c => c.Property)) : view;
}
}
@@ -2496,7 +2510,7 @@ namespace Radzen.Blazor
internal Tuple<GroupRowRenderEventArgs, IReadOnlyDictionary<string, object>> GroupRowAttributes(RadzenDataGridGroupRow<TItem> item)
{
var args = new Radzen.GroupRowRenderEventArgs() { Group = item.Group, FirstRender = firstRender };
var args = new Radzen.GroupRowRenderEventArgs() { Group = item.Group, FirstRender = firstRender, Expandable = item.GroupResult.Count > 0 };
if (GroupRowRender != null)
{
@@ -3568,6 +3582,11 @@ namespace Radzen.Blazor
groups.CollectionChanged -= GroupsCollectionChanged;
}
if (sortDescriptors != null)
{
sortDescriptors.CollectionChanged -= SortsCollectionChanged;
}
if (IsJSRuntimeAvailable && JSRuntime != null)
{
foreach (var column in allColumns.ToList().Where(c => c.GetVisible()))
@@ -3576,6 +3595,59 @@ namespace Radzen.Blazor
}
}
if (expandedItems != null)
{
expandedItems.Clear();
}
if (editedItems != null)
{
editedItems.Clear();
}
if (editContexts != null)
{
editContexts.Clear();
}
if (childData != null)
{
childData.Clear();
}
if (selectedItems != null)
{
selectedItems.Clear();
}
if (rowSpans != null)
{
rowSpans.Clear();
}
if (columns != null)
{
columns.Clear();
}
if (allPickableColumns != null)
{
allPickableColumns.Clear();
}
if (allColumns != null)
{
allColumns.Clear();
}
if (childColumns != null)
{
childColumns.Clear();
}
_value = null;
Data = null;
GC.SuppressFinalize(this);
}

View File

@@ -2,7 +2,8 @@
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.JSInterop
@inject IJSRuntime JSRuntime
<button title=@Column.GetFilterOperatorText(Column.GetFilterOperator()) class="@FilterIconStyle()" onclick="@($"Radzen.togglePopup(this.parentNode, '{filterId}')")">
<button title=@Column.GetFilterOperatorText(Column.GetFilterOperator()) class="@FilterIconStyle()" onclick="@($"Radzen.togglePopup(this.parentNode, '{filterId}')")"
aria-haspopup="menu" aria-controls="@filterId" aria-expanded="false">
<i class="notranslate rzi">@Grid.FilterIcon</i>
@if (Column.GetFilterOperator() == FilterOperator.DoesNotContain)
{
@@ -14,110 +15,144 @@
}
</button>
<div id="@($"{filterId}")" class="rz-overlaypanel"
style="display:none;" tabindex="0">
style="display:none;" tabindex="0" role="menu" aria-label="@Grid.FilterText">
<div class="rz-overlaypanel-content">
<ul class="rz-listbox-list">
@if (Column.FilterPropertyType != null && (QueryableExtension.IsEnumerable(Column.FilterPropertyType) || Column.FilterPropertyType == typeof(string)))
{
@if (Column.GetFilterOperators().Contains(FilterOperator.Contains))
{
<li class="@(FilterOperatorStyle(Column, FilterOperator.Contains))" @onclick="@(args => ApplyFilter(FilterOperator.Contains))" style="display: block;">
<span class="rz-filter-menu-symbol">@Column.GetFilterOperatorSymbol(FilterOperator.Contains)</span><span>@Grid.ContainsText</span>
<li role="presentation">
<button type="button" class="@(FilterOperatorStyle(Column, FilterOperator.Contains)) rz-filter-menu-item" @onclick="@(args => ApplyFilter(FilterOperator.Contains))">
<span class="rz-filter-menu-symbol">@Column.GetFilterOperatorSymbol(FilterOperator.Contains)</span><span>@Grid.ContainsText</span>
</button>
</li>
}
@if (Column.GetFilterOperators().Contains(FilterOperator.DoesNotContain))
{
<li class="@(FilterOperatorStyle(Column, FilterOperator.DoesNotContain))" @onclick="@(args => ApplyFilter(FilterOperator.DoesNotContain))" style="display: block;">
<span class="rz-filter-menu-symbol"><s>@Column.GetFilterOperatorSymbol(FilterOperator.DoesNotContain)</s></span><span>@Grid.DoesNotContainText</span>
<li role="presentation">
<button type="button" class="@(FilterOperatorStyle(Column, FilterOperator.DoesNotContain)) rz-filter-menu-item" @onclick="@(args => ApplyFilter(FilterOperator.DoesNotContain))">
<span class="rz-filter-menu-symbol"><s>@Column.GetFilterOperatorSymbol(FilterOperator.DoesNotContain)</s></span><span>@Grid.DoesNotContainText</span>
</button>
</li>
}
@if (Column.GetFilterOperators().Contains(FilterOperator.In))
{
<li class="@(FilterOperatorStyle(Column, FilterOperator.In))" @onclick="@(args => ApplyFilter(FilterOperator.In))" style="display: block;">
<span class="rz-filter-menu-symbol">@Column.GetFilterOperatorSymbol(FilterOperator.In)</span><span>@Grid.InText</span>
<li role="presentation">
<button type="button" class="@(FilterOperatorStyle(Column, FilterOperator.In)) rz-filter-menu-item" @onclick="@(args => ApplyFilter(FilterOperator.In))">
<span class="rz-filter-menu-symbol">@Column.GetFilterOperatorSymbol(FilterOperator.In)</span><span>@Grid.InText</span>
</button>
</li>
}
@if (Column.GetFilterOperators().Contains(FilterOperator.NotIn))
{
<li class="@(FilterOperatorStyle(Column, FilterOperator.NotIn))" @onclick="@(args => ApplyFilter(FilterOperator.NotIn))" style="display: block;">
<span class="rz-filter-menu-symbol"><s>@Column.GetFilterOperatorSymbol(FilterOperator.NotIn)</s></span><span>@Grid.NotInText</span>
<li role="presentation">
<button type="button" class="@(FilterOperatorStyle(Column, FilterOperator.NotIn)) rz-filter-menu-item" @onclick="@(args => ApplyFilter(FilterOperator.NotIn))">
<span class="rz-filter-menu-symbol"><s>@Column.GetFilterOperatorSymbol(FilterOperator.NotIn)</s></span><span>@Grid.NotInText</span>
</button>
</li>
}
@if (Column.GetFilterOperators().Contains(FilterOperator.StartsWith) && Column.FilterPropertyType == typeof(string))
{
<li class="@(FilterOperatorStyle(Column, FilterOperator.StartsWith))" @onclick="@(args => ApplyFilter(FilterOperator.StartsWith))" style="display: block;">
<span class="rz-filter-menu-symbol">@Column.GetFilterOperatorSymbol(FilterOperator.StartsWith)</span><span>@Grid.StartsWithText</span>
<li role="presentation">
<button type="button" class="@(FilterOperatorStyle(Column, FilterOperator.StartsWith)) rz-filter-menu-item" @onclick="@(args => ApplyFilter(FilterOperator.StartsWith))">
<span class="rz-filter-menu-symbol">@Column.GetFilterOperatorSymbol(FilterOperator.StartsWith)</span><span>@Grid.StartsWithText</span>
</button>
</li>
}
@if (Column.GetFilterOperators().Contains(FilterOperator.EndsWith) && Column.FilterPropertyType == typeof(string))
{
<li class="@(FilterOperatorStyle(Column, FilterOperator.EndsWith))" @onclick="@(args => ApplyFilter(FilterOperator.EndsWith))" style="display: block;">
<span class="rz-filter-menu-symbol">@Column.GetFilterOperatorSymbol(FilterOperator.EndsWith)</span><span>@Grid.EndsWithText</span>
<li role="presentation">
<button type="button" class="@(FilterOperatorStyle(Column, FilterOperator.EndsWith)) rz-filter-menu-item" @onclick="@(args => ApplyFilter(FilterOperator.EndsWith))">
<span class="rz-filter-menu-symbol">@Column.GetFilterOperatorSymbol(FilterOperator.EndsWith)</span><span>@Grid.EndsWithText</span>
</button>
</li>
}
}
@if (Column.GetFilterOperators().Contains(FilterOperator.Equals))
{
<li class="@(FilterOperatorStyle(Column, FilterOperator.Equals))" @onclick="@(args => ApplyFilter(FilterOperator.Equals))" style="display: block;">
<span class="rz-filter-menu-symbol">@Column.GetFilterOperatorSymbol(FilterOperator.Equals)</span><span>@Grid.EqualsText</span>
<li role="presentation">
<button type="button" class="@(FilterOperatorStyle(Column, FilterOperator.Equals)) rz-filter-menu-item" @onclick="@(args => ApplyFilter(FilterOperator.Equals))">
<span class="rz-filter-menu-symbol">@Column.GetFilterOperatorSymbol(FilterOperator.Equals)</span><span>@Grid.EqualsText</span>
</button>
</li>
}
@if (Column.GetFilterOperators().Contains(FilterOperator.NotEquals))
{
<li class="@(FilterOperatorStyle(Column, FilterOperator.NotEquals))" @onclick="@(args => ApplyFilter(FilterOperator.NotEquals))" style="display: block;">
<span class="rz-filter-menu-symbol">@Column.GetFilterOperatorSymbol(FilterOperator.NotEquals)</span><span>@Grid.NotEqualsText</span>
<li role="presentation">
<button type="button" class="@(FilterOperatorStyle(Column, FilterOperator.NotEquals)) rz-filter-menu-item" @onclick="@(args => ApplyFilter(FilterOperator.NotEquals))">
<span class="rz-filter-menu-symbol">@Column.GetFilterOperatorSymbol(FilterOperator.NotEquals)</span><span>@Grid.NotEqualsText</span>
</button>
</li>
}
@if (Column.GetFilterOperators().Contains(FilterOperator.LessThan))
{
<li class="@(FilterOperatorStyle(Column, FilterOperator.LessThan))" @onclick="@(args => ApplyFilter(FilterOperator.LessThan))" style="display: block;">
<span class="rz-filter-menu-symbol">@Column.GetFilterOperatorSymbol(FilterOperator.LessThan)</span><span>@Grid.LessThanText</span>
<li role="presentation">
<button type="button" class="@(FilterOperatorStyle(Column, FilterOperator.LessThan)) rz-filter-menu-item" @onclick="@(args => ApplyFilter(FilterOperator.LessThan))">
<span class="rz-filter-menu-symbol">@Column.GetFilterOperatorSymbol(FilterOperator.LessThan)</span><span>@Grid.LessThanText</span>
</button>
</li>
}
@if (Column.GetFilterOperators().Contains(FilterOperator.LessThanOrEquals))
{
<li class="@(FilterOperatorStyle(Column, FilterOperator.LessThanOrEquals))" @onclick="@(args => ApplyFilter(FilterOperator.LessThanOrEquals))" style="display: block;">
<span class="rz-filter-menu-symbol">@Column.GetFilterOperatorSymbol(FilterOperator.LessThanOrEquals)</span><span>@Grid.LessThanOrEqualsText</span>
<li role="presentation">
<button type="button" class="@(FilterOperatorStyle(Column, FilterOperator.LessThanOrEquals)) rz-filter-menu-item" @onclick="@(args => ApplyFilter(FilterOperator.LessThanOrEquals))">
<span class="rz-filter-menu-symbol">@Column.GetFilterOperatorSymbol(FilterOperator.LessThanOrEquals)</span><span>@Grid.LessThanOrEqualsText</span>
</button>
</li>
}
@if (Column.GetFilterOperators().Contains(FilterOperator.GreaterThan))
{
<li class="@(FilterOperatorStyle(Column, FilterOperator.GreaterThan))" @onclick="@(args => ApplyFilter(FilterOperator.GreaterThan))" style="display: block;">
<span class="rz-filter-menu-symbol">@Column.GetFilterOperatorSymbol(FilterOperator.GreaterThan)</span><span>@Grid.GreaterThanText</span>
<li role="presentation">
<button type="button" class="@(FilterOperatorStyle(Column, FilterOperator.GreaterThan)) rz-filter-menu-item" @onclick="@(args => ApplyFilter(FilterOperator.GreaterThan))">
<span class="rz-filter-menu-symbol">@Column.GetFilterOperatorSymbol(FilterOperator.GreaterThan)</span><span>@Grid.GreaterThanText</span>
</button>
</li>
}
@if (Column.GetFilterOperators().Contains(FilterOperator.GreaterThanOrEquals))
{
<li class="@(FilterOperatorStyle(Column, FilterOperator.GreaterThanOrEquals))" @onclick="@(args => ApplyFilter(FilterOperator.GreaterThanOrEquals))" style="display: block;">
<span class="rz-filter-menu-symbol">@Column.GetFilterOperatorSymbol(FilterOperator.GreaterThanOrEquals)</span><span>@Grid.GreaterThanOrEqualsText</span>
<li role="presentation">
<button type="button" class="@(FilterOperatorStyle(Column, FilterOperator.GreaterThanOrEquals)) rz-filter-menu-item" @onclick="@(args => ApplyFilter(FilterOperator.GreaterThanOrEquals))">
<span class="rz-filter-menu-symbol">@Column.GetFilterOperatorSymbol(FilterOperator.GreaterThanOrEquals)</span><span>@Grid.GreaterThanOrEqualsText</span>
</button>
</li>
}
@if (Column.GetFilterOperators().Contains(FilterOperator.IsNull))
{
<li class="@(FilterOperatorStyle(Column, FilterOperator.IsNull))" @onclick="@(args => ApplyFilter(FilterOperator.IsNull))" style="display: block;">
<span class="rz-filter-menu-symbol">@Column.GetFilterOperatorSymbol(FilterOperator.IsNull)</span><span>@Grid.IsNullText</span>
<li role="presentation">
<button type="button" class="@(FilterOperatorStyle(Column, FilterOperator.IsNull)) rz-filter-menu-item" @onclick="@(args => ApplyFilter(FilterOperator.IsNull))">
<span class="rz-filter-menu-symbol">@Column.GetFilterOperatorSymbol(FilterOperator.IsNull)</span><span>@Grid.IsNullText</span>
</button>
</li>
}
@if (Column.GetFilterOperators().Contains(FilterOperator.IsNotNull))
{
<li class="@(FilterOperatorStyle(Column, FilterOperator.IsNotNull))" @onclick="@(args => ApplyFilter(FilterOperator.IsNotNull))" style="display: block;">
<span class="rz-filter-menu-symbol">@Column.GetFilterOperatorSymbol(FilterOperator.IsNotNull)</span><span>@Grid.IsNotNullText</span>
<li role="presentation">
<button type="button" class="@(FilterOperatorStyle(Column, FilterOperator.IsNotNull)) rz-filter-menu-item" @onclick="@(args => ApplyFilter(FilterOperator.IsNotNull))">
<span class="rz-filter-menu-symbol">@Column.GetFilterOperatorSymbol(FilterOperator.IsNotNull)</span><span>@Grid.IsNotNullText</span>
</button>
</li>
}
@if (Column.GetFilterOperators().Contains(FilterOperator.IsEmpty) && Column.FilterPropertyType == typeof(string))
{
<li class="@(FilterOperatorStyle(Column, FilterOperator.IsEmpty))" @onclick="@(args => ApplyFilter(FilterOperator.IsEmpty))" style="display: block;">
<span class="rz-filter-menu-symbol">@Column.GetFilterOperatorSymbol(FilterOperator.IsEmpty)</span><span>@Grid.IsEmptyText</span>
<li role="presentation">
<button type="button" class="@(FilterOperatorStyle(Column, FilterOperator.IsEmpty)) rz-filter-menu-item" @onclick="@(args => ApplyFilter(FilterOperator.IsEmpty))">
<span class="rz-filter-menu-symbol">@Column.GetFilterOperatorSymbol(FilterOperator.IsEmpty)</span><span>@Grid.IsEmptyText</span>
</button>
</li>
}
@if (Column.GetFilterOperators().Contains(FilterOperator.IsNotEmpty) && Column.FilterPropertyType == typeof(string))
{
<li class="@(FilterOperatorStyle(Column, FilterOperator.IsNotEmpty))" @onclick="@(args => ApplyFilter(FilterOperator.IsNotEmpty))" style="display: block;">
<span class="rz-filter-menu-symbol">@Column.GetFilterOperatorSymbol(FilterOperator.IsNotEmpty)</span><span>@Grid.IsNotEmptyText</span>
<li role="presentation">
<button type="button" class="@(FilterOperatorStyle(Column, FilterOperator.IsNotEmpty)) rz-filter-menu-item" @onclick="@(args => ApplyFilter(FilterOperator.IsNotEmpty))">
<span class="rz-filter-menu-symbol">@Column.GetFilterOperatorSymbol(FilterOperator.IsNotEmpty)</span><span>@Grid.IsNotEmptyText</span>
</button>
</li>
}
<li class="rz-multiselect-item" @onclick="@(args => ClearFilter())" style="display:block;">
<span class="rz-filter-menu-symbol">x</span><span>@Grid.ClearFilterText</span>
<li role="presentation">
<button type="button" class="rz-multiselect-item rz-filter-menu-item" @onclick="@(args => ClearFilter())">
<span class="rz-filter-menu-symbol">x</span><span>@Grid.ClearFilterText</span>
</button>
</li>
</ul>
</div>

View File

@@ -26,9 +26,15 @@
}
<td class="rz-col-icon">
<span class="rz-column-title"></span>
<a id="@(Grid.GridId() + Group.GetHashCode())" aria-label=@Grid.ExpandGroupAriaLabel @onclick:preventDefault="true" @onclick="@(_ => Grid.ExpandGroupItem(this, rowArgs?.Item1.Expanded))">
@if (rowArgs?.Item1.Expandable == true)
{
<button type="button" id="@(Grid.GridId() + Group.GetHashCode())" aria-label=@Grid.ExpandGroupAriaLabel
aria-expanded="@(Grid.IsGroupItemExpanded(this).ToString().ToLowerInvariant())"
class="rz-button rz-button-sm rz-button-icon-only rz-variant-text rz-base rz-shade-default"
@onclick="@(_ => Grid.ExpandGroupItem(this, rowArgs?.Item1.Expanded))">
<span class="@(Grid.ExpandedGroupItemStyle(this, Grid.allGroupsExpanded != null ? Grid.allGroupsExpanded : rowArgs?.Item1.Expanded))"></span>
</a>
</button>
}
</td>
}
<td colspan="@(TotalColumnCount + (Grid?.Groups.Count ?? 0) - 1 - Group.Level + ((Grid?.Template != null && Grid?.ShowExpandColumn == true) ? 1 : 0))">

View File

@@ -7,8 +7,11 @@
@if (RowIndex == Column.GetLevel())
{
<th rowspan="@(Column.GetRowSpan())" colspan="@(Column.GetColSpan())" @attributes="@Attributes" class="@CssClass" scope="col" style="@GetStyle()" @onmouseup=@(args => Grid.EndColumnReorder(args, ColumnIndex))>
<div @onclick='@((args) => Grid.OnSort(args, Column))' @onkeydown="OnSortKeyPressed">
<th rowspan="@(Column.GetRowSpan())" colspan="@(Column.GetColSpan())" @attributes="@Attributes" class="@CssClass"
scope="col" style="@GetStyle()" @onmouseup=@(args => Grid.EndColumnReorder(args, ColumnIndex))
aria-sort="@(Grid.AllowSorting && Column.Sortable ? (Column.GetSortOrder() == SortOrder.Ascending ? "ascending" : Column.GetSortOrder() == SortOrder.Descending ? "descending" : "none") : null)">
<div @onclick='@((args) => Grid.OnSort(args, Column))' @onkeydown="OnSortKeyPressed"
role="@(Grid.AllowSorting && Column.Sortable ? "button" : null)">
@if ((Grid.AllowColumnReorder && Column.Reorderable || Grid.AllowGrouping && Column.Groupable))
{
<span id="@(Grid.getColumnUniqueId(ColumnIndex) + "-drag")" class="rz-column-drag"
@@ -60,12 +63,15 @@
@{var filterMode = Column.FilterMode ?? Grid.FilterMode;}
@if (Grid.AllowFiltering && Column.Filterable && (filterMode == FilterMode.Advanced || filterMode == FilterMode.CheckBoxList))
{
<i @ref=@filterButton @onclick:stopPropagation="true" @onmousedown=@ToggleFilter
class="@getFilterIconCss(Column)" onclick=@getFilterOpen() @onclick:preventDefault="true">
<button type="button" @ref=@filterButton @onclick:stopPropagation="true" @onmousedown=@ToggleFilter
class="@getFilterIconCss(Column)" onclick=@getFilterOpen() @onclick:preventDefault="true"
aria-haspopup="dialog" aria-controls="@Column.GetColumnPopupID()" aria-expanded="false"
aria-label="@Grid.FilterToggleAriaLabel">
@Grid.FilterIcon
</i>
</button>
<Popup Lazy=@(Grid.FilterPopupRenderMode == PopupRenderMode.OnDemand) @ref=popup id="@($"{Column.GetColumnPopupID()}")" class="rz-overlaypanel"
role="dialog" aria-label="@Grid.FilterText"
style="display:none;min-width:250px;" @onkeydown="OnFilterPopupKeyPressed">
<div class="rz-overlaypanel-content">
@if (Column.FilterTemplate != null)

View File

@@ -27,9 +27,12 @@
<span class="rz-column-title"></span>
@if (rowArgs?.Item1.Expandable == true && !(Grid?.LoadChildData.HasDelegate ?? false))
{
<a id="@((Grid?.GridId() ?? "") + Item.GetHashCode())" aria-label="@(Grid?.ExpandChildItemAriaLabel ?? "")" @onclick:preventDefault="true" @onclick="@(_ => Grid?.ExpandItem(Item)!)" @onclick:stopPropagation>
<button type="button" id="@((Grid?.GridId() ?? "") + Item.GetHashCode())" aria-label="@(Grid?.ExpandChildItemAriaLabel ?? "")"
aria-expanded="@(Grid != null && Grid.IsRowExpanded(Item) ? "true" : "false")"
class="rz-button rz-button-sm rz-button-icon-only rz-variant-text rz-base rz-shade-default"
@onclick="@(_ => Grid?.ExpandItem(Item)!)" @onclick:stopPropagation>
<span class="@(Grid?.ExpandedItemStyle(Item) ?? "")"></span>
</a>
</button>
}
</td>
}

View File

@@ -17,27 +17,30 @@
}
@if (ShowButton)
{
<button aria-label="@ToggleAriaLabel" @onmousedown=@OnToggle class="@($"rz-datepicker-trigger{(ShowInput ? " rz-datepicker-field-button" : "")} rz-button rz-button-icon-only{(Disabled ? " rz-state-disabled" : "")} {ButtonClass}")" tabindex="-1" type="button">
<button aria-label="@ToggleAriaLabel" aria-haspopup="dialog" aria-controls="@PopupID" aria-expanded="false"
@onmousedown=@OnToggle class="@($"rz-datepicker-trigger{(ShowInput ? " rz-datepicker-field-button" : "")} rz-button rz-button-icon-only{(Disabled ? " rz-state-disabled" : "")} {ButtonClass}")" tabindex="-1" type="button">
<span aria-hidden="true" class="@ButtonClasses"></span><span class="rz-button-text"></span>
</button>
}
@if (AllowClear && HasValue && (ShowInput || !ShowButton))
{
<i class="notranslate rz-dropdown-clear-icon rzi rzi-times" @onclick="@Clear" @onclick:stopPropagation="true"></i>
<button type="button" class="notranslate rz-dropdown-clear-icon rzi rzi-times" aria-label="@ClearAriaLabel" @onclick="@Clear" @onclick:stopPropagation="true"></button>
}
}
<Popup id="@PopupID" Lazy=@(PopupRenderMode == PopupRenderMode.OnDemand) @ref=@popup style=@PopupStyle class="@($"{(Inline ? "rz-datepicker-inline-container " : "rz-datepicker-popup-container ")}")">
<Popup id="@PopupID" Lazy=@(PopupRenderMode == PopupRenderMode.OnDemand) @ref=@popup style=@PopupStyle
role="dialog" aria-label="@PopupAriaLabel"
class="@($"{(Inline ? "rz-datepicker-inline-container " : "rz-datepicker-popup-container ")}")">
<div class="rz-calendar" @onkeydown="@OnPopupKeyDown">
@if (!TimeOnly)
{
<div class="rz-calendar-header">
<a id="@(GetId() + "pm")" tabindex="-1" aria-label="@PrevMonthAriaLabel" @onclick:preventDefault="true" class="rz-button rz-button-md rz-variant-text rz-button-icon-only rz-secondary rz-shade-default rz-calendar-prev" @onclick="@(() => { if (!Disabled) { try { if(CurrentDate.AddMonths(-1).Year >= YearFrom) {CurrentDate = CurrentDate.AddMonths(-1);}} catch (ArgumentOutOfRangeException) {}} })">
<button id="@(GetId() + "pm")" type="button" tabindex="-1" aria-label="@PrevMonthAriaLabel" class="rz-button rz-button-md rz-variant-text rz-button-icon-only rz-secondary rz-shade-default rz-calendar-prev" @onclick="@(() => { if (!Disabled) { try { if(CurrentDate.AddMonths(-1).Year >= YearFrom) {CurrentDate = CurrentDate.AddMonths(-1);}} catch (ArgumentOutOfRangeException) {}} })" disabled="@Disabled" aria-disabled="@(Disabled ? "true" : "false")">
<span class="notranslate rzi rz-calendar-prev-icon"></span>
</a>
<a id="@(GetId() + "nm")" tabindex="-1" aria-label="@NextMonthAriaLabel" @onclick:preventDefault="true" class="rz-button rz-button-md rz-variant-text rz-button-icon-only rz-secondary rz-shade-default rz-calendar-next" @onclick="@(() => { if (!Disabled) { try { if(CurrentDate.AddMonths(1).Year <= YearTo) {CurrentDate = CurrentDate.AddMonths(1);}} catch (ArgumentOutOfRangeException) {} } })">
</button>
<button id="@(GetId() + "nm")" type="button" tabindex="-1" aria-label="@NextMonthAriaLabel" class="rz-button rz-button-md rz-variant-text rz-button-icon-only rz-secondary rz-shade-default rz-calendar-next" @onclick="@(() => { if (!Disabled) { try { if(CurrentDate.AddMonths(1).Year <= YearTo) {CurrentDate = CurrentDate.AddMonths(1);}} catch (ArgumentOutOfRangeException) {} } })" disabled="@Disabled" aria-disabled="@(Disabled ? "true" : "false")">
<span class="notranslate rzi rz-calendar-next-icon"></span>
</a>
</button>
<div class="rz-calendar-title">
<RadzenDropDown @ref="monthDropDown" class="rz-calendar-month-dropdown" TabIndex="@TabIndex"
TValue="int" Value="@CurrentDate.Month" Disabled="@Disabled" Data="@months" TextProperty="Name" ValueProperty="Value"
@@ -88,8 +91,10 @@
DateTime date = StartDate.AddDays(dayNumber++);
var dateArgs = DateAttributes(date);
<td @attributes="@(dateArgs.Attributes)" class="@GetDayCssClass(date, dateArgs)"
@onclick="@(async () => { if (!Disabled && !dateArgs.Disabled) { await SetDay(date); } })">
<td @attributes="@(dateArgs.Attributes)" class="@GetDayCssClass(date, dateArgs)"
role="button" tabindex="@(Disabled || dateArgs.Disabled ? "-1" : "0")"
@onclick="@(async () => { if (!Disabled && !dateArgs.Disabled) { await SetDay(date); } })"
@onkeydown="@(args => OnDayKeyDown(args, date, dateArgs))">
<span class=@GetDayCssClass(date, dateArgs, false)>@date.Day</span>
</td>
}
@@ -112,7 +117,7 @@
<div class="rz-timepicker" @onmousedown:stopPropagation>
@if (ShowHour)
{
<RadzenNumeric InputAttributes="@(new Dictionary<string,object>(){ { "aria-label", "hour" }})" TValue="int" Disabled="@Disabled" Value="@(HourFormat == "12" ? ((CurrentDate.Hour + 11) % 12) + 1 : CurrentDate.Hour)"
<RadzenNumeric InputAttributes="@(new Dictionary<string,object>(){ { "aria-label", HourAriaLabel }})" TValue="int" Disabled="@Disabled" Value="@(HourFormat == "12" ? ((CurrentDate.Hour + 11) % 12) + 1 : CurrentDate.Hour)"
Min="@(HourFormat == "12" ? 1 : -1)" Max="@(HourFormat == "12" ? 12 : 24)" Step="@HoursStep"
Change="@UpdateHour" class="rz-hour-picker" @oninput=@OnUpdateHourInput Format="@(PadHours ? "00" : "")" Name="@($"{UniqueID}-h")" />
}
@@ -121,7 +126,7 @@
<div class="rz-separator">
<span>:</span>
</div>
<RadzenNumeric InputAttributes="@(new Dictionary<string,object>(){ { "aria-label", "minutes" }})" TValue="int" Disabled="@Disabled" Value="CurrentDate.Minute" Step="@MinutesStep" Min="0" Max="59"
<RadzenNumeric InputAttributes="@(new Dictionary<string,object>(){ { "aria-label", MinutesAriaLabel }})" TValue="int" Disabled="@Disabled" Value="CurrentDate.Minute" Step="@MinutesStep" Min="0" Max="59"
Change="@UpdateMinutes" class="rz-minute-picker" @oninput=@OnUpdateHourMinutes Format="@(PadMinutes ? "00" : "")" Name="@($"{UniqueID}-m")"/>
}
@if (ShowSeconds)
@@ -129,19 +134,19 @@
<div class="rz-separator">
<span>:</span>
</div>
<RadzenNumeric InputAttributes="@(new Dictionary<string,object>(){ { "aria-label", "seconds" }})" TValue="int" Disabled="@Disabled" Value="CurrentDate.Second" Step="@SecondsStep" Min="0" Max="59"
<RadzenNumeric InputAttributes="@(new Dictionary<string,object>(){ { "aria-label", SecondsAriaLabel }})" TValue="int" Disabled="@Disabled" Value="CurrentDate.Second" Step="@SecondsStep" Min="0" Max="59"
Change="@UpdateSeconds" class="rz-second-picker" @oninput=@OnUpdateHourSeconds Format="@(PadSeconds ? "00" : "")" Name="@($"{UniqueID}-s")"/>
}
@if (HourFormat == "12")
{
<div class="rz-ampm-picker">
<a id="@(GetId() + "ampmup")" tabindex="@(Disabled ? "-1" : $"{TabIndex}")" aria-label="@ToggleAmPmAriaLabel" @onclick:preventDefault="true" @onclick="@ToggleAmPm">
<button id="@(GetId() + "ampmup")" type="button" tabindex="@(Disabled ? "-1" : $"{TabIndex}")" aria-label="@ToggleAmPmAriaLabel" @onclick="@ToggleAmPm" disabled="@Disabled" aria-disabled="@(Disabled ? "true" : "false")">
<span class="notranslate rzi rzi-chevron-up"></span>
</a>
</button>
<span>@CurrentDate.ToString("tt")</span>
<a id="@(GetId() + "ampmdown")" tabindex="@(Disabled ? "-1" : $"{TabIndex}")" aria-label="@ToggleAmPmAriaLabel" @onclick:preventDefault="true" @onclick="@ToggleAmPm">
<button id="@(GetId() + "ampmdown")" type="button" tabindex="@(Disabled ? "-1" : $"{TabIndex}")" aria-label="@ToggleAmPmAriaLabel" @onclick="@ToggleAmPm" disabled="@Disabled" aria-disabled="@(Disabled ? "true" : "false")">
<span class="notranslate rzi rzi-chevron-down"></span>
</a>
</button>
</div>
}
@if (ShowTimeOkButton)

View File

@@ -70,6 +70,41 @@ namespace Radzen.Blazor
[Parameter]
public string ToggleAriaLabel { get; set; } = "Toggle";
/// <summary>
/// Gets or sets the popup aria label text.
/// </summary>
/// <value>The popup aria label text.</value>
[Parameter]
public string PopupAriaLabel { get; set; } = "Date picker";
/// <summary>
/// Gets or sets the clear button aria label text.
/// </summary>
/// <value>The clear button aria label text.</value>
[Parameter]
public string ClearAriaLabel { get; set; } = "Clear";
/// <summary>
/// Gets or sets the hour input aria label text.
/// </summary>
/// <value>The hour input aria label text.</value>
[Parameter]
public string HourAriaLabel { get; set; } = "Hour";
/// <summary>
/// Gets or sets the minutes input aria label text.
/// </summary>
/// <value>The minutes input aria label text.</value>
[Parameter]
public string MinutesAriaLabel { get; set; } = "Minutes";
/// <summary>
/// Gets or sets the seconds input aria label text.
/// </summary>
/// <value>The seconds input aria label text.</value>
[Parameter]
public string SecondsAriaLabel { get; set; } = "Seconds";
/// <summary>
/// Gets or sets the OK button aria label text.
/// </summary>
@@ -453,6 +488,15 @@ namespace Radzen.Blazor
return args;
}
async Task OnDayKeyDown(KeyboardEventArgs args, DateTime date, DateRenderEventArgs dateArgs)
{
var key = args.Code != null ? args.Code : args.Key;
if ((key == "Enter" || key == "Space") && !Disabled && !dateArgs.Disabled)
{
await SetDay(date);
}
}
/// <summary>
/// Gets or sets the kind of DateTime bind to control
/// </summary>

View File

@@ -1,8 +1,29 @@
@implements IDisposable
@implements IAsyncDisposable
@using Microsoft.JSInterop
@using Radzen.Blazor.Rendering
@inject IJSRuntime JSRuntime
@{
var sideOptions = sideDialogOptions;
var sideDialogId = $"rz-dialog-side-{sideOptions?.GetHashCode()}";
var sideDialogLabelId = $"{sideDialogId}-label";
var sideShowTitle = sideOptions?.ShowTitle == true;
var sideAriaAttributes = new Dictionary<string, object?>();
if (sideShowTitle)
{
sideAriaAttributes["aria-labelledby"] = sideDialogLabelId;
}
else
{
sideAriaAttributes["aria-label"] = sideOptions?.AriaLabel ?? sideDialog?.Title ?? sideOptions?.Title;
}
if (sideOptions?.ShowMask == true)
{
sideAriaAttributes["aria-modal"] = "true";
}
}
@foreach (var dialog in dialogs)
{
<DialogContainer @key=@dialog Dialog=@dialog ShowMask=@(dialog==dialogs.LastOrDefault()) />
@@ -12,57 +33,58 @@
{
<aside
class="@GetSideDialogCssClass()"
role="dialog"
@ref="sideDialogElement"
tabindex="0"
style="@GetSideDialogStyle()"
aria-labelledby="rz-dialog-side-label">
@if (sideDialogOptions?.Resizable == true)
@attributes="sideAriaAttributes">
@if (sideOptions?.Resizable == true)
{
<div @ref="resizeBarElement"
class="@GetResizeBarCssClass()"
title="@sideDialogOptions?.ResizeBarTitle" aria-label="@sideDialogOptions?.ResizeBarAriaLabel">
title="@sideOptions?.ResizeBarTitle" aria-label="@sideOptions?.ResizeBarAriaLabel">
<span class="rz-resize" aria-hidden="true"></span>
</div>
}
@if (sideDialogOptions?.ShowTitle == true)
@if (sideShowTitle)
{
<div class="rz-dialog-side-titlebar">
<div class="rz-dialog-side-title" style="display: inline" id="rz-dialog-side-label">
@if (sideDialogOptions.TitleContent != null)
<div class="rz-dialog-side-title" style="display: inline" id="@sideDialogLabelId">
@if (sideOptions?.TitleContent != null)
{
@sideDialogOptions.TitleContent(Service!)
@sideOptions.TitleContent(Service!)
}
else if (!string.IsNullOrEmpty(sideDialog?.Title))
{
@sideDialog.Title
}
else
else if (!string.IsNullOrEmpty(sideOptions?.Title))
{
@sideDialogOptions.Title
@sideOptions.Title
}
</div>
@if (sideDialogOptions.ShowClose)
@if (sideOptions?.ShowClose == true)
{
<a id="@(sideDialogOptions.GetHashCode() + "cl")" aria-label="@CloseSideDialogAriaLabel" @onclick:preventDefault="true" class="rz-dialog-side-titlebar-close" @onclick="@(_ => Service!.CloseSide())" role="button" tabindex="@sideDialogOptions.CloseTabIndex">
<button id="@($"rz-dialog-side-{sideOptions?.GetHashCode() ?? 0}-cl")" type="button" aria-label="@CloseSideDialogAriaLabel" class="rz-dialog-side-titlebar-close" @onclick="@(_ => Service!.CloseSide())" tabindex="@(sideOptions?.CloseTabIndex ?? 0)">
<span class="notranslate rzi rzi-times"></span>
</a>
</button>
}
</div>
}
<div class="@SideDialogContentCssClass" style="@sideDialogOptions?.Style">
<div class="@SideDialogContentCssClass" style="@sideOptions?.Style">
@sideDialogContent
</div>
</aside>
@if (dialogs.Count == 0 && sideDialogOptions?.ShowMask == true)
@if (dialogs.Count == 0 && sideOptions?.ShowMask == true)
{
@if (sideDialogOptions.CloseDialogOnOverlayClick)
@if (sideOptions.CloseDialogOnOverlayClick)
{
<div @onclick="@Service!.CloseSide" class="rz-dialog-mask"></div>
<div @onclick="@Service!.CloseSide" class="rz-dialog-mask" aria-hidden="true" tabindex="-1"></div>
}
else
{
<div class="rz-dialog-mask"></div>
<div class="rz-dialog-mask" aria-hidden="true" tabindex="-1"></div>
}
}
}
@@ -141,11 +163,15 @@
/// <inheritdoc />
public void Dispose()
{
if (Service is null) return;
Service.OnOpen -= OnOpen;
Service.OnClose -= OnClose;
Service.OnSideOpen -= OnSideOpen;
Service.OnSideClose -= OnSideClose;
if (Service != null)
{
Service.OnOpen -= OnOpen;
Service.OnClose -= OnClose;
Service.OnSideOpen -= OnSideOpen;
Service.OnSideClose -= OnSideClose;
}
_ = DisposeSideDialogResizeHandleAsync();
}
/// <inheritdoc />
@@ -175,7 +201,9 @@
WrapperCssClass = options.WrapperCssClass,
ContentCssClass = options.ContentCssClass,
CloseTabIndex = options.CloseTabIndex,
TitleContent = options.TitleContent
TitleContent = options.TitleContent,
AriaLabel = options.AriaLabel,
CloseAriaLabel = options.CloseAriaLabel
};
// Create a Dialog object for the side dialog to support cascading parameter
@@ -295,17 +323,36 @@
/// <returns>A ValueTask that represents the asynchronous disposal operation.</returns>
public async ValueTask DisposeAsync()
{
Dispose();
await DisposeSideDialogResizeHandleAsync();
}
private async Task DisposeSideDialogResizeHandleAsync()
{
if (sideDialogResizeHandleJsModule == null)
{
return;
}
try
{
if (sideDialogResizeHandleJsModule != null)
{
await sideDialogResizeHandleJsModule.InvokeVoidAsync("dispose");
}
await sideDialogResizeHandleJsModule.InvokeVoidAsync("dispose");
}
catch
{
/* Ignore */
}
try
{
await sideDialogResizeHandleJsModule.DisposeAsync();
}
catch
{
/* Ignore */
}
sideDialogResizeHandleJsModule = null;
}

View File

@@ -8,7 +8,9 @@
@if (Visible)
{
<div @ref="@Element" @attributes="Attributes" class="@GetCssClass()" onmousedown="Radzen.activeElement = null" @onclick="@(args => OpenPopup("ArrowDown", false, true))" @onclick:preventDefault @onclick:stopPropagation style="@Style" tabindex="@(Disabled ? "-1" : $"{TabIndex}")"
<div @ref="@Element" @attributes="Attributes" class="@GetCssClass()" onmousedown="Radzen.activeElement = null"
role="combobox" aria-haspopup="listbox" aria-expanded="@isPopupOpen.ToString().ToLowerInvariant()" aria-controls="@ListId" 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"
@@ -54,7 +56,13 @@
@GetItemOrValueFromProperty(item, TextProperty ?? string.Empty)
}
</span>
<button tabindex="0" title="@(RemoveChipTitle + " " + GetItemOrValueFromProperty(item, TextProperty ?? ""))" type=button class="rz-button rz-button-sm rz-button-icon-only rz-base rz-shade-default @(Disabled ?"rz-state-disabled":"")" @onclick:preventDefault @onclick:stopPropagation @onclick="() => OnChipRemove(item)"><RadzenIcon Icon="close" /></button>
<button tabindex="0"
title="@(RemoveChipTitle + " " + GetItemOrValueFromProperty(item, TextProperty ?? ""))"
aria-label="@(RemoveChipTitle + " " + GetItemOrValueFromProperty(item, TextProperty ?? ""))"
type=button class="rz-button rz-button-sm rz-button-icon-only rz-base rz-shade-default @(Disabled ?"rz-state-disabled":"")"
@onclick:preventDefault @onclick:stopPropagation @onclick="() => OnChipRemove(item)">
<RadzenIcon Icon="close" />
</button>
</div>
}
</div>
@@ -133,9 +141,13 @@
<div class="rz-multiselect-header rz-helper-clearfix" @onclick:preventDefault>
@if(AllowSelectAll && Data != null && Data.Cast<object>().Any())
{
<div class="rz-chkbox" title="@(!AllowFiltering ? "" : SelectAllText)" @onclick="@SelectAll">
<div class="rz-chkbox" title="@(!AllowFiltering ? "" : SelectAllText)" @onclick="@SelectAll"
role="checkbox" tabindex="@(Disabled || ReadOnly ? "-1" : "0")"
aria-checked="@(IsAllSelected().ToString().ToLowerInvariant())"
aria-disabled="@(Disabled || ReadOnly ? "true" : "false")"
@onkeydown="OnSelectAllKeyDown">
<div class="rz-helper-hidden-accessible">
<input readonly="readonly" type="checkbox" id="@($"{(Name ?? UniqueID + "sa")}")" aria-label="@SearchAriaLabel" aria-checked="@(IsAllSelected().ToString().ToLowerInvariant())">
<input readonly="readonly" type="checkbox" id="@($"{(Name ?? UniqueID + "sa")}")" aria-label="@SelectAllText" aria-checked="@(IsAllSelected().ToString().ToLowerInvariant())">
</div>
<div class="@(IsAllSelected() ? "notranslate rz-chkbox-box rz-state-active" : "notranslate rz-chkbox-box")">
<span class="@(IsAllSelected() ? "notranslate rz-chkbox-icon rzi rzi-check" : "notranslate rz-chkbox-icon")"></span>
@@ -144,28 +156,30 @@
}
@if (AllowSelectAll && !AllowFiltering && !string.IsNullOrEmpty(SelectAllText) && Data != null && Data.Cast<object>().Any())
{
<span style="cursor:pointer" @onclick="@SelectAll">@SelectAllText</span>
<button type="button" class="rz-multiselect-selectall" disabled="@(Disabled || ReadOnly)" @onclick="@SelectAll">@SelectAllText</button>
}
@if (AllowFiltering)
{
<div class="rz-multiselect-filter-container">
<input id="@SearchID" tabindex="@(Disabled ? "-1" : $"{TabIndex}")" class="rz-inputtext" role="textbox" type="text"
onclick="Radzen.preventDefaultAndStopPropagation(event)" aria-label="@SearchAriaLabel"
@ref="@search" @oninput=@(args => { searchText = $"{args.Value}"; SearchTextChanged.InvokeAsync(searchText);})
@onchange="@((args) => OnFilter(args))" @onkeydown="@((args) => OnFilterKeyPress(args))" value="@searchText" autocomplete="@FilterAutoCompleteType" />
@ref="@search" @oninput="@OnFilterInput"
@onchange="@OnFilter" @onkeydown="@OnFilterKeyPress" value="@searchText" autocomplete="@FilterAutoCompleteType" />
<span class="notranslate rz-multiselect-filter-icon rzi rzi-search"></span>
</div>
}
<a id="@(GetId() + "clear")" class="rz-multiselect-close " @onclick="@ClearAll" @onclick:stopPropagation="true">
<button type="button" id="@(GetId() + "clear")" class="rz-multiselect-close" aria-label="@ClearAriaLabel"
@onclick="@ClearAll" @onclick:stopPropagation="true">
<span class="notranslate rzi rzi-times"></span>
</a>
</button>
</div>
}
}
<div class="@(Multiple ? "rz-multiselect-items-wrapper" : "rz-dropdown-items-wrapper")" style="@PopupStyle">
@if (Data != null && Data.Cast<object>().Any())
{
<ul @ref="list" class="@(Multiple ? "rz-multiselect-items rz-multiselect-list" : "rz-dropdown-items rz-dropdown-list")" role="listbox">
<ul id="@ListId" @ref="list" class="@(Multiple ? "rz-multiselect-items rz-multiselect-list" : "rz-dropdown-items rz-dropdown-list")"
role="listbox" aria-multiselectable="@(Multiple ? "true" : "false")">
@if (View != null)
{
@RenderItems()
@@ -184,7 +198,8 @@
</div>
@if (AllowClear && (!Multiple && HasValue || Multiple && selectedItems.Count > 0))
{
<i class="notranslate rz-dropdown-clear-icon rzi rzi-times" @onclick="@ClearAll" @onclick:stopPropagation="true"></i>
<button type="button" class="notranslate rz-dropdown-clear-icon rzi rzi-times" aria-label="@ClearAriaLabel"
@onclick="@ClearAll" @onclick:stopPropagation="true"></button>
}
</div>
}
@@ -197,7 +212,7 @@
<div class="rz-dropdown-filter-container">
<input aria-label="@SearchAriaLabel" id="@SearchID" @ref="@search" tabindex="@(Disabled ? "-1" : $"{TabIndex}")" placeholder="@FilterPlaceholder" class="rz-dropdown-filter rz-inputtext" autocomplete="@FilterAutoCompleteType" aria-autocomplete="none" type="text"
@onchange="@OnFilter" @onkeydown="@OnFilterKeyPress"
@bind:event="oninput" @bind:get="searchText" @bind:set="@(args => { searchText = $"{args}"; SearchTextChanged.InvokeAsync(searchText);})" />
@bind:event="oninput" @bind:get="searchText" @bind:set="@(args => OnFilterInput(new ChangeEventArgs(){ Value = args }))" />
<span class="notranslate rz-dropdown-filter-icon rzi rzi-search"></span>
</div>
</text>
@@ -208,7 +223,7 @@
<div class="rz-dropdown-filter-container">
<input aria-label="@SearchAriaLabel" id="@SearchID" @ref="@search" tabindex="@(Disabled ? "-1" : $"{TabIndex}")" placeholder="@FilterPlaceholder" class="rz-dropdown-filter rz-inputtext" autocomplete="@FilterAutoCompleteType" aria-autocomplete="none" type="text"
@onchange="@OnFilter" @onkeydown="@OnFilterKeyPress" value="@searchText"
@oninput="@((ChangeEventArgs args) => { searchText = $"{args.Value}"; SearchTextChanged.InvokeAsync(searchText);})" />
@oninput="@OnFilterInput" />
<span class="notranslate rz-dropdown-filter-icon rzi rzi-search"></span>
</div>
</text>

View File

@@ -1,4 +1,4 @@
@using Radzen
@using Radzen
@using System.Collections
@using System.Collections.Generic
@using Microsoft.AspNetCore.Components.Forms
@@ -9,6 +9,7 @@
@if (Visible)
{
<div @ref="@Element" @attributes="Attributes" class="@GetCssClass()" @onfocus=@this.AsNonRenderingEventHandler(OnFocus)
role="combobox" aria-haspopup="listbox" aria-expanded="@isPopupOpen.ToString().ToLowerInvariant()" aria-controls="@PopupID" aria-disabled="@(Disabled.ToString().ToLowerInvariant())"
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">
@@ -58,7 +59,13 @@
@GetItemOrValueFromProperty(item!, TextProperty ?? string.Empty)
}
</span>
<button tabindex="0" title="@(RemoveChipTitle + " " + GetItemOrValueFromProperty(item!, TextProperty ?? string.Empty))" type="button" class="rz-button rz-button-sm rz-button-icon-only rz-base rz-shade-default @(Disabled ?"rz-state-disabled":"")" @onclick:preventDefault @onclick:stopPropagation @onclick="() => OnChipRemove(item)"><RadzenIcon Icon="close" /></button>
<button tabindex="0"
title="@(RemoveChipTitle + " " + GetItemOrValueFromProperty(item!, TextProperty ?? string.Empty))"
aria-label="@(RemoveChipTitle + " " + GetItemOrValueFromProperty(item!, TextProperty ?? string.Empty))"
type="button" class="rz-button rz-button-sm rz-button-icon-only rz-base rz-shade-default @(Disabled ?"rz-state-disabled":"")"
@onclick:preventDefault @onclick:stopPropagation @onclick="() => OnChipRemove(item)">
<RadzenIcon Icon="close" />
</button>
</div>
}
</div>
@@ -126,13 +133,15 @@
@RenderFilter()
@if (ShowSearch)
{
<button tabindex="0" class="rz-button rz-button-md rz-button-icon-only rz-primary" type="button" title="@SearchTextPlaceholder" @onclick="@((args) => OnFilter(new ChangeEventArgs()))">
<button tabindex="0" class="rz-button rz-button-md rz-button-icon-only rz-primary" type="button"
title="@SearchTextPlaceholder" aria-label="@SearchTextPlaceholder" @onclick="@((args) => OnFilter(new ChangeEventArgs()))">
<i class="notranslate rz-button-icon-left rzi">search</i>
</button>
}
@if (ShowAdd)
{
<button tabindex="0" class="rz-button rz-button-md rz-button-icon-only rz-primary" style="margin-left: 5px" type="button" title="@AddAriaLabel" @onclick="@OnAddClick">
<button tabindex="0" class="rz-button rz-button-md rz-button-icon-only rz-primary" style="margin-left: 5px" type="button"
title="@AddAriaLabel" aria-label="@AddAriaLabel" @onclick="@OnAddClick">
<i class="notranslate rz-button-icon-left rzi">add</i>
</button>
}
@@ -192,7 +201,8 @@
@if (AllowClear && (!Multiple && HasValue || Multiple && (selectedItems.Count > 0 || SelectedValue is IEnumerable)))
{
<i class="notranslate rz-dropdown-clear-icon rzi rzi-times" @onclick="@Clear" @onclick:stopPropagation="true"></i>
<button type="button" class="notranslate rz-dropdown-clear-icon rzi rzi-times" aria-label="@ClearAriaLabel"
@onclick="@Clear" @onclick:stopPropagation="true"></button>
}
</div>
}
@@ -204,7 +214,7 @@
<text>
<input aria-label="@SearchAriaLabel" class="rz-lookup-search-input" id="@SearchID" @ref="@search" tabindex="0" placeholder="@SearchTextPlaceholder"
@onchange="@OnFilter" @onkeydown="@OnFilterKeyPress" style="@(ShowSearch ? "" : "margin-right:0px;")"
@bind:event="oninput" @bind:get="searchText" @bind:set=@(args => { searchText = $"{args}"; SearchTextChanged.InvokeAsync(searchText);}) />
@bind:event="oninput" @bind:get="searchText" @bind:set=@(args => OnFilterInput(new ChangeEventArgs(){ Value = args }))" />
</text>
};
#else
@@ -212,7 +222,7 @@
<text>
<input aria-label="@SearchAriaLabel" class="rz-lookup-search-input" id="@SearchID" @ref="@search" tabindex="0" placeholder="@SearchTextPlaceholder"
@onchange="@OnFilter" @onkeydown="@OnFilterKeyPress" value="@searchText" style="@(ShowSearch ? "" : "margin-right:0px;")"
@oninput=@((ChangeEventArgs args) => { searchText = $"{args.Value}"; SearchTextChanged.InvokeAsync(searchText);}) />
@oninput="@OnFilterInput" />
</text>
};
#endif

View File

@@ -622,18 +622,12 @@ namespace Radzen.Blazor
foreach (string word in words)
{
if (TextProperty != null)
{
query = query.Where(TextProperty, word, FilterOperator, FilterCaseSensitivity);
}
query = query.Where(TextProperty!, word, FilterOperator, FilterCaseSensitivity);
}
}
else
{
if (TextProperty != null)
{
query = query.Where(TextProperty, searchText, FilterOperator, FilterCaseSensitivity);
}
query = query.Where(TextProperty!, searchText, FilterOperator, FilterCaseSensitivity);
}
}
}

View File

@@ -8,8 +8,10 @@
Disabled = itemArgs.Disabled;
@if (DropDown?.Multiple == true)
{
<li class="@GetComponentCssClass("rz-multiselect")"
<li class="@GetComponentCssClass("rz-multiselect")" role="option" tabindex="-1"
aria-label="@(DropDown?.GetItemOrValueFromProperty(Item!, DropDown?.TextProperty ?? string.Empty) ?? "")"
aria-selected="@(DropDown?.IsSelected(Item) == true)"
aria-disabled="@(Disabled || DropDown?.Disabled == true ? "true" : "false")"
@onmousedown:preventDefault @onmousedown="args=>SelectItem(args,false)"
@onclick:preventDefault @onclick="args=>SelectItem(args,true)"
@attributes="@(itemArgs?.Attributes.Any() == true ? itemArgs.Attributes : Attributes)">
@@ -32,10 +34,13 @@ Disabled = itemArgs.Disabled;
}
else
{
<li role="option" class="@GetComponentCssClass("rz-dropdown")" aria-label="@(DropDown?.GetItemOrValueFromProperty(Item, DropDown?.TextProperty ?? "") ?? "")"
@onmousedown:preventDefault @onmousedown="args=>SelectItem(args,false)"
@onclick:preventDefault @onclick="args=>SelectItem(args,true)"
@attributes="@(itemArgs?.Attributes.Any() == true ? itemArgs.Attributes : Attributes)">
<li role="option" class="@GetComponentCssClass("rz-dropdown")" tabindex="-1"
aria-label="@(DropDown?.GetItemOrValueFromProperty(Item, DropDown?.TextProperty ?? "") ?? "")"
aria-selected="@(DropDown?.IsSelected(Item) == true)"
aria-disabled="@(Disabled || DropDown?.Disabled == true ? "true" : "false")"
@onmousedown:preventDefault @onmousedown="args=>SelectItem(args,false)"
@onclick:preventDefault @onclick="args=>SelectItem(args,true)"
@attributes="@(itemArgs?.Attributes.Any() == true ? itemArgs.Attributes : Attributes)">
<span>
@if (DropDown?.Template != null)
{

View File

@@ -14,12 +14,15 @@
ToggleShade="@ToggleShade"
Disabled="@Disabled"
AriaLabel="@AriaLabel"
AriaExpanded="@isOpen.ToString().ToLowerInvariant()"
AriaControls="@($"{GetId()}-items")"
AriaHasPopup="menu"
class="@ButtonClass"
style="@ButtonStyleCss">
</RadzenToggleButton>
@if (isOpen)
{
<RadzenStack onclick=@CloseAsync Orientation=@StackOrientation AlignItems=@StackAlignment Gap=@Gap class=@GetStackClasses() style=@ComputedItemsStyle ChildContent="@ChildContent" />
<RadzenStack onclick=@CloseAsync Orientation=@StackOrientation AlignItems=@StackAlignment Gap=@Gap class=@GetStackClasses() style=@ComputedItemsStyle ChildContent="@ChildContent" id=@($"{GetId()}-items") role="menu" />
}
</div>

View File

@@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.JSInterop;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Radzen.Blazor;

View File

@@ -9,9 +9,9 @@
<legend class="rz-fieldset-legend" style="white-space:nowrap">
@if (AllowCollapse)
{
<a id="@(GetId() + "expc")" title="@TitleAttribute()" aria-label="@AriaLabelAttribute()" @onclick:preventDefault="true"
aria-controls="rz-fieldset-0-content" aria-expanded="@(collapsed ? "false" : "true")" @onclick=@Toggle
tabindex="0" @onkeypress="@(args => OnKeyPress(args, Toggle(new EventArgs())))" @onkeypress:preventDefault=preventKeyPress @onkeypress:stopPropagation>
<button type="button" id="@(GetId() + "expc")" title="@TitleAttribute()" aria-label="@AriaLabelAttribute()"
aria-controls="@($"{GetId()}-content")" aria-expanded="@(collapsed ? "false" : "true")" @onclick=@Toggle
@onkeypress="@(args => OnKeyPress(args, Toggle(new EventArgs())))" @onkeypress:preventDefault=preventKeyPress @onkeypress:stopPropagation>
@if (collapsed)
{
<span class="notranslate rz-fieldset-toggler rzi rzi-w rzi-plus"></span>
@@ -30,7 +30,7 @@
<span class="rz-fieldset-legend-text">@Text</span>
}
@HeaderTemplate
</a>
</button>
}
else
{
@@ -46,7 +46,7 @@
}
</legend>
}
<Expander Expanded=@(!collapsed) CssClass="rz-fieldset-content-wrapper" id="rz-fieldset-0-content">
<Expander Expanded=@(!collapsed) CssClass="rz-fieldset-content-wrapper" id="@($"{GetId()}-content")">
<div class="rz-fieldset-content">
@ChildContent
</div>

View File

@@ -9,7 +9,10 @@
{
<div @ref="@Element" style="@Style" @attributes="Attributes" class="@GetCssClass()" id="@GetId()">
<div class="rz-fileupload-buttonbar">
<span class=@ChooseClass tabindex="@(Disabled ? "-1" : $"{TabIndex}")" onkeydown="if(event.keyCode == 32 || event.keyCode == 13){event.preventDefault();this.firstElementChild.click();}">
<span class=@ChooseClass tabindex="@(Disabled ? "-1" : $"{TabIndex}")" role="button"
aria-disabled="@(Disabled ? "true" : "false")" aria-label="@ChooseText"
aria-controls="@(Name ?? GetId())"
onkeydown="if(event.keyCode == 32 || event.keyCode == 13){event.preventDefault();this.firstElementChild.click();}">
<input id="@(Name ?? GetId())" @attributes="InputAttributes" tabindex="-1" disabled="@Disabled" @ref="@fileUpload" name="@Name" type="file" accept="@Accept" onkeydown="event.stopPropagation()"
onchange="Radzen.uploadInputChange(event, null, false, false, true)" />
<span class="rz-button-text">@ChooseText</span>
@@ -23,7 +26,9 @@
<div>
@if (IsImage)
{
<img style="@ImageStyle" src="@ImageValue" @onclick="@OnImageClick" alt="@ImageAlternateText" />
<img style="@ImageStyle" src="@ImageValue" @onclick="@OnImageClick" alt="@ImageAlternateText"
role="@(ImageClick.HasDelegate ? "button" : null)" tabindex="@(ImageClick.HasDelegate ? "0" : null)"
@onkeydown="OnImageKeyDown" />
}
</div>
<div>

View File

@@ -247,6 +247,20 @@ namespace Radzen.Blazor
}
}
async Task OnImageKeyDown(KeyboardEventArgs args)
{
if (!ImageClick.HasDelegate)
{
return;
}
var key = args.Code != null ? args.Code : args.Key;
if (key == "Enter" || key == "Space")
{
await OnImageClick(new MouseEventArgs());
}
}
async System.Threading.Tasks.Task Remove(EventArgs args)
{
Value = default(TValue)!;

View File

@@ -1,6 +1,21 @@
@inherits RadzenComponentWithChildren
@using System.Collections.Generic
@inherits RadzenComponentWithChildren
@if (Visible)
{
<img @ref="@Element" src="@Path" style="@Style" @onclick="@((args) => OnClick(args))" @attributes="Attributes" class="@GetCssClass()" id="@GetId()" alt="@GetAlternateText()" />
var imgAttributes = new Dictionary<string, object>();
if (Attributes != null)
{
foreach (var attribute in Attributes)
{
imgAttributes[attribute.Key] = attribute.Value;
}
}
if (Click.HasDelegate)
{
imgAttributes["role"] = "button";
imgAttributes["tabindex"] = "0";
}
<img @ref="@Element" src="@Path" style="@Style" @onclick="@((args) => OnClick(args))" @onkeydown="OnKeyDown" @attributes="imgAttributes" class="@GetCssClass()" id="@GetId()" alt="@GetAlternateText()" />
}

View File

@@ -64,6 +64,26 @@ namespace Radzen.Blazor
await Click.InvokeAsync(args);
}
/// <summary>
/// Handles keyboard activation for clickable images.
/// </summary>
/// <param name="args">The <see cref="KeyboardEventArgs"/> instance containing the event data.</param>
protected async System.Threading.Tasks.Task OnKeyDown(KeyboardEventArgs args)
{
ArgumentNullException.ThrowIfNull(args);
if (!Click.HasDelegate)
{
return;
}
var key = args.Code != null ? args.Code : args.Key;
if (key == "Enter" || key == "Space")
{
await OnClick(new MouseEventArgs());
}
}
string GetAlternateText()
{
if (Attributes != null && Attributes.TryGetValue("alt", out var @alt) && !string.IsNullOrEmpty(Convert.ToString(@alt, CultureInfo.InvariantCulture)))

View File

@@ -1,4 +1,4 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.DependencyInjection;
using Radzen.Blazor.Rendering;
using System;
@@ -32,7 +32,7 @@ namespace Radzen.Blazor
/// &lt;/RadzenLayout&gt;
/// </code>
/// </example>
public partial class RadzenLayout : RadzenComponentWithChildren
public partial class RadzenLayout : RadzenComponentWithChildren, IDisposable
{
[Inject]
private IServiceProvider? ServiceProvider { get; set; }
@@ -57,6 +57,18 @@ namespace Radzen.Blazor
StateHasChanged();
}
/// <inheritdoc />
public override void Dispose()
{
if (themeService != null)
{
themeService.ThemeChanged -= OnThemeChanged;
}
base.Dispose();
GC.SuppressFinalize(this);
}
/// <inheritdoc />
protected override string GetComponentCssClass() => ClassList.Create("rz-layout")
.Add($"rz-{themeService?.Theme}", themeService != null)

View File

@@ -1,4 +1,4 @@
@using Radzen
@using Radzen
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.JSInterop
@using Microsoft.AspNetCore.Components.Rendering
@@ -23,7 +23,10 @@
{
@if (AllowSelectAll)
{
<div class="rz-chkbox " @onclick="@SelectAll">
<div class="rz-chkbox" @onclick="@SelectAll" role="checkbox" tabindex="@(Disabled || ReadOnly ? "-1" : "0")"
aria-checked="@(IsAllSelected().ToString().ToLowerInvariant())"
aria-disabled="@(Disabled || ReadOnly ? "true" : "false")"
@onkeydown="OnSelectAllKeyDown">
<div class="rz-helper-hidden-accessible">
<input id="@($"{UniqueID}sa")" readonly="readonly" type="checkbox" aria-label="@SelectAllText" aria-checked="@(IsAllSelected().ToString().ToLowerInvariant())">
</div>
@@ -50,7 +53,7 @@
@if (View != null)
{
<div class="rz-listbox-list-wrapper">
<ul @ref="list" class="rz-listbox-list">
<ul @ref="list" class="rz-listbox-list" role="listbox" aria-multiselectable="@(Multiple ? "true" : "false")">
@RenderItems()
</ul>
</div>
@@ -65,7 +68,7 @@
<text>
<input id="@SearchID" @ref="@search" disabled="@Disabled" class="rz-inputtext" role="textbox" type="text" placeholder="@Placeholder"
@onchange="@OnFilter" @onkeydown="@OnFilterKeyPress" @onkeydown:stopPropagation="true"
@bind:event="oninput" @bind:get="searchText" @bind:set=@(args => { searchText = $"{args}"; SearchTextChanged.InvokeAsync(searchText);}) aria-label="@SearchAriaLabel" />
@bind:event="oninput" @bind:get="searchText" @bind:set=@(args => OnFilterInput(new ChangeEventArgs(){ Value = args }))" aria-label="@SearchAriaLabel" />
</text>
};
#else
@@ -73,7 +76,7 @@
<text>
<input id="@SearchID" @ref="@search" disabled="@Disabled" class="rz-inputtext" role="textbox" type="text" placeholder="@Placeholder"
@onchange="@OnFilter" @onkeydown="@OnFilterKeyPress" value="@searchText" @onkeydown:stopPropagation="true"
@oninput=@((ChangeEventArgs args) => { searchText = $"{args.Value}"; SearchTextChanged.InvokeAsync(searchText);}) aria-label="@SearchAriaLabel" />
@oninput="@OnFilterInput" aria-label="@SearchAriaLabel" />
</text>
};
#endif

View File

@@ -109,6 +109,7 @@ namespace Radzen.Blazor
await OnKeyPress(args, false);
}
private bool visibleChanged;
private bool disabledChanged;
private bool firstRender = true;

View File

@@ -6,7 +6,9 @@
@if (itemArgs?.Visible == true)
{
Disabled = itemArgs.Disabled;
<li class="@GetComponentCssClass()" aria-label="@PropertyAccess.GetItemOrValueFromProperty(Item, ListBox?.TextProperty)" @onclick="@SelectItem"
<li class="@GetComponentCssClass()" role="option" aria-selected="@(ListBox?.IsSelected(Item) == true)" tabindex="-1"
aria-label="@PropertyAccess.GetItemOrValueFromProperty(Item, ListBox?.TextProperty)" @onclick="@SelectItem"
aria-disabled="@(Disabled || ListBox?.Disabled == true ? "true" : "false")"
@attributes="@(itemArgs?.Attributes.Any() == true ? itemArgs.Attributes : Attributes)">
@if (ListBox?.Multiple == true)
{

View File

@@ -37,7 +37,7 @@
}
@if (AllowResetPassword)
{
<a tabindex="0" class="rz-link" @onclick=@OnReset>@ResetPasswordText</a>
<button type="button" class="rz-link" @onclick=@OnReset>@ResetPasswordText</button>
}
</RadzenStack>
@if (AllowRegister)
@@ -84,7 +84,7 @@
}
@if (AllowResetPassword)
{
<a tabindex="0" class="rz-link" @onclick=@OnReset>@ResetPasswordText</a>
<button type="button" class="rz-link" @onclick=@OnReset>@ResetPasswordText</button>
}
</div>
</div>

View File

@@ -2,13 +2,14 @@
@if (Visible)
{
<ul @ref=@Element style=@Style @attributes=@Attributes class=@GetCssClass() id=@GetId()
<ul @ref=@Element style=@Style @attributes=@Attributes class=@GetCssClass() id=@GetId() role="menubar" aria-label="@AriaLabel"
tabindex="0" @onkeydown="@OnKeyPress" @onkeydown:preventDefault=preventKeyPress @onkeydown:stopPropagation
@onfocus=@this.AsNonRenderingEventHandler(OnFocus)>
@if (Responsive)
{
<li class="rz-menu-toggle-item">
<button aria-label=@ToggleAriaLabel class="rz-menu-toggle" @onclick=@OnToggle>
<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)
{
<i class="notranslate rzi">close</i>

View File

@@ -78,6 +78,13 @@ namespace Radzen.Blazor
[Parameter]
public EventCallback<MenuItemEventArgs> Click { get; set; }
/// <summary>
/// Gets or sets the menu aria label text.
/// </summary>
/// <value>The menu aria label text.</value>
[Parameter]
public string AriaLabel { get; set; } = "Menu";
[Inject]
NavigationManager? NavigationManager { get; set; }

View File

@@ -57,7 +57,7 @@
</div>
@if (ChildContent != null)
{
<ul class="rz-navigation-menu" style="display: none">
<ul id="@($"{GetId()}-submenu")" class="rz-navigation-menu" role="menu" style="display: none">
<CascadingValue Value=this>
@ChildContent
</CascadingValue>

View File

@@ -111,6 +111,8 @@ namespace Radzen.Blazor
[Parameter]
public RenderFragment? ChildContent { get; set; }
internal string? SubmenuId => ChildContent != null ? $"{GetId()}-submenu" : null;
/// <summary>
/// Gets or sets the click callback.
/// </summary>

View File

@@ -1,10 +1,16 @@
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.JSInterop
@inherits RadzenComponentWithChildren
@if (Item?.Parent?.ClickToOpen == true)
{
<li style=@Style @attributes="@Attributes" @onclick="@Item.OnClick" @onclick:stopPropagation>
<li style=@Style @attributes="@Attributes" @onclick="@Item.OnClick" @onclick:stopPropagation role="menuitem"
tabindex="@(Item?.Disabled == true ? -1 : 0)" aria-disabled="@(Item?.Disabled == true ? "true" : "false")"
aria-haspopup="@(Item?.ChildContent != null ? "menu" : null)"
aria-controls="@Item?.SubmenuId"
aria-expanded="@(Item?.ChildContent != null ? "false" : null)"
@onkeydown="OnKeyDown">
@ChildContent
</li>
}
@@ -12,13 +18,20 @@ else
{
if (Item?.ChildContent != null)
{
<li style=@Style @attributes="@Attributes" onmouseenter="Radzen.toggleMenuItem(this, event, true, false)" onmouseleave="Radzen.toggleMenuItem(this, event, false, false)">
<li style=@Style @attributes="@Attributes" onmouseenter="Radzen.toggleMenuItem(this, event, true, false)" onmouseleave="Radzen.toggleMenuItem(this, event, false, false)"
role="menuitem" tabindex="@(Item?.Disabled == true ? -1 : 0)" aria-disabled="@(Item?.Disabled == true ? "true" : "false")"
aria-haspopup="menu" aria-controls="@Item?.SubmenuId" aria-expanded="false" @onkeydown="OnKeyDown">
@ChildContent
</li>
}
else
{
<li style=@Style @attributes="@Attributes" @onclick="@(Item!.OnClick)" @onclick:stopPropagation>
<li style=@Style @attributes="@Attributes" @onclick="@(Item!.OnClick)" @onclick:stopPropagation role="menuitem"
tabindex="@(Item?.Disabled == true ? -1 : 0)" aria-disabled="@(Item?.Disabled == true ? "true" : "false")"
aria-haspopup="@(Item?.ChildContent != null ? "menu" : null)"
aria-controls="@Item?.SubmenuId"
aria-expanded="@(Item?.ChildContent != null ? "false" : null)"
@onkeydown="OnKeyDown">
@ChildContent
</li>
}
@@ -32,4 +45,25 @@ else
/// <value>The menu item.</value>
[Parameter]
public RadzenMenuItem? Item { get; set; }
async Task OnKeyDown(KeyboardEventArgs args)
{
if (Item == null || Item.Disabled)
{
return;
}
var key = args.Code != null ? args.Code : args.Key;
if (key == "Enter" || key == "Space")
{
if (Item.ChildContent != null)
{
await Item.Toggle();
}
else
{
await Item.OnClick(new MouseEventArgs());
}
}
}
}

View File

@@ -1,20 +1,28 @@
@using System.Collections.Generic
@using System.Timers
@using System.Threading
@implements IDisposable
@if (Visible)
{
var classes = GetMessageCssClasses();
<div aria-live="polite" class="rz-notification-item-wrapper rz-open @classes.Item1" style="@(Message?.Click != null || Message?.CloseOnClick == true ? "cursor: pointer;" : "") @Style">
var clickable = Message?.Click != null || Message?.CloseOnClick == true;
var wrapperAttributes = clickable ? new Dictionary<string, object>
{
{ "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 class="rz-notification-item">
<div class="rz-notification-message-wrapper">
<span class="notranslate rzi rz-notification-icon @classes.Item2" @onclick="NotificationClicked"></span>
<div class="rz-notification-message" @onclick="NotificationClicked">
<span class="notranslate rzi rz-notification-icon @classes.Item2"></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>
</div>
</div>
<div class="notranslate rzi rz-notification-close" @onclick="@Close"></div>
<button type="button" class="notranslate rzi rz-notification-close" aria-label="@(Message?.CloseAriaLabel ?? "Close")" @onclick="@Close" @onclick:stopPropagation="true"></button>
</div>
<RadzenProgressBar ShowValue="false" Visible=@(Message?.Duration != null && Message.ShowProgress)
Value=@progress Max="@(Message?.Duration ?? 0)" ProgressBarStyle=@GetProgressBarStyle() />
@@ -85,33 +93,87 @@
}
double progress = 0;
Timer? timer;
System.Timers.Timer? timer;
ElapsedEventHandler? timerElapsedHandler;
CancellationTokenSource? closeCts;
protected override void OnInitialized()
{
if (Message?.ShowProgress == true)
{
timer = new Timer() { Enabled = true };
timer.Elapsed += (sender, args) =>
{
progress = progress + 100;
InvokeAsync(StateHasChanged);
};
timer = new System.Timers.Timer() { Enabled = true };
timerElapsedHandler = OnTimerElapsed;
timer.Elapsed += timerElapsedHandler;
}
closeCts = new CancellationTokenSource();
_ = CloseAfterDelayAsync(Convert.ToInt32(Message?.Duration ?? 3000), closeCts.Token);
}
private void OnTimerElapsed(object? sender, ElapsedEventArgs args)
{
progress = progress + 100;
_ = InvokeAsync(StateHasChanged);
}
private async Task CloseAfterDelayAsync(int duration, CancellationToken token)
{
try
{
await Task.Delay(duration, token);
if (!token.IsCancellationRequested)
{
await InvokeAsync(Close);
}
}
catch (TaskCanceledException)
{
// Ignore
}
System.Threading.Tasks.Task.Delay(Convert.ToInt32(Message?.Duration ?? 3000)).ContinueWith(r => InvokeAsync(Close));
}
private void NotificationClicked()
{
if (Message?.Click == null && Message?.CloseOnClick != true)
return;
if (Message?.CloseOnClick == true)
Close();
Message?.Click?.Invoke(Message);
}
private void OnKeyDown(KeyboardEventArgs args)
{
if (Message?.Click == null && Message?.CloseOnClick != true)
{
return;
}
var key = args.Code != null ? args.Code : args.Key;
if (key == "Enter" || key == "Space")
{
NotificationClicked();
}
}
public void Dispose()
{
if (closeCts != null)
{
closeCts.Cancel();
closeCts.Dispose();
closeCts = null;
}
if (timer != null && timerElapsedHandler != null)
{
timer.Elapsed -= timerElapsedHandler;
timerElapsedHandler = null;
}
timer?.Stop();
timer?.Dispose();
timer = null;
}
}

View File

@@ -1,9 +1,10 @@
@inherits RadzenComponent
@if (GetVisible())
{
<div @ref="@Element" @attributes="Attributes" class="@GetCssClass()" style="@Style" id="@GetId()"
@onkeydown="@OnKeyDown" @onkeydown:preventDefault="preventKeyDown" @onkeydown:stopPropagation="true" tabindex=0
@onfocus=@this.AsNonRenderingEventHandler(OnFocus)>
<nav @ref="@Element" @attributes="Attributes" class="@GetCssClass()" style="@Style" id="@GetId()"
aria-label="@NavigationAriaLabel"
@onkeydown="@OnKeyDown" @onkeydown:preventDefault="preventKeyDown" @onkeydown:stopPropagation="true" tabindex=0
@onfocus=@this.AsNonRenderingEventHandler(OnFocus)>
@if(ShowPagingSummary)
{
<span class="rz-pager-summary">
@@ -17,41 +18,53 @@
}
</span>
}
<a id="@(GetId() + "fp")" class="rz-pager-first rz-pager-element @(skip > 0 ? "": "rz-state-disabled") @(focusedIndex == -2 ? "rz-state-focused": "")" @onclick:preventDefault="true" @onclick="@(() => OnFirstPageClick())" aria-label="@FirstPageAriaLabel" role="button" title="@FirstPageTitle" disabled="@(CurrentPage <= 0)">
<span class="notranslate rz-pager-icon rzi rzi-step-backward"></span>
</a>
<a id="@(GetId() + "pp")" class="rz-pager-prev rz-pager-element @(skip > 0 ? "": "rz-state-disabled") @(focusedIndex == -1 ? "rz-state-focused": "")" @onclick:preventDefault="true" @onclick="@(() => OnPrevPageClick())" aria-label="@PrevPageAriaLabel" role="button" title="@PrevPageTitle" disabled="@(CurrentPage <= 0)">
<span class="notranslate rz-pager-icon rzi rzi-caret-left"></span>
@if (PrevPageLabel != null)
{
<span class="rz-pager-label">@PrevPageLabel</span>
}
</a>
<span class="rz-pager-pages">
<ul class="rz-pager-pages" role="list">
<li class="rz-pager-item">
<button id="@(GetId() + "fp")" type="button" 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" 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)
{
<span class="rz-pager-label">@PrevPageLabel</span>
}
</button>
</li>
@foreach (var i in Enumerable.Range(startPage, Math.Min(endPage + 1, PageNumbersCount)))
{
<a id="@(GetId() + i.ToString() + "p")" class="rz-pager-page rz-pager-element @(i == CurrentPage ? "rz-state-active" : "") @(startPage + focusedIndex == i ? "rz-state-focused": "")" @onclick:preventDefault="true" @onclick="@(() => OnPageClick(i, startPage))" aria-current="@(i == CurrentPage ? "page" : null)" aria-label="@string.Format(PageAriaLabelFormat, (i + 1).ToString())" role="button" title="@string.Format(PageTitleFormat, (i + 1).ToString())">@(i + 1)</a>
<li class="rz-pager-item">
<button id="@(GetId() + i.ToString() + "p")" type="button" 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>
}
</span>
<a id="@(GetId() + "np")" class="rz-pager-next rz-pager-element @((CurrentPage != numberOfPages - 1) ? "": "rz-state-disabled") @(focusedIndex == Math.Min(endPage + 1, PageNumbersCount)? "rz-state-focused": "")" @onclick:preventDefault="true" @onclick="@(() => OnNextPageClick(endPage))" aria-label="@NextPageAriaLabel" role="button" title="@NextPageTitle" disabled="@(CurrentPage >= (numberOfPages - 1))">
@if (NextPageLabel != null)
{
<span class="rz-pager-label">@NextPageLabel</span>
}
<span class="notranslate rz-pager-icon rzi rzi-caret-right"></span>
</a>
<a id="@(GetId() + "lp")" class="rz-pager-last rz-pager-element @((CurrentPage != numberOfPages - 1) ? "": "rz-state-disabled") @(focusedIndex == Math.Min(endPage + 1, PageNumbersCount) + 1 ? "rz-state-focused": "")" @onclick:preventDefault="true" @onclick="@(() => OnLastPageClick(endPage))" aria-label="@LastPageAriaLabel" role="button" title="@LastPageTitle" disabled="@(CurrentPage >= (numberOfPages - 1))">
<span class="notranslate rz-pager-icon rzi rzi-step-forward"></span>
</a>
<li class="rz-pager-item">
<button id="@(GetId() + "np")" type="button" 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)
{
<span class="rz-pager-label">@NextPageLabel</span>
}
<span class="notranslate rz-pager-icon rzi rzi-caret-right"></span>
</button>
</li>
<li class="rz-pager-item">
<button id="@(GetId() + "lp")" type="button" 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>
</li>
</ul>
@if(PageSizeOptions != null && PageSizeOptions.Any())
{
<RadzenDropDown TValue="int" Data="@PageSizeOptions" Value="@PageSize" Change="@OnPageSizeChanged" />
<span class="rz-pagesize-text">@PageSizeText</span>
}
</div>
</nav>
}

View File

@@ -165,6 +165,12 @@ namespace Radzen.Blazor
[Parameter]
public bool ShowPagingSummary { get; set; }
/// <summary>
/// Gets or sets the navigation aria-label.
/// </summary>
[Parameter]
public string NavigationAriaLabel { get; set; } = "Pagination";
/// <summary>
/// Gets or sets the pager summary format. <see cref="PagingSummaryTemplate" /> has preference over this property.
/// </summary>
@@ -349,6 +355,7 @@ namespace Radzen.Blazor
skip = page * PageSize;
await InvokeAsync(Reload);
await PageChanged.InvokeAsync(new PagerEventArgs() { Skip = skip, Top = PageSize, PageIndex = CurrentPage });
StateHasChanged();
}
}

View File

@@ -20,15 +20,15 @@
@HeaderTemplate
@if (AllowCollapse)
{
<a id="@(GetId() + "expc")" @onclick=@Toggle class="rz-panel-titlebar-icon rz-panel-titlebar-toggler"
@onclick:preventDefault="true" role="button" aria-controls="rz-panel-0-content"
aria-expanded="@(collapsed ? "false" : "true")" aria-label="@(collapsed ? ExpandAriaLabel : CollapseAriaLabel)" title="@(collapsed ? ExpandTitle : CollapseTitle)"
tabindex="0" @onkeypress="@(args => OnKeyPress(args, Toggle(new MouseEventArgs())))" @onkeypress:preventDefault=preventKeyPress @onkeypress:stopPropagation>
<button id="@(GetId() + "expc")" type="button" @onclick=@Toggle class="rz-panel-titlebar-icon rz-panel-titlebar-toggler"
aria-controls="@($"{GetId()}-content")"
aria-expanded="@(collapsed ? "false" : "true")" aria-label="@(collapsed ? ExpandAriaLabel : CollapseAriaLabel)" title="@(collapsed ? ExpandTitle : CollapseTitle)"
@onkeypress="@(args => OnKeyPress(args, Toggle(new MouseEventArgs())))" @onkeypress:preventDefault=preventKeyPress @onkeypress:stopPropagation>
<span class="notranslate rzi @(collapsed ? "rzi-plus" : "rzi-minus")"></span>
</a>
</button>
}
</div>
<Expander Expanded=@(!collapsed) CssClass="rz-panel-content-wrapper">
<Expander Expanded=@(!collapsed) CssClass="rz-panel-content-wrapper" id="@($"{GetId()}-content")">
<div class="rz-panel-content">
@ChildContent
</div>

View File

@@ -2,7 +2,8 @@
@if (Visible)
{
<ul @ref=@Element style=@Style @attributes=@Attributes class=@GetCssClass() id=@GetId() tabindex="0"
<ul @ref=@Element style=@Style @attributes=@Attributes class=@GetCssClass() id=@GetId()
role="menu" aria-label="@AriaLabel" tabindex="0"
@onkeydown=@OnKeyPress
@onkeydown:preventDefault=preventKeyPress @onkeydown:stopPropagation
@onfocus=@this.AsNonRenderingEventHandler(OnFocus)>

View File

@@ -38,6 +38,13 @@ namespace Radzen.Blazor
[Parameter]
public EventCallback<MenuItemEventArgs> Click { get; set; }
/// <summary>
/// Gets or sets the menu aria label text.
/// </summary>
/// <value>The menu aria label text.</value>
[Parameter]
public string AriaLabel { get; set; } = "Menu";
/// <summary>
/// Gets or sets a value representing the URL matching behavior.
/// </summary>

View File

@@ -3,7 +3,12 @@
@inherits RadzenComponent
@if (Visible)
{
<li @ref=@Element style=@Style @attributes=@Attributes class=@GetCssClass() id=@GetId() @onclick=@(this.AsNonRenderingEventHandler<MouseEventArgs>(OnClick)) @onclick:stopPropagation>
<li @ref=@Element style=@Style @attributes=@Attributes class=@GetCssClass() id=@GetId()
role="menuitem" tabindex="@(Disabled ? -1 : 0)" aria-disabled="@(Disabled ? "true" : "false")"
aria-haspopup="@(ChildContent != null ? "menu" : null)"
aria-controls="@(ChildContent != null ? $"{GetId()}-submenu" : null)"
aria-expanded="@(ChildContent != null ? expanded.ToString().ToLowerInvariant() : null)"
@onclick=@(this.AsNonRenderingEventHandler<MouseEventArgs>(OnClick)) @onclick:stopPropagation @onkeydown="OnKeyDown">
<div class=@WrapperClass>
@if (Path != null)
{
@@ -22,17 +27,21 @@
}
else if (Parent?.DisplayStyle == MenuItemDisplayStyle.Text || Parent?.DisplayStyle == MenuItemDisplayStyle.IconAndText)
{
<span class="rz-navigation-item-text" @onclick="@Toggle">@Text</span>
<span class="rz-navigation-item-text">@Text</span>
}
@if (ChildContent != null && Parent?.ShowArrow == true)
{
<i class=@ToggleClass @onclick="@Toggle" @onclick:preventDefault>keyboard_arrow_down</i>
<i class=@ToggleClass>keyboard_arrow_down</i>
}
</NavLink>
}
else
{
<div class="rz-navigation-item-link" style="@(Parent?.DisplayStyle == MenuItemDisplayStyle.Icon?"margin-inline-end:0px;":"")" @onclick="@Toggle">
<div class="rz-navigation-item-link" style="@(Parent?.DisplayStyle == MenuItemDisplayStyle.Icon?"margin-inline-end:0px;":"")"
role="button" tabindex="0" aria-expanded="@expanded.ToString().ToLowerInvariant()"
aria-controls="@(ChildContent != null ? $"{GetId()}-submenu" : null)"
aria-haspopup="@(ChildContent != null ? "menu" : null)"
@onclick="@Toggle" @onkeydown="OnToggleKeyDown">
@if (!string.IsNullOrEmpty(Icon) && (Parent?.DisplayStyle == MenuItemDisplayStyle.Icon || Parent?.DisplayStyle == MenuItemDisplayStyle.IconAndText))
{
<i class="notranslate rzi rz-navigation-item-icon" style="@getIconStyle()">@Icon</i>
@@ -59,7 +68,7 @@
@if (ChildContent != null)
{
<Expander Expanded=@expanded>
<ul class="rz-navigation-menu">
<ul id="@($"{GetId()}-submenu")" class="rz-navigation-menu" role="menu">
<CascadingValue Value=this>
@ChildContent
</CascadingValue>

View File

@@ -424,6 +424,34 @@ namespace Radzen.Blazor
}
}
async Task OnKeyDown(KeyboardEventArgs args)
{
if (Disabled)
{
return;
}
var key = args.Code != null ? args.Code : args.Key;
if (key == "Enter" || key == "Space")
{
await OnClick(new MouseEventArgs());
}
}
async Task OnToggleKeyDown(KeyboardEventArgs args)
{
if (Disabled)
{
return;
}
var key = args.Code != null ? args.Code : args.Key;
if (key == "Enter" || key == "Space")
{
await Toggle();
}
}
/// <inheritdoc />
public override void Dispose()
{

View File

@@ -98,7 +98,7 @@
class="rz-pivot-header-content @(isSortable ? "rz-sortable" : "")"
tabindex="@(isSortable ? "0" : null)"
role="@(isSortable ? "button" : null)"
aria-label="@(isSortable ? $"Sort by {row?.GetTitle()}" : null)">
aria-label="@(isSortable ? string.Format(SortAriaLabelFormat, row?.GetTitle()) : null)">
<span class="rz-pivot-header-text">@row?.GetTitle()</span>
@if (isSortable)
{
@@ -118,12 +118,14 @@
</div>
@if (isFilterable)
{
<i @ref=FilterIconRef[filterIconRefKey] @onclick:stopPropagation="true"
@onmousedown="@(() => { if (row != null) _ = ToggleFilter(row, filterIconRefKey); })"
class="@(row != null ? GetFilterIconCss(row) : "")"
title="Filter">
<button type="button" @ref=FilterIconRef[filterIconRefKey] @onclick:stopPropagation="true"
@onmousedown="@(() => { if (row != null) _ = ToggleFilter(row, filterIconRefKey); })"
@onclick="@(() => { if (row != null) _ = ToggleFilter(row, filterIconRefKey); })"
class="@(row != null ? GetFilterIconCss(row) : "")"
aria-label="@FilterText" aria-haspopup="dialog"
aria-controls="@($"pivot-filter-{row?.Property}")" aria-expanded="false">
@FilterIcon
</i>
</button>
}
</div>
</th>
@@ -151,7 +153,7 @@
class="rz-pivot-header-content @(isSortable ? "rz-sortable" : "")"
tabindex="@(isSortable ? "0" : null)"
role="@(isSortable ? "button" : null)"
aria-label="@(isSortable ? $"Sort by {column?.GetTitle()}" : null)">
aria-label="@(isSortable ? string.Format(SortAriaLabelFormat, column?.GetTitle()) : null)">
<span class="rz-pivot-header-text">@column?.GetTitle()</span>
@if (isSortable)
{
@@ -171,12 +173,14 @@
</div>
@if (isFilterable)
{
<i @ref=FilterIconRef[filterIconRefKey] @onclick:stopPropagation="true"
@onmousedown="@(() => { if (column != null) _ = ToggleFilter(column, filterIconRefKey); })"
class="@(column != null ? GetFilterIconCss(column) : "")"
title="Filter">
<button type="button" @ref=FilterIconRef[filterIconRefKey] @onclick:stopPropagation="true"
@onmousedown="@(() => { if (column != null) _ = ToggleFilter(column, filterIconRefKey); })"
@onclick="@(() => { if (column != null) _ = ToggleFilter(column, filterIconRefKey); })"
class="@(column != null ? GetFilterIconCss(column) : "")"
aria-label="@FilterText" aria-haspopup="dialog"
aria-controls="@($"pivot-filter-{column?.Property}")" aria-expanded="false">
@FilterIcon
</i>
</button>
}
</div>
</th>
@@ -204,7 +208,7 @@
class="rz-pivot-header-content @(isSortable ? "rz-sortable" : "")"
tabindex="@(isSortable ? "0" : null)"
role="@(isSortable ? "button" : null)"
aria-label="@(isSortable ? $"Sort by {column?.GetTitle()}" : null)">
aria-label="@(isSortable ? string.Format(SortAriaLabelFormat, column?.GetTitle()) : null)">
<span class="rz-pivot-header-text">@column?.GetTitle()</span>
@if (isSortable)
{
@@ -224,12 +228,14 @@
</div>
@if (isFilterable)
{
<i @ref=FilterIconRef[filterIconRefKey] @onclick:stopPropagation="true"
@onmousedown="@(() => { if (column != null) _ = ToggleFilter(column, filterIconRefKey); })"
class="@(column != null ? GetFilterIconCss(column) : "")"
title="Filter">
<button type="button" @ref=FilterIconRef[filterIconRefKey] @onclick:stopPropagation="true"
@onmousedown="@(() => { if (column != null) _ = ToggleFilter(column, filterIconRefKey); })"
@onclick="@(() => { if (column != null) _ = ToggleFilter(column, filterIconRefKey); })"
class="@(column != null ? GetFilterIconCss(column) : "")"
aria-label="@FilterText" aria-haspopup="dialog"
aria-controls="@($"pivot-filter-{column?.Property}")" aria-expanded="false">
@FilterIcon
</i>
</button>
}
</div>
</th>
@@ -295,6 +301,8 @@
{
<div class="rz-pivot-drill-down-header">
<span @onclick="@(() => ToggleColumnDrillDown(cell.PathKey))"
@onkeydown="@(args => OnDrillDownKeyDown(args, cell.PathKey))"
role="button" tabindex="0" aria-expanded="@(!cell.IsCollapsed).ToString().ToLowerInvariant()"
class="notranslate rz-tree-toggler rzi rzi-caret-@(cell.IsCollapsed ? "right" : "down")" style="margin-inline-start:0"></span>
<span class="rz-pivot-header-text">
@if (cell.HeaderTemplate != null && cell.Group != null)
@@ -431,6 +439,7 @@
var prefix = currentFilterField is RadzenPivotColumn<TItem> ? "col" : currentFilterField is RadzenPivotRow<TItem> ? "row" : "agg";
var filterIconRefKey = $"{prefix}{currentFilterField.GetFilterProperty()}";
<Popup @ref="filterPopup" id="@($"pivot-filter-{currentFilterField.Property}")" class="rz-overlaypanel"
role="dialog" aria-label="@FilterText"
style="display:none;min-width:250px;" @onkeydown=@(args => OnFilterPopupKeyPressed(args, filterIconRefKey))>
<div class="rz-overlaypanel-content">
@if (currentFilterField.FilterTemplate != null)

View File

@@ -190,6 +190,13 @@ namespace Radzen.Blazor
[Parameter]
public string AggregatesText { get; set; } = "Aggregates";
/// <summary>
/// Gets or sets the sort aria label format.
/// </summary>
/// <value>The sort aria label format.</value>
[Parameter]
public string SortAriaLabelFormat { get; set; } = "Sort by {0}";
/// <summary>
/// Gets or set the filter icon to use.
/// </summary>
@@ -1602,6 +1609,15 @@ namespace Radzen.Blazor
await HandleFieldSort(pivotRows, row);
}
async Task OnDrillDownKeyDown(KeyboardEventArgs args, string pathKey)
{
var key = args.Code != null ? args.Code : args.Key;
if (key == "Enter" || key == "Space")
{
await ToggleColumnDrillDown(pathKey);
}
}
private async Task HandleFieldSort<T>(List<T> allFields, T sortedField)
where T : RadzenPivotField<TItem>
{

View File

@@ -3,9 +3,13 @@
@if (Visible)
{
<ul @ref="@Element" style="@Style" @attributes="Attributes" class="@GetCssClass()" id="@GetId()"
tabindex="0" @onkeydown="@OnKeyPress" @onkeydown:preventDefault=preventKeyPress @onkeydown:stopPropagation>
role="menu" aria-label="@ToggleAriaLabel" tabindex="0"
@onkeydown="@OnKeyPress" @onkeydown:preventDefault=preventKeyPress @onkeydown:stopPropagation>
<li class="rz-navigation-item">
<div class="rz-navigation-item-wrapper" onclick="Radzen.toggleMenuItem(this)">
<div class="rz-navigation-item-wrapper" role="button" tabindex="0" aria-haspopup="menu"
aria-label="@ToggleAriaLabel"
aria-expanded="@(!Collapsed).ToString().ToLowerInvariant()" aria-controls="@($"{GetId()}-menu")"
onclick="Radzen.toggleMenuItem(this)" @onkeydown="OnToggleKeyDown">
<div class="rz-navigation-item-link">
<div class="item-text" @onkeydown:stopPropagation>
@if (Template != null)
@@ -19,7 +23,8 @@
}
</div>
</div>
<ul class="rz-navigation-menu" style="@contentStyle" @onkeydown:stopPropagation>
<ul id="@($"{GetId()}-menu")" class="rz-navigation-menu" role="menu" style="@contentStyle"
aria-hidden="@(Collapsed ? "true" : "false")" @onkeydown:stopPropagation>
<CascadingValue Value=this>
@ChildContent
</CascadingValue>

View File

@@ -50,6 +50,13 @@ namespace Radzen.Blazor
[Parameter]
public bool ShowIcon { get; set; } = true;
/// <summary>
/// Gets or sets the toggle aria label text.
/// </summary>
/// <value>The toggle aria label text.</value>
[Parameter]
public string ToggleAriaLabel { get; set; } = "Profile menu";
string contentStyle = "display:none;position:absolute;z-index:1;";
/// <summary>
@@ -133,6 +140,15 @@ namespace Radzen.Blazor
}
}
async Task OnToggleKeyDown(KeyboardEventArgs args)
{
var key = args.Code != null ? args.Code : args.Key;
if (key == "Space" || key == "Enter")
{
await Toggle(new MouseEventArgs());
}
}
internal bool IsFocused(RadzenProfileMenuItem item)
{
return items.IndexOf(item) == focusedIndex && focusedIndex != -1;

View File

@@ -2,7 +2,7 @@
@inherits RadzenComponent
@if (Visible)
{
<li @attributes="Attributes" class="@GetItemCssClass()" style="@Style" @onclick="@OnClick">
<li @attributes="Attributes" class="@GetItemCssClass()" style="@Style" @onclick="@OnClick" role="menuitem" tabindex="0" @onkeydown="OnKeyDown">
<div class="rz-navigation-item-wrapper">
@if (Path != null)
{

View File

@@ -125,6 +125,15 @@ namespace Radzen.Blazor
}
}
async Task OnKeyDown(KeyboardEventArgs args)
{
var key = args.Code != null ? args.Code : args.Key;
if (key == "Enter" || key == "Space")
{
await OnClick(new MouseEventArgs());
}
}
RadzenProfileMenu? _parent;
/// <summary>
/// Gets or sets the parent.

View File

@@ -6,6 +6,8 @@
int n = modules.GetLength(0);
int vb = n + 8; // quiet zone
var backgroundFill = GetSvgFillParts(Background);
// --- Center Image math (viewBox units == modules incl. quiet zone) ---
bool hasImage = !string.IsNullOrWhiteSpace(Image);
// Clamp percent for scan reliability (5%..60% of QR inner box)
@@ -32,14 +34,14 @@
height="@Size"
@ref="@Element" style="@Style" @attributes="Attributes" class="@GetCssClass()" id="@GetId()">
<!-- Background -->
<rect x="0" y="0" width="@vb" height="@vb" fill="@Background" />
<rect x="0" y="0" width="@vb" height="@vb" fill="@backgroundFill.Color" fill-opacity="@Format(backgroundFill.Opacity)" />
@if (modules is not null)
{
<!-- finder patterns / eyes -->
@DrawEye(4, 4, EyeShapeTopLeft ?? EyeShape, EyeColorTopLeft ?? EyeColor ?? Foreground)
@DrawEye(vb - 11, 4, EyeShapeTopRight ?? EyeShape, EyeColorTopRight ?? EyeColor ?? Foreground)
@DrawEye(4, vb - 11, EyeShapeBottomLeft ?? EyeShape, EyeColorBottomLeft ?? EyeColor ?? Foreground)
@DrawEye(4, 4, EyeShapeTopLeft ?? EyeShape, EyeColorTopLeft ?? EyeColor ?? Foreground, $"{GetId()}-eye-0")
@DrawEye(vb - 11, 4, EyeShapeTopRight ?? EyeShape, EyeColorTopRight ?? EyeColor ?? Foreground, $"{GetId()}-eye-1")
@DrawEye(4, vb - 11, EyeShapeBottomLeft ?? EyeShape, EyeColorBottomLeft ?? EyeColor ?? Foreground, $"{GetId()}-eye-2")
<!-- data modules (skip eye regions) -->
@for (var r = 0; r < n; r++)
@@ -95,7 +97,7 @@
{
// Draw a 7x7 eye whose top-left screen coordinate (including quiet zone) is (x,y).
// NOTE: x,y are *SVG units* (already offset by quiet zone).
private RenderFragment DrawEye(double x, double y, QRCodeEyeShape shape, string color)
private RenderFragment DrawEye(double x, double y, QRCodeEyeShape shape, string color, string maskId)
{
return __builder =>
{
@@ -104,10 +106,14 @@
{
case QRCodeEyeShape.Rounded:
{
<!-- Outer 7x7 rounded ring -->
<rect x="@Format(x)" y="@Format(y)" width="@Format(7)" height="@Format(7)" rx="@Format(1.25)" ry="@Format(1.25)" fill="@color" />
<!-- Inner hole (background) -->
<rect x="@Format(x + 1)" y="@Format(y + 1)" width="@Format(5)" height="@Format(5)" rx="@Format(0.25)" ry="@Format(0.25)" fill="@Background" />
<defs>
<mask id="@maskId" maskUnits="userSpaceOnUse">
<rect x="@Format(x)" y="@Format(y)" width="@Format(7)" height="@Format(7)" rx="@Format(1.25)" ry="@Format(1.25)" fill="white" />
<rect x="@Format(x + 1)" y="@Format(y + 1)" width="@Format(5)" height="@Format(5)" rx="@Format(0.25)" ry="@Format(0.25)" fill="black" />
</mask>
</defs>
<!-- Outer 7x7 rounded ring with transparent hole -->
<rect x="@Format(x)" y="@Format(y)" width="@Format(7)" height="@Format(7)" rx="@Format(1.25)" ry="@Format(1.25)" fill="@color" mask="url(#@maskId)" />
<!-- Pupil 3x3 rounded -->
<rect x="@Format(x + 2)" y="@Format(y + 2)" width="@Format(3)" height="@Format(3)" rx="@Format(0.75)" ry="@Format(0.75)" fill="@color" />
}
@@ -115,9 +121,14 @@
case QRCodeEyeShape.Framed:
{
<!-- Bold square frame (thickness ~1.2) -->
<rect x="@Format(x)" y="@Format(y)" width="@Format(7)" height="@Format(7)" fill="@color" />
<rect x="@Format(x + 1.2)" y="@Format(y + 1.2)" width="@Format(7 - 2 * 1.2)" height="@Format(7 - 2 * 1.2)" fill="@Background" />
<defs>
<mask id="@maskId" maskUnits="userSpaceOnUse">
<rect x="@Format(x)" y="@Format(y)" width="@Format(7)" height="@Format(7)" fill="white" />
<rect x="@Format(x + 1.2)" y="@Format(y + 1.2)" width="@Format(7 - 2 * 1.2)" height="@Format(7 - 2 * 1.2)" fill="black" />
</mask>
</defs>
<!-- Bold square frame (thickness ~1.2) with transparent hole -->
<rect x="@Format(x)" y="@Format(y)" width="@Format(7)" height="@Format(7)" fill="@color" mask="url(#@maskId)" />
<!-- Pupil -->
<rect x="@Format(x + 2)" y="@Format(y + 2)" width="@Format(3)" height="@Format(3)" fill="@color" />
}
@@ -126,9 +137,14 @@
case QRCodeEyeShape.Square:
default:
{
<!-- Classic square: 7x7 outer, 5x5 inner (background), 3x3 pupil -->
<rect x="@Format(x)" y="@Format(y)" width="@Format(7)" height="@Format(7)" fill="@color" />
<rect x="@Format(x + 1)" y="@Format(y + 1)" width="@Format(5)" height="@Format(5)" fill="@Background" />
<defs>
<mask id="@maskId" maskUnits="userSpaceOnUse">
<rect x="@Format(x)" y="@Format(y)" width="@Format(7)" height="@Format(7)" fill="white" />
<rect x="@Format(x + 1)" y="@Format(y + 1)" width="@Format(5)" height="@Format(5)" fill="black" />
</mask>
</defs>
<!-- Classic square: 7x7 outer with transparent 5x5 hole -->
<rect x="@Format(x)" y="@Format(y)" width="@Format(7)" height="@Format(7)" fill="@color" mask="url(#@maskId)" />
<rect x="@Format(x + 2)" y="@Format(y + 2)" width="@Format(3)" height="@Format(3)" fill="@color" />
}
break;

View File

@@ -1,5 +1,7 @@
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using Radzen.Blazor.Rendering;
using System;
using System.Globalization;
using System.Threading.Tasks;
@@ -155,6 +157,28 @@ namespace Radzen.Blazor
/// </summary>
protected override string GetComponentCssClass() => "rz-qrcode";
private static string Format(double v) => v.ToString(CultureInfo.InvariantCulture);
private static (string Color, double Opacity) GetSvgFillParts(string? color)
{
if (string.IsNullOrWhiteSpace(color))
{
return ("none", 1);
}
if (string.Equals(color, "transparent", StringComparison.OrdinalIgnoreCase))
{
return ("rgb(0, 0, 0)", 0);
}
var rgb = RGB.Parse(color);
if (rgb == null)
{
return (color, 1);
}
var opacity = Math.Clamp(rgb.Alpha, 0, 1);
var fill = $"rgb({Format(rgb.Red)}, {Format(rgb.Green)}, {Format(rgb.Blue)})";
return (fill, opacity);
}
private static bool IsFinderCell(int r, int c, int n)
{
bool inTL = r < 7 && c < 7;

View File

@@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Components;
using System;
using System.Collections.Generic;
using System.Globalization;
using Radzen.Blazor.Rendering;
using System.Text;
namespace Radzen.Blazor;
@@ -185,12 +186,13 @@ public static class RadzenQREncoder
var sb = new StringBuilder(n * n + 1024);
sb.Append(CultureInfo.InvariantCulture, $"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"{px}\" height=\"{px}\" viewBox=\"0 0 {vb} {vb}\" shape-rendering=\"crispEdges\">");
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"0\" y=\"0\" width=\"{vb}\" height=\"{vb}\" fill=\"{background}\"/>");
var backgroundFill = GetSvgFillParts(background);
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"0\" y=\"0\" width=\"{vb}\" height=\"{vb}\" fill=\"{backgroundFill.Color}\" fill-opacity=\"{Format(backgroundFill.Opacity)}\"/>");
var baseEyeColor = eyeColor ?? foreground;
AppendEye(sb, 4, 4, eyeShapeTopLeft ?? eyeShape, eyeColorTopLeft ?? baseEyeColor, background);
AppendEye(sb, vb - 11, 4, eyeShapeTopRight ?? eyeShape, eyeColorTopRight ?? baseEyeColor, background);
AppendEye(sb, 4, vb - 11, eyeShapeBottomLeft ?? eyeShape, eyeColorBottomLeft ?? baseEyeColor, background);
AppendEye(sb, 4, 4, eyeShapeTopLeft ?? eyeShape, eyeColorTopLeft ?? baseEyeColor, "eye-mask-0");
AppendEye(sb, vb - 11, 4, eyeShapeTopRight ?? eyeShape, eyeColorTopRight ?? baseEyeColor, "eye-mask-1");
AppendEye(sb, 4, vb - 11, eyeShapeBottomLeft ?? eyeShape, eyeColorBottomLeft ?? baseEyeColor, "eye-mask-2");
for (int r = 0; r < n; r++)
{
@@ -1147,28 +1149,60 @@ public static class RadzenQREncoder
return inTL || inTR || inBL;
}
private static void AppendEye(StringBuilder sb, double x, double y, QRCodeEyeShape shape, string color, string background)
private static void AppendEye(StringBuilder sb, double x, double y, QRCodeEyeShape shape, string color, string maskId)
{
switch (shape)
{
case QRCodeEyeShape.Rounded:
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x)}\" y=\"{Format(y)}\" width=\"7\" height=\"7\" rx=\"1.25\" ry=\"1.25\" fill=\"{color}\"/>");
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x + 1)}\" y=\"{Format(y + 1)}\" width=\"5\" height=\"5\" rx=\"0.25\" ry=\"0.25\" fill=\"{background}\"/>");
sb.Append(CultureInfo.InvariantCulture, $"<defs><mask id=\"{maskId}\" maskUnits=\"userSpaceOnUse\">");
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x)}\" y=\"{Format(y)}\" width=\"7\" height=\"7\" rx=\"1.25\" ry=\"1.25\" fill=\"white\"/>");
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x + 1)}\" y=\"{Format(y + 1)}\" width=\"5\" height=\"5\" rx=\"0.25\" ry=\"0.25\" fill=\"black\"/>");
sb.Append("</mask></defs>");
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x)}\" y=\"{Format(y)}\" width=\"7\" height=\"7\" rx=\"1.25\" ry=\"1.25\" fill=\"{color}\" mask=\"url(#{maskId})\"/>");
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x + 2)}\" y=\"{Format(y + 2)}\" width=\"3\" height=\"3\" rx=\"0.75\" ry=\"0.75\" fill=\"{color}\"/>");
break;
case QRCodeEyeShape.Framed:
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x)}\" y=\"{Format(y)}\" width=\"7\" height=\"7\" fill=\"{color}\"/>");
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x + 1.2)}\" y=\"{Format(y + 1.2)}\" width=\"{Format(7 - 2 * 1.2)}\" height=\"{Format(7 - 2 * 1.2)}\" fill=\"{background}\"/>");
sb.Append(CultureInfo.InvariantCulture, $"<defs><mask id=\"{maskId}\" maskUnits=\"userSpaceOnUse\">");
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x)}\" y=\"{Format(y)}\" width=\"7\" height=\"7\" fill=\"white\"/>");
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x + 1.2)}\" y=\"{Format(y + 1.2)}\" width=\"{Format(7 - 2 * 1.2)}\" height=\"{Format(7 - 2 * 1.2)}\" fill=\"black\"/>");
sb.Append("</mask></defs>");
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x)}\" y=\"{Format(y)}\" width=\"7\" height=\"7\" fill=\"{color}\" mask=\"url(#{maskId})\"/>");
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x + 2)}\" y=\"{Format(y + 2)}\" width=\"3\" height=\"3\" fill=\"{color}\"/>");
break;
case QRCodeEyeShape.Square:
default:
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x)}\" y=\"{Format(y)}\" width=\"7\" height=\"7\" fill=\"{color}\"/>");
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x + 1)}\" y=\"{Format(y + 1)}\" width=\"5\" height=\"5\" fill=\"{background}\"/>");
sb.Append(CultureInfo.InvariantCulture, $"<defs><mask id=\"{maskId}\" maskUnits=\"userSpaceOnUse\">");
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x)}\" y=\"{Format(y)}\" width=\"7\" height=\"7\" fill=\"white\"/>");
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x + 1)}\" y=\"{Format(y + 1)}\" width=\"5\" height=\"5\" fill=\"black\"/>");
sb.Append("</mask></defs>");
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x)}\" y=\"{Format(y)}\" width=\"7\" height=\"7\" fill=\"{color}\" mask=\"url(#{maskId})\"/>");
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x + 2)}\" y=\"{Format(y + 2)}\" width=\"3\" height=\"3\" fill=\"{color}\"/>");
break;
}
}
private static (string Color, double Opacity) GetSvgFillParts(string? color)
{
if (string.IsNullOrWhiteSpace(color))
{
return ("none", 1);
}
if (string.Equals(color, "transparent", StringComparison.OrdinalIgnoreCase))
{
return ("rgb(0, 0, 0)", 0);
}
var rgb = RGB.Parse(color);
if (rgb == null)
{
return (color, 1);
}
var opacity = Math.Clamp(rgb.Alpha, 0, 1);
var fill = $"rgb({Format(rgb.Red)}, {Format(rgb.Green)}, {Format(rgb.Blue)})";
return (fill, opacity);
}
private static string Format(double v) => v.ToString(CultureInfo.InvariantCulture);
}

View File

@@ -15,15 +15,19 @@
@if (Visible)
{
<div @ref="@Element" style="@Style" @attributes="Attributes" class="@GetCssClass()" id="@GetId()"
role="radiogroup" @onfocus=@OnFocus @onblur=@OnBlur
role="radiogroup" aria-disabled="@(Disabled ? "true" : "false")" @onfocus=@OnFocus @onblur=@OnBlur
tabindex="@(Disabled ? "-1" : $"{TabIndex}")" @onkeydown="@(args => OnKeyPress(args))" @onkeydown:preventDefault=preventKeyPress @onkeydown:stopPropagation>
<RadzenStack Orientation="@Orientation" JustifyContent="@JustifyContent" AlignItems="@AlignItems" Gap="@Gap" Wrap="@Wrap">
@foreach (var item in allItems.Where(i => i.Visible))
{
<div @ref="@item.Element" id="@item.GetItemId()" @onclick="@(args => SelectItem(item))" @attributes="item.Attributes" class="@item.GetItemCssClass()" style="@item.Style" role="radio" aria-checked=@(IsSelected(item)? "true" : "false") aria-label="@item.Text">
<div @ref="@item.Element" id="@item.GetItemId()" @onclick="@(args => SelectItem(item))" @onkeydown="@(args => OnItemKeyDown(args, item))"
@attributes="item.Attributes" class="@item.GetItemCssClass()" style="@item.Style"
role="radio" tabindex="@(Disabled || item.Disabled ? "-1" : "0")"
aria-checked=@(IsSelected(item)? "true" : "false") aria-label="@item.Text"
aria-disabled="@(Disabled || item.Disabled ? "true" : "false")">
<div class="rz-radiobutton">
<div class="rz-helper-hidden-accessible">
<input type="radio" disabled="@Disabled" name="@Name" value="@item.Value" tabindex="-1" aria-label="@(item.Text + " " + item.Value)" @attributes="item.InputAttributes">
<input type="radio" disabled="@(Disabled || item.Disabled)" name="@Name" value="@item.Value" tabindex="-1" aria-label="@(item.Text + " " + item.Value)" @attributes="item.InputAttributes">
</div>
<div class=@ItemClass(item)>
<span class=@IconClass(item)></span>

View File

@@ -250,6 +250,15 @@ namespace Radzen.Blazor
StateHasChanged();
}
async Task OnItemKeyDown(KeyboardEventArgs args, RadzenRadioButtonListItem<TValue> item)
{
var key = args.Code != null ? args.Code : args.Key;
if (key == "Enter" || key == "Space")
{
await SelectItem(item);
}
}
/// <summary>
/// Refreshes this instance.
/// </summary>

View File

@@ -6,19 +6,19 @@
<div @ref="@Element" style=@Style @attributes="Attributes" class="@GetCssClass()" id="@GetId()">
@if (!ReadOnly)
{
<a id="@(GetId() + "cl")" aria-label="@ClearAriaLabel" @onclick="@(args => SetValue(0))" @onclick:preventDefault="true" class="rz-rating-cancel"
tabindex="@(Disabled ? "-1" : $"{TabIndex}")"
<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>
<span class="notranslate rz-rating-icon rzi rzi-ban"></span>
</a>
</button>
}
@foreach (var index in Enumerable.Range(1, Stars))
{
<a id="@(GetId() + index.ToString() + "r")" aria-label="@RateAriaLabel" @onclick="@(args => SetValue(index))" @onclick:preventDefault="true"
<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>
<span class="notranslate rz-rating-icon rzi @(index <= Value ? "rzi-star": "rzi-star-o")"></span>
</a>
</button>
}
</div>
}

View File

@@ -14,13 +14,17 @@
@if (Visible)
{
<div @ref="@Element" style="@Style" @attributes="Attributes" class="@GetCssClass()" id="@GetId()"
role="toolbar" aria-orientation="horizontal" @onfocus=@this.AsNonRenderingEventHandler(OnFocus) @onblur=@this.AsNonRenderingEventHandler(OnBlur)
role="toolbar" aria-orientation="@(Orientation == Orientation.Vertical ? "vertical" : "horizontal")"
aria-disabled="@(Disabled ? "true" : "false")"
@onfocus=@this.AsNonRenderingEventHandler(OnFocus) @onblur=@this.AsNonRenderingEventHandler(OnBlur)
tabindex="@(Disabled ? "-1" : $"{TabIndex}")" @onkeydown="@(args => OnKeyPress(args))" @onkeydown:preventDefault=preventKeyPress @onkeydown:stopPropagation>
@foreach (var item in allItems.Where(i => i.Visible))
{
<div @ref="@item.Element" id="@item.GetItemId()"
<button type="button" @ref="@item.Element" id="@item.GetItemId()"
@onclick="@(args => SelectItem(item))"
@attributes="item.Attributes" style="@item.Style" class=@ButtonClass(item) aria-label="@item.Text" aria-selected=@(IsSelected(item)? "true" : "false")>
@attributes="item.Attributes" style="@item.Style" class=@ButtonClass(item)
aria-label="@item.Text" aria-pressed="@(IsSelected(item)? "true" : "false")"
disabled="@(Disabled || item.Disabled)">
@if (item.Template != null)
{
@item.Template(item)
@@ -37,7 +41,7 @@
}
<span class="rz-button-text">@item.Text</span>
}
</div>
</button>
}
</div>
}

View File

@@ -1,7 +1,7 @@
@inherits RadzenComponent
@if (Visible)
{
<button aria-label="@ToggleAriaLabel" tabindex="0" @ref="@Element" style="@Style" @attributes="Attributes" class="@GetCssClass()" @onclick="@OnClick" id="@GetId()">
<button type="button" aria-label="@ToggleAriaLabel" tabindex="0" @ref="@Element" style="@Style" @attributes="Attributes" class="@GetCssClass()" @onclick="@OnClick" id="@GetId()">
<i class="notranslate rzi rz-display-flex rz-align-items-center">@(!string.IsNullOrWhiteSpace(Icon) ? Icon : "menu")</i>
</button>
}

View File

@@ -43,11 +43,12 @@
}
</span>
</button>
<button tabindex="-1" disabled="@IsDisabled" onclick="@OpenPopupScript()" class=@PopupButtonClass type="button" aria-label="@OpenAriaLabel">
<button tabindex="-1" disabled="@IsDisabled" onclick="@OpenPopupScript()" class=@PopupButtonClass type="button"
aria-label="@OpenAriaLabel" aria-haspopup="menu" aria-controls="@PopupID" aria-expanded="false">
<RadzenIcon Icon="@DropDownIcon" />
</button>
<div id="@PopupID" class="rz-splitbutton-menu">
<ul class="rz-menu-list">
<ul class="rz-menu-list" role="menu" aria-label="@OpenAriaLabel">
<CascadingValue Value=this>
@ChildContent
</CascadingValue>

View File

@@ -1,8 +1,9 @@
@inherits RadzenComponent
@if (Visible)
{
<li class=@ItemClass role="menuitem" @onclick="@OnClick" @attributes="Attributes" style="@Style">
<a id="@(SplitButton?.SplitButtonId() + GetHashCode())" class="rz-menuitem-link">
<li class=@ItemClass role="menuitem" tabindex="@(Disabled ? "-1" : "0")" aria-disabled="@(Disabled ? "true" : "false")"
@onclick="@OnClick" @onkeydown="OnKeyDown" @attributes="Attributes" style="@Style">
<span id="@(SplitButton?.SplitButtonId() + GetHashCode())" class="rz-menuitem-link">
@if (!string.IsNullOrEmpty(Icon))
{
<span class="rz-menuitem-icon" style="@(!string.IsNullOrEmpty(IconColor) ? $"color:{IconColor}" : null)">@Icon</span>
@@ -11,6 +12,6 @@
{
<span class="rz-menuitem-text">@Text</span>
}
</a>
</span>
</li>
}

View File

@@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Radzen.Blazor.Rendering;
using System;
using System.Threading.Tasks;
namespace Radzen.Blazor
{
@@ -81,6 +82,21 @@ namespace Radzen.Blazor
}
}
async Task OnKeyDown(KeyboardEventArgs args)
{
ArgumentNullException.ThrowIfNull(args);
if (Disabled)
{
return;
}
var key = args.Code != null ? args.Code : args.Key;
if (key == "Enter" || key == "Space")
{
await OnClick(new MouseEventArgs());
}
}
string ItemClass => ClassList.Create("rz-menuitem")
.AddDisabled(Disabled)
.Add("rz-state-highlight", SplitButton?.IsFocused(this) == true)

View File

@@ -155,6 +155,12 @@ namespace Radzen.Blazor
/// </summary>
public bool IsResizing { get; private set; }
/// <summary>
/// Value indicating if the splitter should call StateHasChanged on resizing.
/// </summary>
[Parameter]
public bool ChangeStateOnResize { get; set; } = true;
/// <summary>
/// Called on pane resizing.
/// </summary>
@@ -163,7 +169,10 @@ namespace Radzen.Blazor
{
IsResizing = true;
StateHasChanged();
if (ChangeStateOnResize)
{
StateHasChanged();
}
await Task.CompletedTask;
}

View File

@@ -21,10 +21,13 @@
var step = steps[i];
@if (step.Visible)
{
<li class="@step.GetItemCssClass()" @attributes="step.Attributes" style="@step.Style" tabindex="@(step.Disabled ? -1 : 0)" @onkeypress="@(args => OnKeyPress(args, SelectStep(step, true)))" @onkeypress:preventDefault=preventKeyPress @onkeypress:stopPropagation>
<a id="@(GetId() + i.ToString() + "s")" title="@step.Title" aria-label="@step.AriaLabel"
<li class="@step.GetItemCssClass()" @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}")"
@onclick="@(async (args) => { if (!step.Disabled && AllowStepSelect) { await SelectStep(step, true); } })"
@onclick:preventDefault="true" class="rz-menuitem-link">
@onkeypress="@(args => OnKeyPress(args, SelectStep(step, true)))" @onkeypress:preventDefault=preventKeyPress @onkeypress:stopPropagation
disabled="@step.Disabled" aria-disabled="@step.Disabled" class="rz-menuitem-link">
<span class="rz-steps-number">@(steps.Where(s => s.Visible).ToList().IndexOf(step) + 1)</span>
@if (step.Template != null)
{
@@ -34,7 +37,7 @@
{
<span class="rz-steps-title">@step.Text</span>
}
</a>
</button>
</li>
}
}
@@ -47,7 +50,7 @@
{
@if (IsSelected(i, step))
{
<div class="rz-widget-content">
<div class="rz-widget-content" role="tabpanel" id="@($"{GetId()}-panel-{i}")" aria-labelledby="@(GetId() + i.ToString() + "s")">
@if (step.ChildContent != null)
{
@step.ChildContent
@@ -59,14 +62,16 @@
@if(ShowStepsButtons)
{
<div class="rz-steps-buttons">
<a id="@(GetId() + "prev")" title="@PreviousTitle" aria-label="@PreviousAriaLabel" tabindex="@(IsFirstVisibleStep() ? -1 : 0)"
<button type="button" id="@(GetId() + "prev")" title="@PreviousTitle" aria-label="@PreviousAriaLabel"
class='@($"rz-steps-prev {(IsFirstVisibleStep() ? "rz-state-disabled" : "")}")'
@onkeypress="@(args => OnKeyPress(args, PrevStep()))" @onkeypress:preventDefault=preventKeyPress @onkeypress:stopPropagation
@onclick="@PrevStep" @onclick:preventDefault="true"><span class="notranslate rzi"></span>@PreviousText</a>
<a id="@(GetId() + "next")" title="@NextTitle" aria-label="@NextAriaLabel" tabindex="@(IsLastVisibleStep() ? -1 : 0)"
disabled="@(IsFirstVisibleStep())" aria-disabled="@(IsFirstVisibleStep())"
@onkeypress="@(args => OnKeyPress(args, PrevStep()))" @onkeypress:preventDefault=preventKeyPress @onkeypress:stopPropagation
@onclick="@PrevStep"><span class="notranslate rzi"></span>@PreviousText</button>
<button type="button" id="@(GetId() + "next")" title="@NextTitle" aria-label="@NextAriaLabel"
class='@($"rz-steps-next {(IsLastVisibleStep() ? "rz-state-disabled" : "")}")'
@onkeypress="@(args => OnKeyPress(args, NextStep()))" @onkeypress:preventDefault=preventKeyPress @onkeypress:stopPropagation
@onclick="@NextStep" @onclick:preventDefault="true">@NextText<span class="notranslate rzi"></span></a>
disabled="@(IsLastVisibleStep())" aria-disabled="@(IsLastVisibleStep())"
@onkeypress="@(args => OnKeyPress(args, NextStep()))" @onkeypress:preventDefault=preventKeyPress @onkeypress:stopPropagation
@onclick="@NextStep">@NextText<span class="notranslate rzi"></span></button>
</div>
}
</div>

View File

@@ -3,11 +3,12 @@
@inherits FormComponent<bool>
@if (Visible)
{
<div @ref="@Element" @attributes="Attributes" class="@GetCssClass()" id="@GetId()"
@onclick="@Toggle" @onkeypress="@(args => OnKeyPress(args, Toggle()))" @onkeypress:preventDefault=preventKeyPress @onkeypress:stopPropagation style="@Style" tabindex="@(Disabled ? "-1" : $"{TabIndex}")">
<div class="rz-helper-hidden-accessible">
<input type="checkbox" name="@Name" id="@Name" checked="@Value" value="@ValueAsString" tabindex="-1" aria-checked="@(Value.ToString().ToLowerInvariant())" @attributes=@InputAttributes>
</div>
<label @ref="@Element" @attributes="Attributes" class="@GetCssClass()" id="@GetId()" style="@Style">
<input type="checkbox" role="switch" name="@Name" id="@Name" checked="@Value"
value="@ValueAsString" tabindex="@(Disabled || ReadOnly ? "-1" : $"{TabIndex}")"
disabled="@(Disabled || ReadOnly)" aria-checked="@(Value.ToString().ToLowerInvariant())"
@onclick="@Toggle" @onkeydown="@OnKeyPress"
@onkeydown:stopPropagation class="rz-hidden-accessible" @attributes=@InputAttributes>
<span class="rz-switch-circle@(Disabled ? " rz-disabled" : ReadOnly ? " rz-readonly" : "")"></span>
</div>
</label>
}

View File

@@ -63,7 +63,7 @@ namespace Radzen.Blazor
/// Does nothing if the switch is disabled or read-only.
/// </summary>
/// <returns>A task representing the asynchronous toggle operation.</returns>
public async System.Threading.Tasks.Task Toggle()
public async Task Toggle()
{
if (Disabled || ReadOnly)
{
@@ -77,20 +77,13 @@ namespace Radzen.Blazor
await Change.InvokeAsync(Value);
}
bool preventKeyPress = true;
async Task OnKeyPress(KeyboardEventArgs args, Task task)
async Task OnKeyPress(KeyboardEventArgs args)
{
var key = args.Code != null ? args.Code : args.Key;
if (key == "Space" || key == "Enter")
if (key == "Enter")
{
preventKeyPress = true;
await task;
}
else
{
preventKeyPress = false;
await Toggle();
}
}
}

View File

@@ -6,7 +6,8 @@
{
<div @ref=@Element style=@Style @attributes=@Attributes class=@GetCssClass() id=@GetId()
tabindex=0 @onkeydown="@(args => OnKeyPress(args))" @onkeydown:preventDefault=preventKeyPress @onkeydown:stopPropagation>
<ul role="tablist" class="rz-tabview-nav">
<ul role="tablist" class="rz-tabview-nav"
aria-orientation="@(TabPosition == TabPosition.Left || TabPosition == TabPosition.Right ? "vertical" : "horizontal")">
@Tabs
</ul>
<div class="rz-tabview-panels">

View File

@@ -2,9 +2,15 @@
@implements IDisposable
@if ((Tabs?.RenderMode == TabRenderMode.Server ? Visible : true))
{
<li role="tab" @attributes=@Attributes style=@getStyle() class=@Class
aria-selected=@(IsSelected ? "true" : "false") aria-controls="@($"{Tabs?.Id ?? ""}-tabpanel-{Index}")">
<a @onclick=@OnClick @onclick:preventDefault="true" id="@($"{Tabs?.Id ?? ""}-tabpanel-{Index}-label")" @onkeydown:stopPropagation>
<li role="presentation" style=@getStyle() class=@Class>
<button type="button" role="tab" @attributes=@Attributes @onclick=@OnClick
id="@($"{Tabs?.Id ?? ""}-tabpanel-{Index}-label")"
aria-selected="@(IsSelected ? "true" : "false")"
aria-controls="@($"{Tabs?.Id ?? ""}-tabpanel-{Index}")"
aria-disabled="@(Disabled ? "true" : "false")"
disabled="@Disabled"
tabindex="@(Disabled ? "-1" : (IsSelected ? "0" : "-1"))"
@onkeydown:stopPropagation>
@if (!string.IsNullOrEmpty(Icon))
{
<span class="notranslate rzi rz-tabview-icon" style="@(!string.IsNullOrEmpty(IconColor) ? $"color:{IconColor}" : null)">@Icon</span>
@@ -17,6 +23,6 @@
{
<span class="rz-tabview-title">@Text</span>
}
</a>
</button>
</li>
}

View File

@@ -29,18 +29,20 @@
@onkeydown:preventDefault="@preventKeyPress" @onkeydown:stopPropagation />
@if (ShowPopupButton)
{
<button type="button" aria-label="@TogglePopupAriaLabel" disabled="@Disabled"
tabindex="-1" class="@GetTogglePopupButtonClass()"
<button type="button" aria-label="@TogglePopupAriaLabel" aria-haspopup="dialog"
aria-expanded="@isPopupOpen.ToString().ToLowerInvariant()" aria-controls="@($"{GetId()}-popup")"
disabled="@Disabled" tabindex="-1" class="@GetTogglePopupButtonClass()"
@onclick="@ClickPopupButton">
<span aria-hidden="true" class="notranslate rz-button-icon-left rzi rzi-timespan"></span><span class="rz-button-text"></span>
</button>
}
@if (AllowClear && HasValue && (_isNullable || ConfirmedValue != DefaultNonNullValue) && !Disabled && !ReadOnly)
{
<i class="notranslate rz-dropdown-clear-icon rzi rzi-times" @onclick="@Clear" @onclick:stopPropagation="true"></i>
<button type="button" class="notranslate rz-dropdown-clear-icon rzi rzi-times" aria-label="@ClearAriaLabel" @onclick="@Clear" @onclick:stopPropagation="true"></button>
}
<Popup @ref="@popup" Lazy="@(PopupRenderMode == PopupRenderMode.OnDemand)"
<Popup id="@($"{GetId()}-popup")" @ref="@popup" Lazy="@(PopupRenderMode == PopupRenderMode.OnDemand)"
Open="@OnPopupOpen" Close="@OnPopupClose"
role="dialog" aria-label="@PopupAriaLabel"
class="rz-timespanpicker-popup-container">
@RenderPanel()
</Popup>

View File

@@ -161,6 +161,19 @@ namespace Radzen.Blazor
/// </summary>
[Parameter]
public string TogglePopupAriaLabel { get; set; } = "Toggle popup";
/// <summary>
/// Specifies the aria label for the popup.
/// </summary>
[Parameter]
public string PopupAriaLabel { get; set; } = "Time span picker";
/// <summary>
/// Gets or sets the clear button aria label text.
/// </summary>
/// <value>The clear button aria label text.</value>
[Parameter]
public string ClearAriaLabel { get; set; } = "Clear";
#endregion
#region Parameters: panel config
@@ -716,6 +729,8 @@ namespace Radzen.Blazor
private Task ClosePopup()
=> Inline ? Task.CompletedTask : popup?.CloseAsync(Element) ?? Task.CompletedTask;
private bool isPopupOpen;
private async Task PopupKeyDown(KeyboardEventArgs args)
{
var key = args.Code ?? args.Key;
@@ -728,11 +743,13 @@ namespace Radzen.Blazor
private void OnPopupOpen()
{
isPopupOpen = true;
ResetUnconfirmedValue();
preventKeyPress = true;
}
private void OnPopupClose()
{
isPopupOpen = false;
ResetUnconfirmedValue();
preventKeyPress = false;
}

View File

@@ -139,5 +139,6 @@ public partial class RadzenToc : RadzenComponent, IAsyncDisposable
public async ValueTask DisposeAsync()
{
await UnregisterScrollListenerAsync();
Dispose();
}
}

View File

@@ -1,7 +1,16 @@
@inherits RadzenComponentWithChildren
<li role="presentation" @ref=@Element style=@Style @attributes=@Attributes class=@GetCssClass() id=@GetId()>
<div class=@WrapperClass>
<a href=@Selector class=@LinkClass @onclick:preventDefault @onclick=@OnClickAsync>@if (Template is null) {@Text} else {@Template}</a>
<button type="button" class=@LinkClass @onclick=@OnClickAsync aria-current="@(selected ? "location" : null)">
@if (Template is null)
{
@Text
}
else
{
@Template
}
</button>
</div>
@if (ChildContent != null)
{

View File

@@ -6,10 +6,12 @@
type="@((Enum.GetName(typeof(ButtonType), ButtonType) ?? ButtonType.ToString()).ToLower())"
@attributes="Attributes" class="@GetCssClass()" id="@GetId()"
@onclick="@OnClick" tabindex="@(Disabled ? "-1" : $"{TabIndex}")"
aria-label="@AriaLabel">
<div class="rz-helper-hidden-accessible">
<input type="checkbox" name="@Name" id="@Name" checked="@Value" value="@(Value.ToString().ToLower())" tabindex="-1" aria-checked="@(Value.ToString().ToLowerInvariant())" @attributes=@InputAttributes>
</div>
aria-label="@AriaLabel" aria-pressed="@(Value.ToString().ToLowerInvariant())"
aria-expanded="@AriaExpanded" aria-controls="@AriaControls" aria-haspopup="@AriaHasPopup">
@if (!string.IsNullOrEmpty(Name))
{
<input type="hidden" name="@Name" id="@Name" value="@(Value.ToString().ToLowerInvariant())" @attributes=@InputAttributes />
}
<span class="rz-button-box">
@if (ChildContent != null)
{

View File

@@ -47,6 +47,27 @@ namespace Radzen.Blazor
[Parameter]
public string? ToggleIcon { get; set; }
/// <summary>
/// Gets or sets the aria-expanded attribute.
/// </summary>
/// <value>The aria-expanded attribute.</value>
[Parameter]
public string? AriaExpanded { get; set; }
/// <summary>
/// Gets or sets the aria-controls attribute.
/// </summary>
/// <value>The aria-controls attribute.</value>
[Parameter]
public string? AriaControls { get; set; }
/// <summary>
/// Gets or sets the aria-haspopup attribute.
/// </summary>
/// <value>The aria-haspopup attribute.</value>
[Parameter]
public string? AriaHasPopup { get; set; }
private string? GetIcon()
{
return Value && !string.IsNullOrEmpty(ToggleIcon) ? ToggleIcon : Icon;

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