mirror of
https://github.com/radzenhq/radzen-blazor.git
synced 2026-02-05 21:28:59 +00:00
Compare commits
47 Commits
spreadshee
...
accessibil
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c537fa5a8 | ||
|
|
49bfac1d86 | ||
|
|
dac60a1c6b | ||
|
|
9044596b6d | ||
|
|
8994bd400d | ||
|
|
fb9ff6227a | ||
|
|
6532cba586 | ||
|
|
5bbf7c1fde | ||
|
|
e416cede62 | ||
|
|
270a7e0f80 | ||
|
|
1a12a75bde | ||
|
|
d1917eac0c | ||
|
|
09830f0ea2 | ||
|
|
d34e0684fb | ||
|
|
482eca3278 | ||
|
|
5c8ac16c83 | ||
|
|
29382cf0f4 | ||
|
|
53204cc8d6 | ||
|
|
6cf550c517 | ||
|
|
7bf107af4c | ||
|
|
adf2785a5a | ||
|
|
cae44df00a | ||
|
|
8ba1c69573 | ||
|
|
56031c2fd4 | ||
|
|
ad44802d30 | ||
|
|
64ca088e61 | ||
|
|
f4777565a2 | ||
|
|
eb1423e757 | ||
|
|
596b251511 | ||
|
|
e186315935 | ||
|
|
cba9a5120d | ||
|
|
ee62a21ab6 | ||
|
|
0a5e318f80 | ||
|
|
8dd7d7f521 | ||
|
|
69573b2d7d | ||
|
|
5b933c6643 | ||
|
|
126b2d1efa | ||
|
|
a65c1a9482 | ||
|
|
4939e8498a | ||
|
|
e380467853 | ||
|
|
d3adc9733b | ||
|
|
a03fc50ee8 | ||
|
|
c9201bf947 | ||
|
|
8c8d288afd | ||
|
|
1a679af008 | ||
|
|
bc1654a405 | ||
|
|
94ef62e00b |
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
@@ -508,7 +515,7 @@ namespace Radzen
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="property">The property.</param>
|
||||
/// <returns>System.Object.</returns>
|
||||
public object? GetItemOrValueFromProperty(object? item, string property)
|
||||
public virtual object? GetItemOrValueFromProperty(object? item, string property)
|
||||
{
|
||||
if (item != null)
|
||||
{
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
22
Radzen.Blazor/IRadzenSpiderChart.cs
Normal file
22
Radzen.Blazor/IRadzenSpiderChart.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Radzen.Blazor
|
||||
{
|
||||
/// <summary>
|
||||
/// Non-generic contract for <see cref="RadzenSpiderChart"/> used by configuration components
|
||||
/// like <see cref="RadzenSpiderLegend"/> without relying on reflection (important for trimming/AOT).
|
||||
/// </summary>
|
||||
public interface IRadzenSpiderChart
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the legend configuration for the chart.
|
||||
/// </summary>
|
||||
RadzenSpiderLegend Legend { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Requests the chart to refresh its rendering.
|
||||
/// </summary>
|
||||
Task Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
29
Radzen.Blazor/IRadzenSpiderSeries.cs
Normal file
29
Radzen.Blazor/IRadzenSpiderSeries.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Radzen.Blazor
|
||||
{
|
||||
/// <summary>
|
||||
/// Non-generic contract for spider series so <see cref="RadzenSpiderChart"/> can be non-generic.
|
||||
/// </summary>
|
||||
internal interface IRadzenSpiderSeries
|
||||
{
|
||||
int Index { get; set; }
|
||||
string Title { get; }
|
||||
bool IsVisible { get; set; }
|
||||
bool MarkersVisible { get; }
|
||||
double MarkerSize { get; }
|
||||
double StrokeWidth { get; }
|
||||
|
||||
IEnumerable<string> GetCategories();
|
||||
IEnumerable<double> GetValues();
|
||||
double GetValue(string category);
|
||||
object? GetData(string category);
|
||||
string FormatValue(double value);
|
||||
|
||||
double MeasureLegend();
|
||||
RenderFragment RenderLegendItem();
|
||||
void ForceUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -1695,7 +1754,7 @@ namespace Radzen
|
||||
return columns
|
||||
.Where(c => c.Filterable
|
||||
&& c.FilterPropertyType != null
|
||||
&& (!string.IsNullOrEmpty(c.GetFilterValue() as string)
|
||||
&& (!(c.GetFilterValue() == null || (c.GetFilterValue() as string)?.Length == 0)
|
||||
|| !c.CanSetFilterValue()
|
||||
|| c.HasCustomFilter())
|
||||
&& c.GetFilterProperty() != null)
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<IsPackable>true</IsPackable>
|
||||
<PackageId>Radzen.Blazor</PackageId>
|
||||
<Product>Radzen.Blazor</Product>
|
||||
<Version>8.6.3</Version>
|
||||
<Version>8.7.5</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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.JSInterop;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Radzen.Blazor
|
||||
{
|
||||
@@ -93,22 +95,6 @@ namespace Radzen.Blazor
|
||||
_ => false
|
||||
};
|
||||
|
||||
internal readonly struct BarcodeRect
|
||||
{
|
||||
public readonly double X;
|
||||
public readonly double Y;
|
||||
public readonly double Width;
|
||||
public readonly double Height;
|
||||
|
||||
public BarcodeRect(double x, double y, double width, double height)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
Width = width;
|
||||
Height = height;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the barcode value to encode.
|
||||
/// </summary>
|
||||
@@ -275,894 +261,20 @@ namespace Radzen.Blazor
|
||||
if (vbWidth <= 0) vbWidth = 1;
|
||||
return (geometry.bars, vbWidth, checksumText, null);
|
||||
}
|
||||
}
|
||||
|
||||
internal static class RadzenBarcodeEncoder
|
||||
{
|
||||
// Code 128 patterns (0..106). Each entry is 6 digits (bar/space/bar/space/bar/space) module widths.
|
||||
// Stop code (106) is 7 digits in the spec (includes a final bar). We keep it as 7 digits and handle it.
|
||||
static readonly string[] Code128Patterns = new[]
|
||||
/// <summary>
|
||||
/// Returns the SVG markup of the rendered QR code as a string.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="Task{String}"/> representing the asynchronous operation. The task result contains the SVG markup of the QR code.
|
||||
/// </returns>
|
||||
public async Task<string> ToSvg()
|
||||
{
|
||||
"212222","222122","222221","121223","121322","131222","122213","122312","132212","221213",
|
||||
"221312","231212","112232","122132","122231","113222","123122","123221","223211","221132",
|
||||
"221231","213212","223112","312131","311222","321122","321221","312212","322112","322211",
|
||||
"212123","212321","232121","111323","131123","131321","112313","132113","132311","211313",
|
||||
"231113","231311","112133","112331","132131","113123","113321","133121","313121","211331",
|
||||
"231131","213113","213311","213131","311123","311321","331121","312113","312311","332111",
|
||||
"314111","221411","431111","111224","111422","121124","121421","141122","141221","112214",
|
||||
"112412","122114","122411","142112","142211","241211","221114","413111","241112","134111",
|
||||
"111242","121142","121241","114212","124112","124211","411212","421112","421211","212141",
|
||||
"214121","412121","111143","111341","131141","114113","114311","411113","411311","113141",
|
||||
"114131","311141","411131","211412","211214","211232","2331112"
|
||||
};
|
||||
|
||||
public static IReadOnlyList<int> EncodeCode128B(string value) => EncodeCode128B(value, out _);
|
||||
|
||||
public static IReadOnlyList<int> EncodeCode128B(string value, out int checksum)
|
||||
{
|
||||
// Code 128 subset B supports ASCII 32..127 (inclusive). We treat 127 as DEL.
|
||||
var codes = new List<int>(value.Length + 3);
|
||||
const int startB = 104;
|
||||
const int stop = 106;
|
||||
|
||||
codes.Add(startB);
|
||||
|
||||
for (int i = 0; i < value.Length; i++)
|
||||
if (JSRuntime != null)
|
||||
{
|
||||
var ch = value[i];
|
||||
int ascii = ch;
|
||||
if (ascii < 32 || ascii > 127)
|
||||
{
|
||||
throw new ArgumentException($"Code128B supports ASCII 32..127. Invalid character: U+{ascii:X4}.");
|
||||
}
|
||||
|
||||
// In Code128B, code value is ascii - 32
|
||||
codes.Add(ascii - 32);
|
||||
return await JSRuntime.InvokeAsync<string>("Radzen.outerHTML", Element);
|
||||
}
|
||||
|
||||
int checksumValue = codes[0];
|
||||
for (int i = 1; i < codes.Count; i++)
|
||||
{
|
||||
checksumValue += codes[i] * i;
|
||||
}
|
||||
checksumValue %= 103;
|
||||
|
||||
// expose checksum (0..102)
|
||||
checksum = checksumValue;
|
||||
|
||||
codes.Add(checksumValue);
|
||||
codes.Add(stop);
|
||||
|
||||
// Convert codes to module pattern widths, alternating bar/space.
|
||||
// Most codes are 6 digits; stop is 7 digits.
|
||||
var modules = new List<int>(codes.Count * 6);
|
||||
foreach (var code in codes)
|
||||
{
|
||||
var p = Code128Patterns[code];
|
||||
for (int i = 0; i < p.Length; i++)
|
||||
{
|
||||
modules.Add(p[i] - '0');
|
||||
}
|
||||
}
|
||||
|
||||
// Code 128 requires a 2-module termination bar after the stop pattern.
|
||||
// Many pattern tables omit it because it can be represented by extending the
|
||||
// final bar of the stop pattern by 2 modules.
|
||||
if (modules.Count > 0)
|
||||
{
|
||||
modules[^1] += 2;
|
||||
}
|
||||
|
||||
return modules;
|
||||
}
|
||||
|
||||
static readonly Dictionary<char, string> Code39Map = new Dictionary<char, string>()
|
||||
{
|
||||
// Each pattern is 9 elements (bar/space alternating, starting with bar).
|
||||
// 'n' = narrow (1), 'w' = wide (2). We expand to digits.
|
||||
['0'] = "nnnwwnwnn",
|
||||
['1'] = "wnnwnnnnw",
|
||||
['2'] = "nnwwnnnnw",
|
||||
['3'] = "wnwwnnnnn",
|
||||
['4'] = "nnnwwnnnw",
|
||||
['5'] = "wnnwwnnnn",
|
||||
['6'] = "nnwwwnnnn",
|
||||
['7'] = "nnnwnnwnw",
|
||||
['8'] = "wnnwnnwnn",
|
||||
['9'] = "nnwwnnwnn",
|
||||
['A'] = "wnnnnwnnw",
|
||||
['B'] = "nnwnnwnnw",
|
||||
['C'] = "wnwnnwnnn",
|
||||
['D'] = "nnnnwwnnw",
|
||||
['E'] = "wnnnwwnnn",
|
||||
['F'] = "nnwnwwnnn",
|
||||
['G'] = "nnnnnwwnw",
|
||||
['H'] = "wnnnnwwnn",
|
||||
['I'] = "nnwnnwwnn",
|
||||
['J'] = "nnnnwwwnn",
|
||||
['K'] = "wnnnnnnww",
|
||||
['L'] = "nnwnnnnww",
|
||||
['M'] = "wnwnnnnwn",
|
||||
['N'] = "nnnnwnnww",
|
||||
['O'] = "wnnnwnnwn",
|
||||
['P'] = "nnwnwnnwn",
|
||||
['Q'] = "nnnnnnwww",
|
||||
['R'] = "wnnnnnwwn",
|
||||
['S'] = "nnwnnnwwn",
|
||||
['T'] = "nnnnwnwwn",
|
||||
['U'] = "wwnnnnnnw",
|
||||
['V'] = "nwwnnnnnw",
|
||||
['W'] = "wwwnnnnnn",
|
||||
['X'] = "nwnnwnnnw",
|
||||
['Y'] = "wwnnwnnnn",
|
||||
['Z'] = "nwwnwnnnn",
|
||||
['-'] = "nwnnnnwnw",
|
||||
['.'] = "wwnnnnwnn",
|
||||
[' '] = "nwwnnnwnn",
|
||||
['$'] = "nwnwnwnnn",
|
||||
['/'] = "nwnwnnnwn",
|
||||
['+'] = "nwnnnwnwn",
|
||||
['%'] = "nnnwnwnwn",
|
||||
['*'] = "nwnnwnwnn", // start/stop
|
||||
};
|
||||
|
||||
public static IReadOnlyList<int> EncodeCode39(string value)
|
||||
{
|
||||
// Code39 traditionally uses uppercase
|
||||
var text = value.ToUpperInvariant();
|
||||
|
||||
foreach (var ch in text)
|
||||
{
|
||||
if (!Code39Map.ContainsKey(ch))
|
||||
{
|
||||
throw new ArgumentException($"Code39 does not support character '{ch}'.");
|
||||
}
|
||||
}
|
||||
|
||||
// Start + data + stop, inter-character gap (narrow space) between characters.
|
||||
var full = "*" + text + "*";
|
||||
var modules = new List<int>(full.Length * 10);
|
||||
|
||||
for (int idx = 0; idx < full.Length; idx++)
|
||||
{
|
||||
var pat = Code39Map[full[idx]];
|
||||
for (int i = 0; i < pat.Length; i++)
|
||||
{
|
||||
modules.Add(pat[i] == 'w' ? 2 : 1);
|
||||
}
|
||||
|
||||
// Inter-character gap (narrow space) except after last char.
|
||||
if (idx != full.Length - 1)
|
||||
{
|
||||
modules.Add(1);
|
||||
}
|
||||
}
|
||||
|
||||
return modules;
|
||||
}
|
||||
|
||||
public static IReadOnlyList<int> EncodeItf(string value)
|
||||
{
|
||||
var digits = new string(value.Where(char.IsDigit).ToArray());
|
||||
if (digits.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("ITF requires numeric input.");
|
||||
}
|
||||
if (digits.Length % 2 != 0)
|
||||
{
|
||||
// pad with leading zero (common behavior)
|
||||
digits = "0" + digits;
|
||||
}
|
||||
|
||||
const int narrow = 1;
|
||||
const int wide = 3;
|
||||
|
||||
static string Pat(char d) => d switch
|
||||
{
|
||||
'0' => "nnwwn",
|
||||
'1' => "wnnnw",
|
||||
'2' => "nwnnw",
|
||||
'3' => "wwnnn",
|
||||
'4' => "nnwnw",
|
||||
'5' => "wnwnn",
|
||||
'6' => "nwwnn",
|
||||
'7' => "nnnww",
|
||||
'8' => "wnnwn",
|
||||
'9' => "nwnwn",
|
||||
_ => throw new ArgumentException("ITF requires numeric input.")
|
||||
};
|
||||
|
||||
var widths = new List<int>(digits.Length * 10 + 16);
|
||||
|
||||
// Start: narrow bar, narrow space, narrow bar, narrow space (1010)
|
||||
widths.Add(narrow);
|
||||
widths.Add(narrow);
|
||||
widths.Add(narrow);
|
||||
widths.Add(narrow);
|
||||
|
||||
for (int i = 0; i < digits.Length; i += 2)
|
||||
{
|
||||
var a = Pat(digits[i]);
|
||||
var b = Pat(digits[i + 1]);
|
||||
|
||||
for (int j = 0; j < 5; j++)
|
||||
{
|
||||
widths.Add(a[j] == 'w' ? wide : narrow); // bar
|
||||
widths.Add(b[j] == 'w' ? wide : narrow); // space
|
||||
}
|
||||
}
|
||||
|
||||
// Stop: wide bar, narrow space, narrow bar (1101)
|
||||
widths.Add(wide);
|
||||
widths.Add(narrow);
|
||||
widths.Add(narrow);
|
||||
|
||||
return widths;
|
||||
}
|
||||
|
||||
public static IReadOnlyList<int> EncodeCodabar(string value)
|
||||
{
|
||||
// Wikipedia table mapping (bars: 1=wide, spaces: 0=wide) for the standard symbol set.
|
||||
// Ensure start/stop are present; default to A ... B if missing.
|
||||
var raw = (value ?? string.Empty).Trim().ToUpperInvariant();
|
||||
if (raw.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("Codabar requires a non-empty value.");
|
||||
}
|
||||
|
||||
bool HasStartStop(string s)
|
||||
{
|
||||
if (s.Length < 2) return false;
|
||||
bool isStart = s[0] == 'A' || s[0] == 'B' || s[0] == 'C' || s[0] == 'D';
|
||||
bool isStop = s[s.Length - 1] == 'A' || s[s.Length - 1] == 'B' || s[s.Length - 1] == 'C' || s[s.Length - 1] == 'D';
|
||||
return isStart && isStop;
|
||||
}
|
||||
|
||||
var text = HasStartStop(raw) ? raw : $"A{raw}B";
|
||||
|
||||
static (string spaceBits, string barBits) Map(char ch) => ch switch
|
||||
{
|
||||
'0' => ("001", "0001"),
|
||||
'1' => ("001", "0010"),
|
||||
'2' => ("010", "0001"),
|
||||
'3' => ("100", "1000"),
|
||||
'4' => ("001", "0100"),
|
||||
'5' => ("001", "1000"),
|
||||
'6' => ("100", "0001"),
|
||||
'7' => ("100", "0010"),
|
||||
'8' => ("100", "0100"),
|
||||
'9' => ("010", "1000"),
|
||||
'-' => ("010", "0010"),
|
||||
'$' => ("010", "0100"),
|
||||
'.' => ("000", "0001"),
|
||||
'/' => ("000", "0010"),
|
||||
':' => ("000", "0100"),
|
||||
'+' => ("000", "1000"),
|
||||
'A' => ("011", "0100"),
|
||||
'B' => ("110", "0001"),
|
||||
'C' => ("011", "0001"),
|
||||
'D' => ("011", "0010"),
|
||||
_ => throw new ArgumentException($"Codabar does not support character '{ch}'.")
|
||||
};
|
||||
|
||||
const int narrow = 1;
|
||||
const int wide = 3;
|
||||
|
||||
var widths = new List<int>(text.Length * 8);
|
||||
|
||||
for (int idx = 0; idx < text.Length; idx++)
|
||||
{
|
||||
var ch = text[idx];
|
||||
var (spaceBits, barBits) = Map(ch);
|
||||
|
||||
// Bars: 4 bits, 1=wide
|
||||
int BarWidth(int pos) => barBits[pos] == '1' ? wide : narrow;
|
||||
// Spaces: 3 bits, 0=wide (per wikipedia mapping table)
|
||||
int SpaceWidth(int pos) => spaceBits[pos] == '0' ? wide : narrow;
|
||||
|
||||
widths.Add(BarWidth(0));
|
||||
widths.Add(SpaceWidth(0));
|
||||
widths.Add(BarWidth(1));
|
||||
widths.Add(SpaceWidth(1));
|
||||
widths.Add(BarWidth(2));
|
||||
widths.Add(SpaceWidth(2));
|
||||
widths.Add(BarWidth(3));
|
||||
|
||||
// Inter-character narrow space (except after last char).
|
||||
if (idx != text.Length - 1)
|
||||
{
|
||||
widths.Add(narrow);
|
||||
}
|
||||
}
|
||||
|
||||
return widths;
|
||||
}
|
||||
|
||||
static readonly string[] EanL = new[]
|
||||
{
|
||||
"0001101","0011001","0010011","0111101","0100011","0110001","0101111","0111011","0110111","0001011"
|
||||
};
|
||||
static readonly string[] EanG = new[]
|
||||
{
|
||||
"0100111","0110011","0011011","0100001","0011101","0111001","0000101","0010001","0001001","0010111"
|
||||
};
|
||||
static readonly string[] EanR = new[]
|
||||
{
|
||||
"1110010","1100110","1101100","1000010","1011100","1001110","1010000","1000100","1001000","1110100"
|
||||
};
|
||||
static readonly string[] Ean13Parity = new[]
|
||||
{
|
||||
"LLLLLL","LLGLGG","LLGGLG","LLGGGL","LGLLGG","LGGLLG","LGGGLL","LGLGLG","LGLGGL","LGGLGL"
|
||||
};
|
||||
|
||||
static int ComputeEanCheckDigit(string digitsWithoutCheck)
|
||||
{
|
||||
// digitsWithoutCheck is 7/11/12 digits depending on symbology.
|
||||
int sum = 0;
|
||||
bool weight3 = true;
|
||||
for (int i = digitsWithoutCheck.Length - 1; i >= 0; i--)
|
||||
{
|
||||
int d = digitsWithoutCheck[i] - '0';
|
||||
sum += weight3 ? d * 3 : d;
|
||||
weight3 = !weight3;
|
||||
}
|
||||
int mod = sum % 10;
|
||||
return (10 - mod) % 10;
|
||||
}
|
||||
|
||||
public static string EncodeEan13(string value, out string checksumText)
|
||||
{
|
||||
var digits = new string(value.Where(char.IsDigit).ToArray());
|
||||
if (digits.Length != 12 && digits.Length != 13)
|
||||
{
|
||||
throw new ArgumentException("EAN-13 requires 12 or 13 digits.");
|
||||
}
|
||||
|
||||
if (digits.Length == 12)
|
||||
{
|
||||
var check = ComputeEanCheckDigit(digits);
|
||||
digits += check.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
else
|
||||
{
|
||||
var expected = ComputeEanCheckDigit(digits[..12]);
|
||||
if (digits[12] - '0' != expected)
|
||||
{
|
||||
throw new ArgumentException("Invalid EAN-13 check digit.");
|
||||
}
|
||||
}
|
||||
|
||||
checksumText = digits[^1].ToString();
|
||||
|
||||
int first = digits[0] - '0';
|
||||
var parity = Ean13Parity[first];
|
||||
|
||||
var sb = new StringBuilder(95);
|
||||
sb.Append("101");
|
||||
// digits 2..7 (index 1..6)
|
||||
for (int i = 1; i <= 6; i++)
|
||||
{
|
||||
int d = digits[i] - '0';
|
||||
sb.Append(parity[i - 1] == 'G' ? EanG[d] : EanL[d]);
|
||||
}
|
||||
sb.Append("01010");
|
||||
for (int i = 7; i <= 12; i++)
|
||||
{
|
||||
int d = digits[i] - '0';
|
||||
sb.Append(EanR[d]);
|
||||
}
|
||||
sb.Append("101");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static string EncodeUpcA(string value, out string checksumText)
|
||||
{
|
||||
var digits = new string(value.Where(char.IsDigit).ToArray());
|
||||
if (digits.Length != 11 && digits.Length != 12)
|
||||
{
|
||||
throw new ArgumentException("UPC-A requires 11 or 12 digits.");
|
||||
}
|
||||
|
||||
if (digits.Length == 11)
|
||||
{
|
||||
var check = ComputeEanCheckDigit(digits);
|
||||
digits += check.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
else
|
||||
{
|
||||
var expected = ComputeEanCheckDigit(digits[..11]);
|
||||
if (digits[11] - '0' != expected)
|
||||
{
|
||||
throw new ArgumentException("Invalid UPC-A check digit.");
|
||||
}
|
||||
}
|
||||
|
||||
checksumText = digits[^1].ToString();
|
||||
|
||||
var sb = new StringBuilder(95);
|
||||
sb.Append("101");
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
int d = digits[i] - '0';
|
||||
sb.Append(EanL[d]);
|
||||
}
|
||||
sb.Append("01010");
|
||||
for (int i = 6; i < 12; i++)
|
||||
{
|
||||
int d = digits[i] - '0';
|
||||
sb.Append(EanR[d]);
|
||||
}
|
||||
sb.Append("101");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static string EncodeEan8(string value, out string checksumText)
|
||||
{
|
||||
var digits = new string(value.Where(char.IsDigit).ToArray());
|
||||
if (digits.Length != 7 && digits.Length != 8)
|
||||
{
|
||||
throw new ArgumentException("EAN-8 requires 7 or 8 digits.");
|
||||
}
|
||||
|
||||
if (digits.Length == 7)
|
||||
{
|
||||
var check = ComputeEanCheckDigit(digits);
|
||||
digits += check.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
else
|
||||
{
|
||||
var expected = ComputeEanCheckDigit(digits[..7]);
|
||||
if (digits[7] - '0' != expected)
|
||||
{
|
||||
throw new ArgumentException("Invalid EAN-8 check digit.");
|
||||
}
|
||||
}
|
||||
|
||||
checksumText = digits[^1].ToString();
|
||||
|
||||
var sb = new StringBuilder(67);
|
||||
sb.Append("101");
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
int d = digits[i] - '0';
|
||||
sb.Append(EanL[d]);
|
||||
}
|
||||
sb.Append("01010");
|
||||
for (int i = 4; i < 8; i++)
|
||||
{
|
||||
int d = digits[i] - '0';
|
||||
sb.Append(EanR[d]);
|
||||
}
|
||||
sb.Append("101");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static string EncodeIsbnAsEan13(string value, out string checksumText)
|
||||
{
|
||||
var raw = new string(value.Where(char.IsLetterOrDigit).ToArray()).ToUpperInvariant();
|
||||
if (raw.Length == 10)
|
||||
{
|
||||
// ISBN-10 -> EAN-13: 978 + first 9 digits + EAN check
|
||||
var core = raw[..9];
|
||||
if (!core.All(char.IsDigit)) throw new ArgumentException("Invalid ISBN-10.");
|
||||
return EncodeEan13("978" + core, out checksumText);
|
||||
}
|
||||
if (raw.Length == 13)
|
||||
{
|
||||
if (!raw.All(char.IsDigit)) throw new ArgumentException("Invalid ISBN-13.");
|
||||
return EncodeEan13(raw, out checksumText);
|
||||
}
|
||||
|
||||
throw new ArgumentException("ISBN requires 10 or 13 characters.");
|
||||
}
|
||||
|
||||
public static string EncodeIssnAsEan13(string value, out string checksumText)
|
||||
{
|
||||
// ISSN EAN-13: 977 + first 7 digits + 00 + EAN check
|
||||
var raw = new string(value.Where(char.IsLetterOrDigit).ToArray()).ToUpperInvariant();
|
||||
if (raw.Length != 8) throw new ArgumentException("ISSN requires 8 characters.");
|
||||
var core = raw[..7];
|
||||
if (!core.All(char.IsDigit)) throw new ArgumentException("Invalid ISSN.");
|
||||
return EncodeEan13("977" + core + "00", out checksumText);
|
||||
}
|
||||
|
||||
public static (IReadOnlyList<RadzenBarcode.BarcodeRect> bars, double vbWidth) EncodePharmacode(string value, double barHeight, int quietZone)
|
||||
{
|
||||
// Pharmacode one-track: numbers 3..131070
|
||||
var digits = new string(value.Where(char.IsDigit).ToArray());
|
||||
if (!int.TryParse(digits, NumberStyles.None, CultureInfo.InvariantCulture, out var n))
|
||||
{
|
||||
throw new ArgumentException("Pharmacode requires a numeric value.");
|
||||
}
|
||||
if (n < 3 || n > 131070)
|
||||
{
|
||||
throw new ArgumentException("Pharmacode value must be in range 3..131070.");
|
||||
}
|
||||
|
||||
var bars = new List<(int width, bool isWide)>();
|
||||
while (n > 0)
|
||||
{
|
||||
if (n % 2 == 0)
|
||||
{
|
||||
bars.Add((2, true));
|
||||
n = (n - 2) / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
bars.Add((1, false));
|
||||
n = (n - 1) / 2;
|
||||
}
|
||||
}
|
||||
bars.Reverse();
|
||||
|
||||
double x = Math.Max(0, quietZone);
|
||||
var rects = new List<RadzenBarcode.BarcodeRect>(bars.Count);
|
||||
foreach (var b in bars)
|
||||
{
|
||||
rects.Add(new RadzenBarcode.BarcodeRect(x, 0, b.width, barHeight));
|
||||
x += b.width + 1; // 1 module gap
|
||||
}
|
||||
|
||||
var vbWidth = x + Math.Max(0, quietZone);
|
||||
if (vbWidth <= 0) vbWidth = 1;
|
||||
return (rects, vbWidth);
|
||||
}
|
||||
|
||||
// POSTNET digit encoding from Wikipedia (weights 7,4,2,1,0). 1=full bar, 0=half bar.
|
||||
static readonly Dictionary<char, string> PostnetDigitBits = new Dictionary<char, string>()
|
||||
{
|
||||
['0'] = "11000",
|
||||
['1'] = "00011",
|
||||
['2'] = "00101",
|
||||
['3'] = "00110",
|
||||
['4'] = "01001",
|
||||
['5'] = "01010",
|
||||
['6'] = "01100",
|
||||
['7'] = "10001",
|
||||
['8'] = "10010",
|
||||
['9'] = "10100",
|
||||
};
|
||||
|
||||
public static (IReadOnlyList<RadzenBarcode.BarcodeRect> bars, double vbWidth) EncodePostnet(string value, double barHeight, int quietZone, out string checksumText)
|
||||
{
|
||||
var digits = new string(value.Where(char.IsDigit).ToArray());
|
||||
if ((digits.Length != 5 && digits.Length != 9 && digits.Length != 11))
|
||||
{
|
||||
throw new ArgumentException("POSTNET requires 5, 9, or 11 digits (ZIP / ZIP+4 / Delivery Point).");
|
||||
}
|
||||
|
||||
int sum = digits.Sum(ch => ch - '0');
|
||||
int check = (10 - (sum % 10)) % 10;
|
||||
checksumText = check.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
var payload = digits + checksumText;
|
||||
|
||||
double fullH = barHeight;
|
||||
double halfH = barHeight / 2.0;
|
||||
double halfY = fullH - halfH;
|
||||
|
||||
double x = Math.Max(0, quietZone);
|
||||
var rects = new List<RadzenBarcode.BarcodeRect>();
|
||||
|
||||
// Start frame bar (full)
|
||||
rects.Add(new RadzenBarcode.BarcodeRect(x, 0, 1, fullH));
|
||||
x += 2; // bar(1) + space(1)
|
||||
|
||||
foreach (var ch in payload)
|
||||
{
|
||||
var bits = PostnetDigitBits[ch];
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
bool full = bits[i] == '1';
|
||||
rects.Add(full
|
||||
? new RadzenBarcode.BarcodeRect(x, 0, 1, fullH)
|
||||
: new RadzenBarcode.BarcodeRect(x, halfY, 1, halfH));
|
||||
x += 2;
|
||||
}
|
||||
}
|
||||
|
||||
// Stop frame bar (full)
|
||||
rects.Add(new RadzenBarcode.BarcodeRect(x, 0, 1, fullH));
|
||||
x += 1;
|
||||
|
||||
var vbWidth = x + Math.Max(0, quietZone);
|
||||
if (vbWidth <= 0) vbWidth = 1;
|
||||
return (rects, vbWidth);
|
||||
}
|
||||
|
||||
// RM4SCC patterns and symbol matrix from Wikipedia:
|
||||
// Top patterns (values 1..6) and Bottom patterns (values 1..6) are:
|
||||
// 1=0011, 2=0101, 3=0110, 4=1001, 5=1010, 6=1100
|
||||
static readonly string[] Rm4Patterns = new[] { "0011", "0101", "0110", "1001", "1010", "1100" };
|
||||
|
||||
// Matrix indexed by [topValue-1, bottomValue-1]
|
||||
static readonly char[,] Rm4Matrix = new char[6, 6]
|
||||
{
|
||||
{ '0', '1', '2', '3', '4', '5' },
|
||||
{ '6', '7', '8', '9', 'A', 'B' },
|
||||
{ 'C', 'D', 'E', 'F', 'G', 'H' },
|
||||
{ 'I', 'J', 'K', 'L', 'M', 'N' },
|
||||
{ 'O', 'P', 'Q', 'R', 'S', 'T' },
|
||||
{ 'U', 'V', 'W', 'X', 'Y', 'Z' }
|
||||
};
|
||||
|
||||
static readonly Dictionary<char, (string top, string bottom)> Rm4CharToBits = BuildRm4CharToBits();
|
||||
|
||||
static Dictionary<char, (string top, string bottom)> BuildRm4CharToBits()
|
||||
{
|
||||
var dict = new Dictionary<char, (string top, string bottom)>();
|
||||
for (int r = 0; r < 6; r++)
|
||||
{
|
||||
for (int c = 0; c < 6; c++)
|
||||
{
|
||||
dict[Rm4Matrix[r, c]] = (Rm4Patterns[r], Rm4Patterns[c]);
|
||||
}
|
||||
}
|
||||
return dict;
|
||||
}
|
||||
|
||||
public static (IReadOnlyList<RadzenBarcode.BarcodeRect> bars, double vbWidth) EncodeRm4scc(string value, double barHeight, int quietZone, out string checksumText)
|
||||
{
|
||||
var text = new string(value.Where(char.IsLetterOrDigit).ToArray()).ToUpperInvariant();
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
throw new ArgumentException("RM4SCC requires alphanumeric input.");
|
||||
}
|
||||
|
||||
// Only symbols present in the Wikipedia table (0-9, A-Z).
|
||||
foreach (var ch in text)
|
||||
{
|
||||
if (!(ch is >= '0' and <= '9') && !(ch is >= 'A' and <= 'Z'))
|
||||
{
|
||||
throw new ArgumentException($"RM4SCC does not support character '{ch}'.");
|
||||
}
|
||||
}
|
||||
|
||||
// Compute checksum per Wikipedia: sum top values and bottom values separately, mod 6, 0 => 6.
|
||||
int sumTop = 0;
|
||||
int sumBottom = 0;
|
||||
foreach (var ch in text)
|
||||
{
|
||||
if (!Rm4CharToBits.TryGetValue(ch, out var bits))
|
||||
{
|
||||
throw new ArgumentException($"RM4SCC does not support character '{ch}'.");
|
||||
}
|
||||
int t = Array.IndexOf(Rm4Patterns, bits.top) + 1;
|
||||
int b = Array.IndexOf(Rm4Patterns, bits.bottom) + 1;
|
||||
sumTop += t;
|
||||
sumBottom += b;
|
||||
}
|
||||
|
||||
int topVal = sumTop % 6;
|
||||
topVal = topVal == 0 ? 6 : topVal;
|
||||
int bottomVal = sumBottom % 6;
|
||||
bottomVal = bottomVal == 0 ? 6 : bottomVal;
|
||||
|
||||
var checkChar = Rm4Matrix[topVal - 1, bottomVal - 1];
|
||||
|
||||
checksumText = checkChar.ToString();
|
||||
|
||||
// Encode start + data + checksum + stop.
|
||||
// Start/stop are single bars; we use ascender for start and descender for stop.
|
||||
double h = barHeight;
|
||||
double third = h / 3.0;
|
||||
double trackerY = third;
|
||||
double trackerH = third;
|
||||
|
||||
RadzenBarcode.BarcodeRect BarRect(double x, bool top, bool bottom)
|
||||
{
|
||||
return (top, bottom) switch
|
||||
{
|
||||
(false, false) => new RadzenBarcode.BarcodeRect(x, trackerY, 1, trackerH), // tracker
|
||||
(true, false) => new RadzenBarcode.BarcodeRect(x, 0, 1, trackerY + trackerH), // ascender (top + tracker)
|
||||
(false, true) => new RadzenBarcode.BarcodeRect(x, trackerY, 1, h - trackerY), // descender (tracker + bottom)
|
||||
(true, true) => new RadzenBarcode.BarcodeRect(x, 0, 1, h), // full
|
||||
};
|
||||
}
|
||||
|
||||
double xPos = Math.Max(0, quietZone);
|
||||
var rects = new List<RadzenBarcode.BarcodeRect>();
|
||||
|
||||
// Start bar (ascender)
|
||||
rects.Add(BarRect(xPos, top: true, bottom: false));
|
||||
xPos += 2;
|
||||
|
||||
void AddSymbol(char ch)
|
||||
{
|
||||
var (topBits, bottomBits) = Rm4CharToBits[ch];
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
bool top = topBits[i] == '1';
|
||||
bool bottom = bottomBits[i] == '1';
|
||||
rects.Add(BarRect(xPos, top, bottom));
|
||||
xPos += 2;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var ch in text) AddSymbol(ch);
|
||||
AddSymbol(checkChar);
|
||||
|
||||
// Stop bar (descender)
|
||||
rects.Add(BarRect(xPos, top: false, bottom: true));
|
||||
xPos += 1;
|
||||
|
||||
var vbWidth = xPos + Math.Max(0, quietZone);
|
||||
if (vbWidth <= 0) vbWidth = 1;
|
||||
return (rects, vbWidth);
|
||||
}
|
||||
|
||||
public static string EncodeMsiPlessey(string value, out string checksumText)
|
||||
{
|
||||
var digits = new string(value.Where(char.IsDigit).ToArray());
|
||||
if (digits.Length == 0) throw new ArgumentException("Plessey (MSI) requires numeric input.");
|
||||
|
||||
// Mod 10 (Luhn) check digit (common)
|
||||
int check = ComputeLuhnCheckDigit(digits);
|
||||
checksumText = check.ToString(CultureInfo.InvariantCulture);
|
||||
digits += checksumText;
|
||||
|
||||
// MSI map from Wikipedia.
|
||||
static string DigitMap(char d) => d switch
|
||||
{
|
||||
'0' => "100100100100",
|
||||
'1' => "100100100110",
|
||||
'2' => "100100110100",
|
||||
'3' => "100100110110",
|
||||
'4' => "100110100100",
|
||||
'5' => "100110100110",
|
||||
'6' => "100110110100",
|
||||
'7' => "100110110110",
|
||||
'8' => "110100100100",
|
||||
'9' => "110100100110",
|
||||
_ => throw new ArgumentException("MSI requires numeric input.")
|
||||
};
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.Append("110"); // start
|
||||
foreach (var ch in digits) sb.Append(DigitMap(ch));
|
||||
sb.Append("1001"); // stop
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
static int ComputeLuhnCheckDigit(string digits)
|
||||
{
|
||||
int sum = 0;
|
||||
bool dbl = true;
|
||||
for (int i = digits.Length - 1; i >= 0; i--)
|
||||
{
|
||||
int d = digits[i] - '0';
|
||||
if (dbl)
|
||||
{
|
||||
d *= 2;
|
||||
if (d > 9) d -= 9;
|
||||
}
|
||||
sum += d;
|
||||
dbl = !dbl;
|
||||
}
|
||||
return (10 - (sum % 10)) % 10;
|
||||
}
|
||||
|
||||
public static IReadOnlyList<int> EncodeTelepen(string value, out string checksumText)
|
||||
{
|
||||
// Telepen algorithm per Wikipedia: even parity bytes, little-endian bit order, modulo-127 checksum.
|
||||
var bytes = Encoding.ASCII.GetBytes(value ?? string.Empty);
|
||||
|
||||
int sum = 0;
|
||||
for (int i = 0; i < bytes.Length; i++)
|
||||
{
|
||||
if (bytes[i] > 0x7F) throw new ArgumentException("Telepen supports ASCII only.");
|
||||
sum = (sum + bytes[i]) % 127;
|
||||
}
|
||||
|
||||
int check = (127 - sum) % 127;
|
||||
checksumText = check.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
// Build payload: start '_' + data + checksum byte + stop 'z'
|
||||
var payload = new List<byte>(bytes.Length + 3) { (byte)'_' };
|
||||
payload.AddRange(bytes);
|
||||
payload.Add((byte)check);
|
||||
payload.Add((byte)'z');
|
||||
|
||||
// Build bit stream LSB-first with even parity bit as MSB.
|
||||
var bitStream = new List<int>(payload.Count * 8);
|
||||
foreach (var b0 in payload)
|
||||
{
|
||||
int b = b0 & 0x7F;
|
||||
int ones = CountBits(b);
|
||||
int parityBit = (ones % 2 == 0) ? 0 : 1; // make total even
|
||||
int byteWithParity = b | (parityBit << 7);
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
bitStream.Add((byteWithParity >> i) & 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Encode bit stream into bar/space widths (narrow=1, wide=3).
|
||||
// We produce alternating bar/space widths list, starting with bar.
|
||||
const int narrow = 1;
|
||||
const int wide = 3;
|
||||
var widths = new List<int>();
|
||||
|
||||
int idx = 0;
|
||||
while (idx < bitStream.Count)
|
||||
{
|
||||
if (bitStream[idx] == 1)
|
||||
{
|
||||
// "1" => narrow bar, narrow space
|
||||
widths.Add(narrow);
|
||||
widths.Add(narrow);
|
||||
idx += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// starts with 0
|
||||
if (idx + 1 < bitStream.Count && bitStream[idx + 1] == 0)
|
||||
{
|
||||
// "00" => wide bar, narrow space
|
||||
widths.Add(wide);
|
||||
widths.Add(narrow);
|
||||
idx += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (idx + 2 < bitStream.Count && bitStream[idx] == 0 && bitStream[idx + 1] == 1 && bitStream[idx + 2] == 0)
|
||||
{
|
||||
// "010" => wide bar, wide space
|
||||
widths.Add(wide);
|
||||
widths.Add(wide);
|
||||
idx += 3;
|
||||
continue;
|
||||
}
|
||||
|
||||
// General block 0 1^k 0 with k>=2
|
||||
if (idx + 3 >= bitStream.Count || bitStream[idx] != 0 || bitStream[idx + 1] != 1)
|
||||
{
|
||||
throw new ArgumentException("Invalid Telepen bit stream.");
|
||||
}
|
||||
|
||||
int j = idx + 1;
|
||||
while (j < bitStream.Count && bitStream[j] == 1) j++;
|
||||
if (j >= bitStream.Count || bitStream[j] != 0)
|
||||
{
|
||||
throw new ArgumentException("Invalid Telepen bit stream.");
|
||||
}
|
||||
|
||||
int k = j - (idx + 1); // number of 1s
|
||||
if (k < 2) throw new ArgumentException("Invalid Telepen bit stream.");
|
||||
|
||||
// leading "01" => narrow bar, wide space
|
||||
widths.Add(narrow);
|
||||
widths.Add(wide);
|
||||
|
||||
// middle extra 1s (k-2) => narrow bar, narrow space
|
||||
for (int m = 0; m < k - 2; m++)
|
||||
{
|
||||
widths.Add(narrow);
|
||||
widths.Add(narrow);
|
||||
}
|
||||
|
||||
// trailing "10" => narrow bar, wide space
|
||||
widths.Add(narrow);
|
||||
widths.Add(wide);
|
||||
|
||||
idx = j + 1;
|
||||
}
|
||||
|
||||
return widths;
|
||||
}
|
||||
|
||||
static int CountBits(int v)
|
||||
{
|
||||
int c = 0;
|
||||
while (v != 0)
|
||||
{
|
||||
c += v & 1;
|
||||
v >>= 1;
|
||||
}
|
||||
return c;
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
1227
Radzen.Blazor/RadzenBarcodeEncoder.cs
Normal file
1227
Radzen.Blazor/RadzenBarcodeEncoder.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -55,9 +55,9 @@ namespace Radzen.Blazor
|
||||
/// <summary>
|
||||
/// Gets or sets the color scheme used to assign colors to chart series.
|
||||
/// Determines the palette of colors applied sequentially to each series when series-specific colors are not set.
|
||||
/// Available schemes include Pastel, Palette (default), Monochrome, and custom color schemes.
|
||||
/// Available schemes include Pastel (default), Palette, Monochrome, and custom color schemes.
|
||||
/// </summary>
|
||||
/// <value>The color scheme. Default uses the Palette scheme.</value>
|
||||
/// <value>The color scheme. Default uses the Pastel scheme.</value>
|
||||
[Parameter]
|
||||
public ColorScheme ColorScheme { get; set; }
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -189,6 +189,8 @@ else if (Filter != null)
|
||||
}
|
||||
Filter.FilterValue = null;
|
||||
|
||||
Filter.Type = property.FilterPropertyType;
|
||||
|
||||
var defaultOperator = typeof(System.Collections.IEnumerable).IsAssignableFrom(property.FilterPropertyType) ? FilterOperator.Contains : default(FilterOperator);
|
||||
|
||||
if (property.GetFilterOperators().Any(o => o == property.FilterOperator))
|
||||
|
||||
@@ -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>
|
||||
@@ -502,9 +528,9 @@
|
||||
else
|
||||
{
|
||||
<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))
|
||||
@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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
@@ -3576,6 +3590,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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1310,7 +1310,7 @@ namespace Radzen.Blazor
|
||||
|
||||
internal bool HasCustomFilter()
|
||||
{
|
||||
return GetFilterOperator() == FilterOperator.Custom && GetCustomFilterExpression() != null;
|
||||
return GetFilterOperator() == FilterOperator.Custom && !string.IsNullOrEmpty(GetCustomFilterExpression());
|
||||
}
|
||||
|
||||
internal bool HasActiveFilter()
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 ?? false))">
|
||||
<span class="@(Grid.ExpandedGroupItemStyle(this, Grid.allGroupsExpanded != null ? Grid.allGroupsExpanded : rowArgs?.Item1.Expanded ?? false))"></span>
|
||||
</a>
|
||||
@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>
|
||||
</button>
|
||||
}
|
||||
</td>
|
||||
}
|
||||
<td colspan="@(TotalColumnCount + (Grid?.Groups.Count ?? 0) - 1 - Group.Level + ((Grid?.Template != null && Grid?.ShowExpandColumn == true) ? 1 : 0))">
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
@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 +32,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>
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -175,7 +196,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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)!;
|
||||
|
||||
@@ -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()" />
|
||||
}
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -100,12 +100,16 @@ namespace Radzen.Blazor
|
||||
if (AllowFiltering && key.Length == 1 && JSRuntime != null)
|
||||
{
|
||||
await JSRuntime.InvokeVoidAsync("Radzen.focusElement", SearchID);
|
||||
await JSRuntime.InvokeAsync<string>("Radzen.setInputValue", search, key);
|
||||
if (JSRuntime is not IJSInProcessRuntime)
|
||||
{
|
||||
await JSRuntime.InvokeAsync<string>("Radzen.setInputValue", search, key);
|
||||
}
|
||||
}
|
||||
|
||||
await OnKeyPress(args, false);
|
||||
}
|
||||
|
||||
|
||||
private bool visibleChanged;
|
||||
private bool disabledChanged;
|
||||
private bool firstRender = true;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,27 @@
|
||||
@using System.Collections.Generic
|
||||
@using System.Timers
|
||||
@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() />
|
||||
@@ -103,12 +110,29 @@
|
||||
|
||||
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()
|
||||
{
|
||||
timer?.Stop();
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
{
|
||||
@@ -1618,15 +1634,6 @@ namespace Radzen.Blazor
|
||||
|
||||
sortedField.SetSortOrderInternal(nextSortOrder);
|
||||
|
||||
// Clear other column sorts if single column sorting
|
||||
if (sortedField.GetSortOrder() != null)
|
||||
{
|
||||
foreach (var col in allFields.Where(c => c != sortedField))
|
||||
{
|
||||
col.SetSortOrderInternal(null);
|
||||
}
|
||||
}
|
||||
|
||||
await Reload();
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.JSInterop;
|
||||
using Radzen.Blazor.Rendering;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Radzen.Blazor
|
||||
{
|
||||
@@ -88,19 +89,19 @@ namespace Radzen.Blazor
|
||||
/// <value>The size in CSS units. Default is "100%".</value>
|
||||
[Parameter] public string Size { get; set; } = "100%";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color of the QR code modules (the dark squares/dots).
|
||||
/// Supports any valid CSS color. Use high contrast with background for best scanability.
|
||||
/// </summary>
|
||||
/// <value>The foreground color. Default is "#000" (black).</value>
|
||||
[Parameter] public string Foreground { get; set; } = "#000";
|
||||
/// <summary>
|
||||
/// Gets or sets the color of the QR code modules (the dark squares/dots).
|
||||
/// Supports any valid CSS color. Use high contrast with background for best scanability.
|
||||
/// </summary>
|
||||
/// <value>The foreground color. Default is "#000" (black).</value>
|
||||
[Parameter] public string Foreground { get; set; } = "#000";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the background color of the QR code.
|
||||
/// Should contrast well with the foreground color for reliable scanning.
|
||||
/// </summary>
|
||||
/// <value>The background color. Default is "#FFF" (white).</value>
|
||||
[Parameter] public string Background { get; set; } = "#FFF";
|
||||
/// <summary>
|
||||
/// Gets or sets the background color of the QR code.
|
||||
/// Should contrast well with the foreground color for reliable scanning.
|
||||
/// </summary>
|
||||
/// <value>The background color. Default is "#FFF" (white).</value>
|
||||
[Parameter] public string Background { get; set; } = "#FFF";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the visual shape of the QR code modules (data squares).
|
||||
@@ -156,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;
|
||||
@@ -163,6 +186,21 @@ namespace Radzen.Blazor
|
||||
bool inBL = r >= n - 7 && c < 7;
|
||||
return inTL || inTR || inBL;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the SVG markup of the rendered QR code as a string.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="Task{String}"/> representing the asynchronous operation. The task result contains the SVG markup of the QR code.
|
||||
/// </returns>
|
||||
public async Task<string> ToSvg()
|
||||
{
|
||||
if (JSRuntime != null)
|
||||
{
|
||||
return await JSRuntime.InvokeAsync<string>("Radzen.outerHTML", Element);
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Radzen.Blazor.Rendering;
|
||||
using System.Text;
|
||||
|
||||
namespace Radzen.Blazor;
|
||||
@@ -29,7 +30,10 @@ public enum RadzenQREcc
|
||||
High
|
||||
}
|
||||
|
||||
internal static class RadzenQREncoder
|
||||
/// <summary>
|
||||
/// Provides QR encoding utilities for UTF-8 strings and raw bytes.
|
||||
/// </summary>
|
||||
public static class RadzenQREncoder
|
||||
{
|
||||
/// <summary>Encode a UTF-8 string into a QR module matrix.</summary>
|
||||
public static bool[,] EncodeUtf8(string value, RadzenQREcc ecc, int minVersion = 1, int maxVersion = 40)
|
||||
@@ -93,6 +97,8 @@ internal static class RadzenQREncoder
|
||||
/// <summary>Encode raw bytes into a QR module matrix.</summary>
|
||||
public static bool[,] EncodeBytes(byte[] data, RadzenQREcc ecc = RadzenQREcc.Medium, int minVersion = 1, int maxVersion = 40)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(data);
|
||||
|
||||
if (minVersion < 1 || maxVersion > 40 || minVersion > maxVersion)
|
||||
throw new ArgumentOutOfRangeException(nameof(minVersion), "Version range must be within 1..40");
|
||||
|
||||
@@ -152,24 +158,93 @@ internal static class RadzenQREncoder
|
||||
}
|
||||
|
||||
/// <summary>Render a module matrix into an SVG string with a 4-module quiet zone.</summary>
|
||||
public static string ToSvg(bool[,] modules, int moduleSize = 8, string foreground = "#000000", string background = "#FFFFFF")
|
||||
public static string ToSvg(
|
||||
bool[,] modules,
|
||||
int moduleSize = 8,
|
||||
string foreground = "#000000",
|
||||
string background = "#FFFFFF",
|
||||
QRCodeModuleShape moduleShape = QRCodeModuleShape.Square,
|
||||
QRCodeEyeShape eyeShape = QRCodeEyeShape.Square,
|
||||
QRCodeEyeShape? eyeShapeTopLeft = null,
|
||||
QRCodeEyeShape? eyeShapeTopRight = null,
|
||||
QRCodeEyeShape? eyeShapeBottomLeft = null,
|
||||
string? eyeColor = null,
|
||||
string? eyeColorTopLeft = null,
|
||||
string? eyeColorTopRight = null,
|
||||
string? eyeColorBottomLeft = null,
|
||||
string? image = null,
|
||||
string imageBackground = "#FFF")
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(modules);
|
||||
ArgumentNullException.ThrowIfNull(foreground);
|
||||
ArgumentNullException.ThrowIfNull(background);
|
||||
ArgumentNullException.ThrowIfNull(imageBackground);
|
||||
|
||||
int n = modules.GetLength(0);
|
||||
int vb = n + 8; // 4 modules of quiet zone on each side
|
||||
int px = vb * moduleSize;
|
||||
|
||||
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, "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++)
|
||||
{
|
||||
for (int c = 0; c < n; c++)
|
||||
{
|
||||
if (modules[r, c])
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{c + 4}\" y=\"{r + 4}\" width=\"1\" height=\"1\" fill=\"{foreground}\"/>");
|
||||
if (!modules[r, c]) continue;
|
||||
if (IsFinderCell(r, c, n)) continue;
|
||||
|
||||
var x = c + 4;
|
||||
var y = r + 4;
|
||||
|
||||
if (moduleShape == QRCodeModuleShape.Circle)
|
||||
{
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<circle cx=\"{Format(x + 0.5)}\" cy=\"{Format(y + 0.5)}\" r=\"0.5\" fill=\"{foreground}\"/>");
|
||||
}
|
||||
else if (moduleShape == QRCodeModuleShape.Rounded)
|
||||
{
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x)}\" y=\"{Format(y)}\" width=\"1\" height=\"1\" rx=\"0.25\" ry=\"0.25\" fill=\"{foreground}\"/>");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x)}\" y=\"{Format(y)}\" width=\"1\" height=\"1\" fill=\"{foreground}\"/>");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(image))
|
||||
{
|
||||
const double imageSizePercent = 20;
|
||||
const double imagePaddingModules = 1.0;
|
||||
const double imageCornerRadius = 0.75;
|
||||
const double imageBackgroundOpacity = 1.0;
|
||||
|
||||
double pct = Math.Clamp(imageSizePercent, 5, 60);
|
||||
double boxModules = Math.Max(5, Math.Round(n * (pct / 100.0)));
|
||||
double pad = Math.Max(0, imagePaddingModules);
|
||||
|
||||
double cutoutW = boxModules + 2 * pad;
|
||||
double cutoutH = boxModules + 2 * pad;
|
||||
|
||||
double centerX = vb / 2.0;
|
||||
double centerY = vb / 2.0;
|
||||
|
||||
double cutoutX = centerX - cutoutW / 2.0;
|
||||
double cutoutY = centerY - cutoutH / 2.0;
|
||||
double imgX = centerX - boxModules / 2.0;
|
||||
double imgY = centerY - boxModules / 2.0;
|
||||
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(cutoutX)}\" y=\"{Format(cutoutY)}\" width=\"{Format(cutoutW)}\" height=\"{Format(cutoutH)}\" rx=\"{Format(imageCornerRadius)}\" ry=\"{Format(imageCornerRadius)}\" fill=\"{imageBackground}\" fill-opacity=\"{Format(Math.Clamp(imageBackgroundOpacity, 0, 1))}\"/>");
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<image x=\"{Format(imgX)}\" y=\"{Format(imgY)}\" width=\"{Format(boxModules)}\" height=\"{Format(boxModules)}\" preserveAspectRatio=\"xMidYMid meet\" href=\"{image}\"/>");
|
||||
}
|
||||
|
||||
sb.Append("</svg>");
|
||||
return sb.ToString();
|
||||
}
|
||||
@@ -1065,4 +1140,69 @@ internal static class RadzenQREncoder
|
||||
for (int i = len - 1; i >= 0; i--) this.Add((val >> i) & 1);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsFinderCell(int r, int c, int n)
|
||||
{
|
||||
bool inTL = r < 7 && c < 7;
|
||||
bool inTR = r < 7 && c >= n - 7;
|
||||
bool inBL = r >= n - 7 && c < 7;
|
||||
return inTL || inTR || inBL;
|
||||
}
|
||||
|
||||
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, $"<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, $"<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, $"<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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -156,7 +156,7 @@ namespace Radzen.Blazor
|
||||
/// <summary>
|
||||
/// Specifies the property of <typeparamref name="TItem" /> which will set <see cref="AppointmentData.Text" />.
|
||||
/// </summary>
|
||||
/// <value>The name of the property. Must be a <c>DateTime</c> property.</value>
|
||||
/// <value>The name of the property. Must be a <c>string</c> property.</value>
|
||||
[Parameter]
|
||||
public string? TextProperty { get; set; }
|
||||
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
131
Radzen.Blazor/RadzenSpiderChart.razor
Normal file
131
Radzen.Blazor/RadzenSpiderChart.razor
Normal file
@@ -0,0 +1,131 @@
|
||||
@inherits RadzenComponent
|
||||
@using Radzen.Blazor.Rendering
|
||||
|
||||
@if (Visible)
|
||||
{
|
||||
<CascadingValue Value="@this">
|
||||
@ChildContent
|
||||
</CascadingValue>
|
||||
|
||||
<CascadingValue Value="@this" IsFixed="true">
|
||||
<div @ref="@Element" class="@GetCssClass()" style="@Style" @attributes="Attributes">
|
||||
@{
|
||||
var wrapperClass = ClassList.Create("rz-spider-chart-wrapper")
|
||||
.Add($"rz-spider-legend-{Legend?.Position}".ToLowerInvariant(), Legend?.Visible == true)
|
||||
.ToString();
|
||||
}
|
||||
|
||||
<div class="@wrapperClass" @onmouseleave="OnChartMouseLeave">
|
||||
@if (Width != null && Height != null && Series.Count > 0)
|
||||
{
|
||||
<svg viewBox="@($"0 0 {Width.Value.ToInvariantString()} {Height.Value.ToInvariantString()}")">
|
||||
<g class="rz-spider-chart-grid">
|
||||
@* Diagonal lines first (bottom layer) *@
|
||||
@foreach (var category in GetAllCategories())
|
||||
{
|
||||
var index = GetCategoryIndex(category);
|
||||
var (x1, y1) = GetPoint(index, MinValue);
|
||||
var (x2, y2) = GetPoint(index, MaxValue);
|
||||
<line x1="@x1.ToInvariantString()" y1="@y1.ToInvariantString()" x2="@x2.ToInvariantString()" y2="@y2.ToInvariantString()" class="rz-spider-chart-grid-line" />
|
||||
}
|
||||
|
||||
@* Grid shapes second (top layer) *@
|
||||
@for (int i = 1; i <= 5; i++)
|
||||
{
|
||||
@if (GridShape == SpiderChartGridShape.Circular)
|
||||
{
|
||||
var centerX = Width.Value / 2;
|
||||
var centerY = Height.Value / 2;
|
||||
var radius = Math.Min(centerX, centerY) * 0.8 * (i / 5.0);
|
||||
<circle cx="@centerX.ToInvariantString()" cy="@centerY.ToInvariantString()" r="@radius.ToInvariantString()" class="rz-spider-chart-grid-circle" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<path d="@GetGridPath(i / 5.0)" class="rz-spider-chart-grid-polygon" />
|
||||
}
|
||||
}
|
||||
</g>
|
||||
|
||||
<g class="rz-spider-chart-areas">
|
||||
@foreach (var series in VisibleSeries)
|
||||
{
|
||||
var path = GetSeriesPath(series);
|
||||
var isHovered = HoveredSeries == series;
|
||||
var shouldShow = !IsLegendHover || HoveredSeries == null || HoveredSeries == series;
|
||||
var localSeries = series;
|
||||
|
||||
<path d="@path"
|
||||
class="rz-spider-chart-area rz-series-@(series.Index) @(isHovered && IsLegendHover ? "rz-state-hover" : "") @(!shouldShow ? "rz-state-hidden" : "")"
|
||||
data-stroke-width="@series.StrokeWidth.ToInvariantString()"
|
||||
@onclick="@(args => InvokeSeriesClick(localSeries))"
|
||||
@onmouseenter="@(args => { OnAreaMouseEnter((MouseEventArgs)args, localSeries); })"
|
||||
@onmouseleave="OnAreaMouseLeave" />
|
||||
}
|
||||
</g>
|
||||
|
||||
@if (ShowMarkers)
|
||||
{
|
||||
@* First layer: Render all visible markers *@
|
||||
<g class="rz-spider-chart-markers">
|
||||
@foreach (var series in VisibleSeries)
|
||||
{
|
||||
@if (series.MarkersVisible)
|
||||
{
|
||||
var isHovered = series == HoveredSeries;
|
||||
var shouldShow = !IsLegendHover || HoveredSeries == null || HoveredSeries == series;
|
||||
@foreach (var category in GetAllCategories())
|
||||
{
|
||||
var value = GetSeriesValue(series, category);
|
||||
var index = GetCategoryIndex(category);
|
||||
var (x, y) = GetPoint(index, value);
|
||||
|
||||
<circle cx="@x.ToInvariantString()" cy="@y.ToInvariantString()" r="@series.MarkerSize.ToInvariantString()"
|
||||
class="rz-spider-chart-marker rz-series-@(series.Index) @(isHovered && IsLegendHover ? "rz-state-hover" : "") @(!shouldShow ? "rz-state-hidden" : "")"
|
||||
pointer-events="none" />
|
||||
}
|
||||
}
|
||||
}
|
||||
</g>
|
||||
|
||||
@* Second layer: Interactive hit areas rendered AFTER visual markers so they're on top *@
|
||||
<g class="rz-spider-chart-hit-areas">
|
||||
@foreach (var series in VisibleSeries)
|
||||
{
|
||||
@if (series.MarkersVisible)
|
||||
{
|
||||
@foreach (var category in GetAllCategories())
|
||||
{
|
||||
var value = GetSeriesValue(series, category);
|
||||
var index = GetCategoryIndex(category);
|
||||
var (x, y) = GetPoint(index, value);
|
||||
var localSeries = series;
|
||||
var localCategory = category;
|
||||
var localValue = value;
|
||||
|
||||
<circle cx="@x.ToInvariantString()" cy="@y.ToInvariantString()" r="10"
|
||||
fill="transparent"
|
||||
stroke="transparent"
|
||||
class="rz-spider-chart-hit-area"
|
||||
@onclick="@(args => InvokeSeriesClick(localSeries, localCategory, localValue, localSeries.GetData(localCategory)))"
|
||||
@onmouseenter="@(args => OnMarkerMouseEnter((MouseEventArgs)args, localSeries, localCategory, localValue))"
|
||||
@onmouseleave="OnMarkerMouseLeave" />
|
||||
}
|
||||
}
|
||||
}
|
||||
</g>
|
||||
}
|
||||
|
||||
<g class="rz-spider-chart-labels">
|
||||
@RenderLabels()
|
||||
</g>
|
||||
</svg>
|
||||
@if (Legend?.Visible == true)
|
||||
{
|
||||
<SpiderLegend />
|
||||
}
|
||||
}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</CascadingValue>
|
||||
}
|
||||
601
Radzen.Blazor/RadzenSpiderChart.razor.cs
Normal file
601
Radzen.Blazor/RadzenSpiderChart.razor.cs
Normal file
@@ -0,0 +1,601 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using Microsoft.JSInterop;
|
||||
using Radzen.Blazor.Rendering;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Radzen.Blazor
|
||||
{
|
||||
/// <summary>
|
||||
/// RadzenSpiderChart component displays multi-dimensional data in a spider web format.
|
||||
/// </summary>
|
||||
public partial class RadzenSpiderChart : RadzenComponent, IRadzenSpiderChart
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the legend settings for the spider chart.
|
||||
/// </summary>
|
||||
internal RadzenSpiderLegend Legend { get; set; } = new RadzenSpiderLegend();
|
||||
|
||||
// Explicit IRadzenSpiderChart implementation to avoid exposing internal members as public API.
|
||||
RadzenSpiderLegend IRadzenSpiderChart.Legend
|
||||
{
|
||||
get => Legend;
|
||||
set => Legend = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets child content containing RadzenSpiderSeries components.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderFragment? ChildContent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the grid shape of the chart.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public SpiderChartGridShape GridShape { get; set; } = SpiderChartGridShape.Polygon;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color scheme of the chart.
|
||||
/// Available schemes include Pastel (default), Palette, Monochrome, and custom color schemes.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public ColorScheme ColorScheme { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether markers are visible.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool ShowMarkers { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether tooltips are shown.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool ShowTooltip { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Event callback for when a series (or a marker) is clicked. Matches <see cref="RadzenChart.SeriesClick" /> behavior.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public EventCallback<Radzen.SeriesClickEventArgs> SeriesClick { get; set; }
|
||||
|
||||
[Inject]
|
||||
private TooltipService TooltipService { get; set; } = default!;
|
||||
private double? Width { get; set; }
|
||||
private double? Height { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the series collection.
|
||||
/// </summary>
|
||||
internal List<IRadzenSpiderSeries> Series { get; } = new();
|
||||
|
||||
private IRadzenSpiderSeries? HoveredSeries { get; set; }
|
||||
private bool IsLegendHover { get; set; }
|
||||
private double MinValue { get; set; }
|
||||
private double MaxValue { get; set; } = 100;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the visible series.
|
||||
/// </summary>
|
||||
internal IEnumerable<IRadzenSpiderSeries> VisibleSeries => Series.Where(s => s.IsVisible);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a series to the chart.
|
||||
/// </summary>
|
||||
internal void AddSeries(IRadzenSpiderSeries series)
|
||||
{
|
||||
if (!Series.Contains(series))
|
||||
{
|
||||
series.Index = Series.Count;
|
||||
Series.Add(series);
|
||||
UpdateMinMax();
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a series from the chart.
|
||||
/// </summary>
|
||||
internal void RemoveSeries(IRadzenSpiderSeries series)
|
||||
{
|
||||
if (Series.Remove(series))
|
||||
{
|
||||
UpdateMinMax();
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the min and max values based on all series data.
|
||||
/// </summary>
|
||||
private void UpdateMinMax()
|
||||
{
|
||||
var allValues = Series.SelectMany(s => s.GetValues()).ToList();
|
||||
if (allValues.Count > 0)
|
||||
{
|
||||
MinValue = Math.Min(0, allValues.Min());
|
||||
MaxValue = Math.Max(100, allValues.Max());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all unique categories from all series.
|
||||
/// </summary>
|
||||
private List<string> GetAllCategories()
|
||||
{
|
||||
var categories = new HashSet<string>();
|
||||
foreach (var series in Series)
|
||||
{
|
||||
foreach (var category in series.GetCategories())
|
||||
{
|
||||
if (!string.IsNullOrEmpty(category))
|
||||
{
|
||||
categories.Add(category);
|
||||
}
|
||||
}
|
||||
}
|
||||
return categories.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of a category.
|
||||
/// </summary>
|
||||
private int GetCategoryIndex(string category)
|
||||
{
|
||||
var categories = GetAllCategories();
|
||||
return categories.IndexOf(category);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value for a series at a specific category.
|
||||
/// </summary>
|
||||
private double GetSeriesValue(IRadzenSpiderSeries series, string category) => series.GetValue(category);
|
||||
|
||||
/// <summary>
|
||||
/// Formats a value for display.
|
||||
/// </summary>
|
||||
private string FormatValue(IRadzenSpiderSeries series, double value) => series.FormatValue(value);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the grid path for a specific scale.
|
||||
/// </summary>
|
||||
private string GetGridPath(double scale)
|
||||
{
|
||||
var categories = GetAllCategories();
|
||||
var points = new List<string>();
|
||||
|
||||
for (int i = 0; i < categories.Count; i++)
|
||||
{
|
||||
var (x, y) = GetPoint(i, MaxValue * scale);
|
||||
points.Add($"{x.ToString("F2", CultureInfo.InvariantCulture)},{y.ToString("F2", CultureInfo.InvariantCulture)}");
|
||||
}
|
||||
|
||||
return $"M{string.Join(" L", points)} Z";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the series path for rendering.
|
||||
/// </summary>
|
||||
private string GetSeriesPath(IRadzenSpiderSeries series)
|
||||
{
|
||||
var categories = GetAllCategories();
|
||||
var points = new List<string>();
|
||||
|
||||
foreach (var category in categories)
|
||||
{
|
||||
var value = GetSeriesValue(series, category);
|
||||
var index = GetCategoryIndex(category);
|
||||
var (x, y) = GetPoint(index, value);
|
||||
points.Add($"{x.ToString("F2", CultureInfo.InvariantCulture)},{y.ToString("F2", CultureInfo.InvariantCulture)}");
|
||||
}
|
||||
|
||||
return points.Count > 0 ? $"M{string.Join(" L", points)} Z" : "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the point position for a given index and value.
|
||||
/// </summary>
|
||||
private (double x, double y) GetPoint(int index, double value)
|
||||
{
|
||||
if (!Width.HasValue || !Height.HasValue)
|
||||
{
|
||||
return (0, 0);
|
||||
}
|
||||
|
||||
var categories = GetAllCategories();
|
||||
if (categories.Count == 0)
|
||||
{
|
||||
return (Width.Value / 2, Height.Value / 2);
|
||||
}
|
||||
|
||||
var angleStep = 2 * Math.PI / categories.Count;
|
||||
var angle = index * angleStep - Math.PI / 2;
|
||||
var normalizedValue = (value - MinValue) / (MaxValue - MinValue);
|
||||
var centerX = Width.Value / 2;
|
||||
var centerY = Height.Value / 2;
|
||||
var maxRadius = Math.Min(centerX, centerY) * 0.8;
|
||||
var radius = maxRadius * normalizedValue;
|
||||
|
||||
return (centerX + radius * Math.Cos(angle), centerY + radius * Math.Sin(angle));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the label position for a category.
|
||||
/// </summary>
|
||||
private (double x, double y) GetLabelPoint(int index)
|
||||
{
|
||||
if (!Width.HasValue || !Height.HasValue)
|
||||
{
|
||||
return (0, 0);
|
||||
}
|
||||
|
||||
var categories = GetAllCategories();
|
||||
if (categories.Count == 0)
|
||||
{
|
||||
return (Width.Value / 2, Height.Value / 2);
|
||||
}
|
||||
|
||||
var angleStep = 2 * Math.PI / categories.Count;
|
||||
var angle = index * angleStep - Math.PI / 2;
|
||||
var centerX = Width.Value / 2;
|
||||
var centerY = Height.Value / 2;
|
||||
var maxRadius = Math.Min(centerX, centerY) * 0.8;
|
||||
var labelRadius = maxRadius * 1.15; // Back to original distance
|
||||
|
||||
return (centerX + labelRadius * Math.Cos(angle), centerY + labelRadius * Math.Sin(angle));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the text anchor for a label based on its position.
|
||||
/// </summary>
|
||||
private string GetLabelAnchor(int index)
|
||||
{
|
||||
var categories = GetAllCategories();
|
||||
if (categories.Count == 0) return "middle";
|
||||
|
||||
var angleStep = 2 * Math.PI / categories.Count;
|
||||
var angle = index * angleStep - Math.PI / 2;
|
||||
|
||||
// More precise anchoring based on position
|
||||
var x = Math.Cos(angle);
|
||||
var y = Math.Sin(angle);
|
||||
|
||||
// Top and bottom positions
|
||||
if (Math.Abs(x) < 0.1)
|
||||
{
|
||||
return "middle";
|
||||
}
|
||||
// Right side
|
||||
else if (x > 0.1)
|
||||
{
|
||||
return "start";
|
||||
}
|
||||
// Left side
|
||||
else
|
||||
{
|
||||
return "end";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the baseline for a label based on its position.
|
||||
/// </summary>
|
||||
private string GetLabelBaseline(int index)
|
||||
{
|
||||
var categories = GetAllCategories();
|
||||
if (categories.Count == 0) return "middle";
|
||||
|
||||
var angleStep = 2 * Math.PI / categories.Count;
|
||||
var angle = index * angleStep - Math.PI / 2;
|
||||
|
||||
var y = Math.Sin(angle);
|
||||
|
||||
// Top position
|
||||
if (y < -0.7)
|
||||
{
|
||||
return "auto";
|
||||
}
|
||||
// Bottom position
|
||||
else if (y > 0.7)
|
||||
{
|
||||
return "hanging";
|
||||
}
|
||||
// Sides
|
||||
else
|
||||
{
|
||||
return "middle";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders the category labels.
|
||||
/// </summary>
|
||||
private RenderFragment RenderLabels() => builder =>
|
||||
{
|
||||
var categories = GetAllCategories();
|
||||
|
||||
for (int i = 0; i < categories.Count; i++)
|
||||
{
|
||||
var (x, y) = GetLabelPoint(i);
|
||||
var category = categories[i];
|
||||
var anchor = GetLabelAnchor(i);
|
||||
var baseline = GetLabelBaseline(i);
|
||||
var xStr = x.ToString("F2", System.Globalization.CultureInfo.InvariantCulture);
|
||||
var yStr = y.ToString("F2", System.Globalization.CultureInfo.InvariantCulture);
|
||||
|
||||
builder.OpenElement(0, "text");
|
||||
builder.AddAttribute(1, "x", xStr);
|
||||
builder.AddAttribute(2, "y", yStr);
|
||||
builder.AddAttribute(3, "text-anchor", anchor);
|
||||
builder.AddAttribute(4, "dominant-baseline", baseline);
|
||||
builder.AddAttribute(5, "fill", "var(--rz-text-color)");
|
||||
builder.AddAttribute(6, "class", "rz-spider-chart-label");
|
||||
builder.AddContent(7, category);
|
||||
builder.CloseElement();
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Handles series click events.
|
||||
/// </summary>
|
||||
private async Task InvokeSeriesClick(IRadzenSpiderSeries series, string? category = null, double? value = null, object? data = null)
|
||||
{
|
||||
if (!SeriesClick.HasDelegate)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await SeriesClick.InvokeAsync(new Radzen.SeriesClickEventArgs
|
||||
{
|
||||
Title = series.Title,
|
||||
Category = category,
|
||||
Value = value,
|
||||
Data = data
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles area mouse enter events.
|
||||
/// </summary>
|
||||
private void OnAreaMouseEnter(MouseEventArgs args, IRadzenSpiderSeries series)
|
||||
{
|
||||
HoveredSeries = series;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles area mouse leave events.
|
||||
/// </summary>
|
||||
private void OnAreaMouseLeave()
|
||||
{
|
||||
if (!IsLegendHover)
|
||||
{
|
||||
HoveredSeries = null;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private IRadzenSpiderSeries? currentTooltipSeries;
|
||||
private string? currentTooltipCategory;
|
||||
|
||||
/// <summary>
|
||||
/// Handles marker mouse enter events.
|
||||
/// </summary>
|
||||
private async Task OnMarkerMouseEnter(MouseEventArgs args, IRadzenSpiderSeries series, string category, double value)
|
||||
{
|
||||
HoveredSeries = series;
|
||||
await ShowMarkerTooltip(args, series, category, value);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Handles marker mouse leave events.
|
||||
/// </summary>
|
||||
private void OnMarkerMouseLeave()
|
||||
{
|
||||
HoveredSeries = null;
|
||||
HideTooltip();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows tooltip for a marker.
|
||||
/// </summary>
|
||||
private async Task ShowMarkerTooltip(MouseEventArgs args, IRadzenSpiderSeries series, string category, double value)
|
||||
{
|
||||
if (!this.ShowTooltip || TooltipService == null)
|
||||
return;
|
||||
|
||||
// Prevent duplicate tooltips
|
||||
if (currentTooltipSeries == series && currentTooltipCategory == category)
|
||||
return;
|
||||
|
||||
currentTooltipSeries = series;
|
||||
currentTooltipCategory = category;
|
||||
|
||||
var valueStr = FormatValue(series, value);
|
||||
|
||||
// Open tooltip using TooltipService
|
||||
// IMPORTANT: OffsetX/OffsetY are relative to the event target (SVG element) and are unreliable for positioning
|
||||
// inside the chart container. Use ClientX/ClientY translated to chart-local coordinates.
|
||||
double x = args.OffsetX;
|
||||
double y = args.OffsetY;
|
||||
|
||||
if (JSRuntime != null)
|
||||
{
|
||||
var rect = await JSRuntime.InvokeAsync<Rect>("Radzen.clientRect", Element);
|
||||
x = args.ClientX - rect.Left;
|
||||
y = args.ClientY - rect.Top;
|
||||
}
|
||||
|
||||
TooltipService.OpenChartTooltip(Element, x, y, tooltipService => builder =>
|
||||
{
|
||||
builder.OpenComponent<Rendering.ChartTooltip>(0);
|
||||
builder.AddAttribute(1, "Title", series.Title ?? "Series");
|
||||
builder.AddAttribute(2, "Label", category ?? "");
|
||||
builder.AddAttribute(3, "Value", valueStr);
|
||||
builder.CloseComponent();
|
||||
}, new ChartTooltipOptions());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hides the tooltip.
|
||||
/// </summary>
|
||||
private void HideTooltip()
|
||||
{
|
||||
currentTooltipSeries = null;
|
||||
currentTooltipCategory = null;
|
||||
TooltipService?.Close();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Handles chart mouse leave events.
|
||||
/// </summary>
|
||||
private void OnChartMouseLeave()
|
||||
{
|
||||
HoveredSeries = null;
|
||||
IsLegendHover = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the chart.
|
||||
/// </summary>
|
||||
internal async Task Refresh()
|
||||
{
|
||||
// Force all series to update to ensure legend items re-render
|
||||
foreach (var series in Series)
|
||||
{
|
||||
series.ForceUpdate();
|
||||
}
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
Task IRadzenSpiderChart.Refresh() => Refresh();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Highlights a series on hover.
|
||||
/// </summary>
|
||||
internal void HighlightSeries(IRadzenSpiderSeries series)
|
||||
{
|
||||
HoveredSeries = series;
|
||||
IsLegendHover = true;
|
||||
InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears series highlight.
|
||||
/// </summary>
|
||||
internal void ClearHighlight()
|
||||
{
|
||||
HoveredSeries = null;
|
||||
IsLegendHover = false;
|
||||
InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
await base.OnAfterRenderAsync(firstRender);
|
||||
|
||||
if (firstRender || !Width.HasValue || !Height.HasValue)
|
||||
{
|
||||
Rendering.Rect rect;
|
||||
|
||||
if (JSRuntime != null)
|
||||
{
|
||||
rect = await JSRuntime.InvokeAsync<Rendering.Rect>("Radzen.createResizable", Element, Reference);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback if JSRuntime is not available.
|
||||
rect = new Rendering.Rect { Width = 0, Height = 0 };
|
||||
}
|
||||
|
||||
if (!Width.HasValue && rect.Width > 0)
|
||||
{
|
||||
Width = rect.Width;
|
||||
}
|
||||
else if (!Width.HasValue)
|
||||
{
|
||||
// Fallback if JavaScript sizing fails
|
||||
Width = 800;
|
||||
}
|
||||
|
||||
if (!Height.HasValue && rect.Height > 0)
|
||||
{
|
||||
Height = rect.Height;
|
||||
}
|
||||
else if (!Height.HasValue)
|
||||
{
|
||||
// Fallback if JavaScript sizing fails
|
||||
Height = 500;
|
||||
}
|
||||
|
||||
// Legend width handled by flexbox layout
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by JavaScript when the chart is resized.
|
||||
/// </summary>
|
||||
[JSInvokable]
|
||||
public void Resize(double width, double height)
|
||||
{
|
||||
bool stateHasChanged = false;
|
||||
|
||||
if (Width != width)
|
||||
{
|
||||
Width = width;
|
||||
stateHasChanged = true;
|
||||
}
|
||||
|
||||
if (Height != height)
|
||||
{
|
||||
Height = height;
|
||||
stateHasChanged = true;
|
||||
}
|
||||
|
||||
// Legend width handled by flexbox layout
|
||||
|
||||
if (stateHasChanged)
|
||||
{
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override string GetComponentCssClass()
|
||||
{
|
||||
return $"rz-spider-chart rz-scheme-{ColorScheme.ToString().ToLowerInvariant()}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spider chart grid shapes.
|
||||
/// </summary>
|
||||
public enum SpiderChartGridShape
|
||||
{
|
||||
/// <summary>
|
||||
/// Polygon grid shape.
|
||||
/// </summary>
|
||||
Polygon,
|
||||
|
||||
/// <summary>
|
||||
/// Circular grid shape.
|
||||
/// </summary>
|
||||
Circular
|
||||
}
|
||||
}
|
||||
56
Radzen.Blazor/RadzenSpiderLegend.cs
Normal file
56
Radzen.Blazor/RadzenSpiderLegend.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Radzen.Blazor
|
||||
{
|
||||
/// <summary>
|
||||
/// Configures the legend of a <see cref="RadzenSpiderChart"/>.
|
||||
/// </summary>
|
||||
public class RadzenSpiderLegend : ComponentBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the legend position.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public LegendPosition Position { get; set; } = LegendPosition.Right;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the legend is visible.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool Visible { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the parent spider chart (non-generic contract).
|
||||
/// </summary>
|
||||
[CascadingParameter]
|
||||
public IRadzenSpiderChart? Chart { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
base.OnInitialized();
|
||||
|
||||
if (Chart != null)
|
||||
{
|
||||
Chart.Legend = this;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task SetParametersAsync(ParameterView parameters)
|
||||
{
|
||||
var positionChanged = parameters.DidParameterChange(nameof(Position), Position);
|
||||
var visibleChanged = parameters.DidParameterChange(nameof(Visible), Visible);
|
||||
|
||||
await base.SetParametersAsync(parameters);
|
||||
|
||||
if ((positionChanged || visibleChanged) && Chart != null)
|
||||
{
|
||||
await Chart.Refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
244
Radzen.Blazor/RadzenSpiderSeries.razor
Normal file
244
Radzen.Blazor/RadzenSpiderSeries.razor
Normal file
@@ -0,0 +1,244 @@
|
||||
@typeparam TItem
|
||||
@inherits RadzenComponent
|
||||
@implements IDisposable
|
||||
@implements Radzen.Blazor.IRadzenSpiderSeries
|
||||
@using Radzen.Blazor.Rendering
|
||||
@using Microsoft.AspNetCore.Components.Rendering
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
|
||||
@code {
|
||||
/// <summary>
|
||||
/// Gets or sets the data for this series.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public IEnumerable<TItem> Data { get; set; } = Enumerable.Empty<TItem>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the property that provides the category values for this series.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string CategoryProperty { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the property that provides the values for this series.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string ValueProperty { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the series title.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value formatter function.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Func<double, string> ValueFormatter { get; set; } = value => value.ToString("F1");
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the format string for values.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string FormatString { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the stroke width for this series.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public double StrokeWidth { get; set; } = 2;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether markers are visible for this series.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool MarkersVisible { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the marker size for this series.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public double MarkerSize { get; set; } = 6;
|
||||
|
||||
[CascadingParameter]
|
||||
public RadzenSpiderChart? Chart { get; set; }
|
||||
|
||||
private bool _registered;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
|
||||
if (Chart != null && !_registered)
|
||||
{
|
||||
_registered = true;
|
||||
Chart.AddSeries(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the component and removes it from the parent chart.
|
||||
/// </summary>
|
||||
public new void Dispose()
|
||||
{
|
||||
if (Chart != null && _registered)
|
||||
{
|
||||
_registered = false;
|
||||
Chart.RemoveSeries(this);
|
||||
}
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the series is visible.
|
||||
/// </summary>
|
||||
public bool IsVisible { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the series index in the chart.
|
||||
/// </summary>
|
||||
internal int Index { get; set; }
|
||||
|
||||
int IRadzenSpiderSeries.Index { get => Index; set => Index = value; }
|
||||
string IRadzenSpiderSeries.Title => Title;
|
||||
bool IRadzenSpiderSeries.IsVisible { get => IsVisible; set => IsVisible = value; }
|
||||
bool IRadzenSpiderSeries.MarkersVisible => MarkersVisible;
|
||||
double IRadzenSpiderSeries.MarkerSize => MarkerSize;
|
||||
double IRadzenSpiderSeries.StrokeWidth => StrokeWidth;
|
||||
|
||||
/// <summary>
|
||||
/// Measures the legend text width for this series.
|
||||
/// </summary>
|
||||
public double MeasureLegend()
|
||||
{
|
||||
if (string.IsNullOrEmpty(Title))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return TextMeasurer.TextWidth(Title);
|
||||
}
|
||||
|
||||
double IRadzenSpiderSeries.MeasureLegend() => MeasureLegend();
|
||||
|
||||
IEnumerable<string> IRadzenSpiderSeries.GetCategories()
|
||||
{
|
||||
if (Data == null || string.IsNullOrEmpty(CategoryProperty))
|
||||
{
|
||||
return Enumerable.Empty<string>();
|
||||
}
|
||||
|
||||
var categoryGetter = PropertyAccess.Getter<TItem, string>(CategoryProperty);
|
||||
return Data.Select(item => categoryGetter(item))
|
||||
.Where(c => !string.IsNullOrEmpty(c))
|
||||
.Distinct()
|
||||
.ToList();
|
||||
}
|
||||
|
||||
IEnumerable<double> IRadzenSpiderSeries.GetValues()
|
||||
{
|
||||
if (Data == null || string.IsNullOrEmpty(ValueProperty))
|
||||
{
|
||||
return Enumerable.Empty<double>();
|
||||
}
|
||||
|
||||
var valueGetter = PropertyAccess.Getter<TItem, double>(ValueProperty);
|
||||
return Data.Select(item => valueGetter(item)).ToList();
|
||||
}
|
||||
|
||||
double IRadzenSpiderSeries.GetValue(string category)
|
||||
{
|
||||
if (Data == null || string.IsNullOrEmpty(CategoryProperty) || string.IsNullOrEmpty(ValueProperty))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var categoryGetter = PropertyAccess.Getter<TItem, string>(CategoryProperty);
|
||||
var valueGetter = PropertyAccess.Getter<TItem, double>(ValueProperty);
|
||||
|
||||
var item = Data.FirstOrDefault(d => categoryGetter(d) == category);
|
||||
return item != null ? valueGetter(item) : 0;
|
||||
}
|
||||
|
||||
object? IRadzenSpiderSeries.GetData(string category)
|
||||
{
|
||||
if (Data == null || string.IsNullOrEmpty(CategoryProperty))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var categoryGetter = PropertyAccess.Getter<TItem, string>(CategoryProperty);
|
||||
return Data.FirstOrDefault(d => categoryGetter(d) == category);
|
||||
}
|
||||
|
||||
string IRadzenSpiderSeries.FormatValue(double value)
|
||||
{
|
||||
if (ValueFormatter != null)
|
||||
{
|
||||
return ValueFormatter(value);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(FormatString))
|
||||
{
|
||||
return value.ToString(FormatString, System.Globalization.CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
return value.ToString("F1", System.Globalization.CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
RenderFragment IRadzenSpiderSeries.RenderLegendItem() => RenderLegendItem();
|
||||
void IRadzenSpiderSeries.ForceUpdate() => ForceUpdate();
|
||||
|
||||
/// <summary>
|
||||
/// Renders the legend item for this series.
|
||||
/// </summary>
|
||||
public RenderFragment RenderLegendItem() => RenderLegendItem(true);
|
||||
|
||||
/// <summary>
|
||||
/// Renders the legend item for this series.
|
||||
/// </summary>
|
||||
protected virtual RenderFragment RenderLegendItem(bool clickable) => builder =>
|
||||
{
|
||||
var style = new List<string>();
|
||||
|
||||
if (!IsVisible)
|
||||
{
|
||||
style.Add("text-decoration: line-through");
|
||||
style.Add("opacity: 0.5");
|
||||
}
|
||||
|
||||
builder.OpenComponent<LegendItem>(0);
|
||||
builder.AddAttribute(1, nameof(LegendItem.Index), Index);
|
||||
builder.AddAttribute(2, nameof(LegendItem.MarkerType), MarkerType.Circle);
|
||||
builder.AddAttribute(3, nameof(LegendItem.Style), string.Join(";", style));
|
||||
builder.AddAttribute(4, nameof(LegendItem.MarkerSize), MarkerSize);
|
||||
builder.AddAttribute(5, nameof(LegendItem.Text), Title);
|
||||
builder.AddAttribute(6, nameof(LegendItem.Click), EventCallback.Factory.Create(this, OnLegendItemClick));
|
||||
builder.AddAttribute(7, nameof(LegendItem.Clickable), clickable);
|
||||
builder.CloseComponent();
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Handles legend item click.
|
||||
/// </summary>
|
||||
private async Task OnLegendItemClick()
|
||||
{
|
||||
if (Chart != null)
|
||||
{
|
||||
IsVisible = !IsVisible;
|
||||
await Chart.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Forces the series to update its display.
|
||||
/// </summary>
|
||||
internal void ForceUpdate()
|
||||
{
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user