mirror of
https://github.com/radzenhq/radzen-blazor.git
synced 2026-02-09 05:35:22 +00:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc6fe54635 | ||
|
|
505ffa7fe4 | ||
|
|
4f82ac9599 | ||
|
|
5bbf7c1fde | ||
|
|
e416cede62 | ||
|
|
270a7e0f80 | ||
|
|
1a12a75bde | ||
|
|
d1917eac0c | ||
|
|
09830f0ea2 | ||
|
|
d34e0684fb | ||
|
|
482eca3278 | ||
|
|
5c8ac16c83 | ||
|
|
29382cf0f4 | ||
|
|
53204cc8d6 |
@@ -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,7 +124,7 @@ 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");
|
||||
@@ -134,7 +135,7 @@ namespace Radzen.Blazor.Tests
|
||||
}
|
||||
|
||||
[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,7 +147,7 @@ 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");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Xunit;
|
||||
|
||||
@@ -114,6 +114,11 @@ namespace Radzen.Blazor.Tests
|
||||
public List<string> Values { get; set; }
|
||||
}
|
||||
|
||||
public class Order
|
||||
{
|
||||
public DateTime? OrderDate { get; set; }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetProperty_Should_Resolve_DescriptionProperty()
|
||||
{
|
||||
@@ -137,6 +142,14 @@ namespace Radzen.Blazor.Tests
|
||||
Assert.NotNull(idProperty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetPropertyType_Resolves_NullableDateTime_Date()
|
||||
{
|
||||
var propertyType = PropertyAccess.GetPropertyType(typeof(Order), "OrderDate.Date");
|
||||
|
||||
Assert.Equal(typeof(DateTime), propertyType);
|
||||
}
|
||||
|
||||
interface ISimpleInterface : ISimpleNestedInterface
|
||||
{
|
||||
string Description { get; set; }
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
using Radzen;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace Radzen.Blazor.Tests
|
||||
{
|
||||
@@ -24,6 +25,11 @@ namespace Radzen.Blazor.Tests
|
||||
public int Priority { get; set; }
|
||||
}
|
||||
|
||||
private class Order
|
||||
{
|
||||
public DateTime? OrderDate { get; set; }
|
||||
}
|
||||
|
||||
private List<TestItem> GetTestData()
|
||||
{
|
||||
return new List<TestItem>
|
||||
@@ -36,6 +42,24 @@ namespace Radzen.Blazor.Tests
|
||||
};
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetNestedPropertyExpression_Handles_NullableDateTime_Date()
|
||||
{
|
||||
var parameter = Expression.Parameter(typeof(Order), "x");
|
||||
var expression = QueryableExtension.GetNestedPropertyExpression(parameter, "OrderDate.Date");
|
||||
var getter = Expression.Lambda<Func<Order, DateTime>>(expression, parameter).Compile();
|
||||
|
||||
var order = new Order { OrderDate = new DateTime(2024, 2, 3, 14, 30, 0) };
|
||||
var result = getter(order);
|
||||
|
||||
Assert.Equal(order.OrderDate.Value.Date, result);
|
||||
|
||||
var nullOrder = new Order { OrderDate = null };
|
||||
var nullResult = getter(nullOrder);
|
||||
|
||||
Assert.Equal(default(DateTime), nullResult);
|
||||
}
|
||||
|
||||
// OrderBy tests
|
||||
[Fact]
|
||||
public void OrderBy_SortsAscending_ByDefault()
|
||||
|
||||
@@ -651,7 +651,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 +679,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;
|
||||
@@ -936,6 +938,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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -26,61 +26,79 @@ public static class PropertyAccess
|
||||
|
||||
if (propertyName.Contains('[', StringComparison.Ordinal))
|
||||
{
|
||||
var arg = Expression.Parameter(typeof(TItem));
|
||||
|
||||
return Expression.Lambda<Func<TItem, TValue>>(QueryableExtension.GetNestedPropertyExpression(arg, propertyName ?? string.Empty, type), arg).Compile();
|
||||
var arg0 = Expression.Parameter(typeof(TItem), "x");
|
||||
return Expression.Lambda<Func<TItem, TValue>>(
|
||||
QueryableExtension.GetNestedPropertyExpression(arg0, propertyName, type),
|
||||
arg0
|
||||
).Compile();
|
||||
}
|
||||
else
|
||||
|
||||
var arg = Expression.Parameter(typeof(TItem), "x");
|
||||
Expression body = arg;
|
||||
|
||||
if (type != null)
|
||||
body = Expression.Convert(body, type);
|
||||
|
||||
Expression AccessNoInterface(Expression instance, string memberName)
|
||||
{
|
||||
var arg = Expression.Parameter(typeof(TItem));
|
||||
|
||||
Expression body = arg;
|
||||
|
||||
if (type != null)
|
||||
if (instance.Type.IsInterface)
|
||||
{
|
||||
body = Expression.Convert(body, type);
|
||||
var declaringType =
|
||||
new[] { instance.Type }
|
||||
.Concat(instance.Type.GetInterfaces())
|
||||
.FirstOrDefault(t => t.GetProperty(memberName) != null);
|
||||
|
||||
if (declaringType == null)
|
||||
throw new InvalidOperationException($"Member '{memberName}' not found on interface '{instance.Type}'.");
|
||||
|
||||
return Expression.Property(instance, declaringType, memberName);
|
||||
}
|
||||
|
||||
foreach (var member in propertyName.Split("."))
|
||||
try
|
||||
{
|
||||
if (body.Type.IsInterface)
|
||||
{
|
||||
body = Expression.Property(body,
|
||||
new[] { body.Type }.Concat(body.Type.GetInterfaces()).FirstOrDefault(t => t.GetProperty(member) != null)!,
|
||||
member
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
body = Expression.PropertyOrField(body, member);
|
||||
}
|
||||
catch (AmbiguousMatchException)
|
||||
{
|
||||
var property = body.Type.GetProperty(member, BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy);
|
||||
|
||||
if (property != null)
|
||||
{
|
||||
body = Expression.Property(body, property);
|
||||
}
|
||||
else
|
||||
{
|
||||
var field = body.Type.GetField(member, BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy);
|
||||
|
||||
if (field != null)
|
||||
{
|
||||
body = Expression.Field(body, field);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Expression.PropertyOrField(instance, memberName);
|
||||
}
|
||||
catch (AmbiguousMatchException)
|
||||
{
|
||||
var prop = instance.Type.GetProperty(memberName,
|
||||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy);
|
||||
if (prop != null) return Expression.Property(instance, prop);
|
||||
|
||||
body = Expression.Convert(body, typeof(TValue));
|
||||
var field = instance.Type.GetField(memberName,
|
||||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy);
|
||||
if (field != null) return Expression.Field(instance, field);
|
||||
|
||||
return Expression.Lambda<Func<TItem, TValue>>(body, arg).Compile();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
Expression AccessWithNullPropagation(Expression instance, string memberName)
|
||||
{
|
||||
var underlying = Nullable.GetUnderlyingType(instance.Type);
|
||||
if (underlying != null && memberName is not ("Value" or "HasValue"))
|
||||
{
|
||||
var hasValue = Expression.Property(instance, "HasValue");
|
||||
var value = Expression.Property(instance, "Value");
|
||||
|
||||
var accessed = AccessNoInterface(value, memberName);
|
||||
return Expression.Condition(hasValue, accessed, Expression.Default(accessed.Type));
|
||||
}
|
||||
|
||||
if (!instance.Type.IsValueType)
|
||||
{
|
||||
var notNull = Expression.NotEqual(instance, Expression.Constant(null, instance.Type));
|
||||
var accessed = AccessNoInterface(instance, memberName);
|
||||
return Expression.Condition(notNull, accessed, Expression.Default(accessed.Type));
|
||||
}
|
||||
|
||||
return AccessNoInterface(instance, memberName);
|
||||
}
|
||||
|
||||
foreach (var member in propertyName.Split('.'))
|
||||
body = AccessWithNullPropagation(body, member);
|
||||
|
||||
body = Expression.Convert(body, typeof(TValue));
|
||||
return Expression.Lambda<Func<TItem, TValue>>(body, arg).Compile();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -402,7 +420,7 @@ public static class PropertyAccess
|
||||
if (type != null)
|
||||
{
|
||||
return !type.IsInterface ?
|
||||
type.GetProperty(property ?? "")?.PropertyType :
|
||||
(Nullable.GetUnderlyingType(type) ?? type).GetProperty(property ?? "")?.PropertyType :
|
||||
new Type[] { type }
|
||||
.Concat(type.GetInterfaces())
|
||||
.FirstOrDefault(t => t.GetProperty(property ?? "") != null)?
|
||||
|
||||
@@ -81,7 +81,10 @@ namespace Radzen
|
||||
}
|
||||
catch (NotSupportedException)
|
||||
{
|
||||
// HttpNavigationManager does not support that
|
||||
// HttpNavigationManager does not support RegisterLocationChangingHandler.
|
||||
// This means we are server-side rendering. Unsubscribe from ThemeChanged to
|
||||
// avoid calling NavigateTo which would cause a 302 redirect during prerendering.
|
||||
themeService.ThemeChanged -= OnThemeChanged;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -41,6 +41,65 @@ namespace Radzen
|
||||
return source.Provider.CreateQuery(selectExpression);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Projects each element of a sequence into a collection of property values.
|
||||
/// </summary>
|
||||
public static IQueryable Select(this IQueryable source, IEnumerable<string> propertyNames)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(source);
|
||||
ArgumentNullException.ThrowIfNull(propertyNames);
|
||||
|
||||
var parameter = Expression.Parameter(source.ElementType, "x");
|
||||
|
||||
var bindings = new List<MemberBinding>();
|
||||
var allProperties = source.ElementType.GetProperties();
|
||||
|
||||
foreach (var property in allProperties.Where(p => propertyNames.Contains(p.Name)))
|
||||
{
|
||||
bindings.Add(Expression.Bind(property, Expression.Property(parameter, property)));
|
||||
}
|
||||
|
||||
var body = Expression.MemberInit(Expression.New(source.ElementType), bindings);
|
||||
|
||||
var delegateType = typeof(Func<,>).MakeGenericType(source.ElementType, source.ElementType);
|
||||
|
||||
var lambda = Expression.Lambda(delegateType, body, parameter);
|
||||
|
||||
var selectExpression = Expression.Call(typeof(Queryable),
|
||||
nameof(Queryable.Select), [source.ElementType, source.ElementType], source.Expression,
|
||||
Expression.Quote(lambda));
|
||||
|
||||
return source.Provider.CreateQuery(selectExpression);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Projects each element of a sequence into a collection of property values.
|
||||
/// </summary>
|
||||
public static IQueryable<T> Select<T>(this IQueryable<T> source, IEnumerable<string> propertyNames)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(source);
|
||||
ArgumentNullException.ThrowIfNull(propertyNames);
|
||||
|
||||
var parameter = Expression.Parameter(typeof(T), "x");
|
||||
|
||||
var bindings = new List<MemberBinding>();
|
||||
var allProperties = typeof(T).GetProperties();
|
||||
|
||||
foreach (var property in allProperties.Where(p => propertyNames.Contains(p.Name)))
|
||||
{
|
||||
bindings.Add(Expression.Bind(property, Expression.Property(parameter, property)));
|
||||
}
|
||||
|
||||
var body = Expression.MemberInit(Expression.New(typeof(T)), bindings);
|
||||
|
||||
var lambda = Expression.Lambda<Func<T, T>>(body, parameter);
|
||||
|
||||
var selectExpression = Expression.Call(typeof(Queryable),
|
||||
nameof(Queryable.Select), [typeof(T), typeof(T)], source.Expression,
|
||||
Expression.Quote(lambda));
|
||||
|
||||
return source.Provider.CreateQuery<T>(selectExpression);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Projects each element of a sequence to an IEnumerable and flattens the resulting sequences into one sequence.
|
||||
@@ -419,79 +478,150 @@ namespace Radzen
|
||||
|
||||
internal static Expression GetNestedPropertyExpression(Expression expression, string property, Type? type = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(expression);
|
||||
if (string.IsNullOrWhiteSpace(property)) return Expression.Constant(null, typeof(object));
|
||||
|
||||
var parts = property.Split(separator, 2);
|
||||
string currentPart = parts[0];
|
||||
var currentPart = parts[0];
|
||||
|
||||
static Expression BuildIsNull(Expression expr)
|
||||
{
|
||||
var underlying = Nullable.GetUnderlyingType(expr.Type);
|
||||
if (underlying != null)
|
||||
{
|
||||
return Expression.Not(Expression.Property(expr, "HasValue"));
|
||||
}
|
||||
|
||||
if (!expr.Type.IsValueType)
|
||||
{
|
||||
return Expression.Equal(expr, Expression.Constant(null, expr.Type));
|
||||
}
|
||||
|
||||
return Expression.Constant(false);
|
||||
}
|
||||
|
||||
static Expression UnwrapNullableIfNeeded(Expression expr)
|
||||
{
|
||||
return Nullable.GetUnderlyingType(expr.Type) != null
|
||||
? Expression.Property(expr, "Value")
|
||||
: expr;
|
||||
}
|
||||
|
||||
static Expression AccessMember(Expression instance, string memberName)
|
||||
{
|
||||
var t = instance.Type;
|
||||
|
||||
if (t.IsInterface)
|
||||
{
|
||||
var declaring =
|
||||
new[] { t }.Concat(t.GetInterfaces())
|
||||
.FirstOrDefault(i => i.GetProperty(memberName) != null);
|
||||
|
||||
if (declaring == null)
|
||||
throw new InvalidOperationException($"Member '{memberName}' not found on interface '{t}'.");
|
||||
|
||||
return Expression.Property(instance, declaring, memberName);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return Expression.PropertyOrField(instance, memberName);
|
||||
}
|
||||
catch (AmbiguousMatchException)
|
||||
{
|
||||
var prop = t.GetProperty(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy);
|
||||
if (prop != null) return Expression.Property(instance, prop);
|
||||
|
||||
var field = t.GetField(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy);
|
||||
if (field != null) return Expression.Field(instance, field);
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
static Expression NullPropagate(Expression parent, Expression accessed)
|
||||
{
|
||||
var isNull = BuildIsNull(parent);
|
||||
|
||||
var whenNull = Expression.Default(accessed.Type);
|
||||
|
||||
return Expression.Condition(isNull, whenNull, accessed);
|
||||
}
|
||||
|
||||
Expression member;
|
||||
|
||||
if (expression.Type.IsGenericType && typeof(IDictionary<,>).IsAssignableFrom(expression.Type.GetGenericTypeDefinition()) ||
|
||||
typeof(IDictionary).IsAssignableFrom(expression.Type) || typeof(System.Data.DataRow).IsAssignableFrom(expression.Type))
|
||||
var parentForAccess = UnwrapNullableIfNeeded(expression);
|
||||
|
||||
if ((parentForAccess.Type.IsGenericType && typeof(IDictionary<,>).IsAssignableFrom(parentForAccess.Type.GetGenericTypeDefinition())) ||
|
||||
typeof(IDictionary).IsAssignableFrom(parentForAccess.Type) ||
|
||||
typeof(System.Data.DataRow).IsAssignableFrom(parentForAccess.Type))
|
||||
{
|
||||
var key = currentPart.Split('"')[1];
|
||||
var typeString = currentPart.Split('(')[0];
|
||||
|
||||
var indexer = typeof(System.Data.DataRow).IsAssignableFrom(expression.Type) ?
|
||||
Expression.Property(expression, expression.Type.GetProperty("Item", new[] { typeof(string) })!, Expression.Constant(key)) :
|
||||
Expression.Property(expression, expression.Type.GetProperty("Item")!, Expression.Constant(key));
|
||||
member = Expression.Convert(
|
||||
indexer,
|
||||
parts.Length > 1 ? indexer.Type : type ?? Type.GetType(typeString.EndsWith('?') ? $"System.Nullable`1[System.{typeString.TrimEnd('?')}]" : $"System.{typeString}") ?? typeof(object));
|
||||
var indexer =
|
||||
typeof(System.Data.DataRow).IsAssignableFrom(parentForAccess.Type)
|
||||
? Expression.Property(parentForAccess, parentForAccess.Type.GetProperty("Item", new[] { typeof(string) })!, Expression.Constant(key))
|
||||
: Expression.Property(parentForAccess, parentForAccess.Type.GetProperty("Item")!, Expression.Constant(key));
|
||||
|
||||
var targetType =
|
||||
parts.Length > 1
|
||||
? indexer.Type
|
||||
: type
|
||||
?? Type.GetType(
|
||||
typeString.EndsWith('?')
|
||||
? $"System.Nullable`1[System.{typeString.TrimEnd('?')}]"
|
||||
: $"System.{typeString}"
|
||||
)
|
||||
?? typeof(object);
|
||||
|
||||
member = Expression.Convert(indexer, targetType);
|
||||
|
||||
member = NullPropagate(expression, member);
|
||||
}
|
||||
else if (currentPart.Contains('[', StringComparison.Ordinal)) // Handle array or list indexing
|
||||
|
||||
else if (currentPart.Contains('[', StringComparison.Ordinal))
|
||||
{
|
||||
var indexStart = currentPart.IndexOf('[', StringComparison.Ordinal);
|
||||
var propertyName = currentPart.Substring(0, indexStart);
|
||||
var indexString = currentPart.Substring(indexStart + 1, currentPart.Length - indexStart - 2);
|
||||
|
||||
member = Expression.PropertyOrField(expression, propertyName);
|
||||
if (int.TryParse(indexString, out int index))
|
||||
var collection = AccessMember(parentForAccess, propertyName);
|
||||
collection = NullPropagate(expression, collection);
|
||||
|
||||
if (!int.TryParse(indexString, out var index))
|
||||
throw new ArgumentException($"Invalid index format: {indexString}");
|
||||
|
||||
var underlyingCollection = Nullable.GetUnderlyingType(collection.Type) != null
|
||||
? Expression.Property(collection, "Value")
|
||||
: collection;
|
||||
|
||||
Expression indexed;
|
||||
if (underlyingCollection.Type.IsArray)
|
||||
{
|
||||
if (member.Type.IsArray)
|
||||
{
|
||||
member = Expression.ArrayIndex(member, Expression.Constant(index));
|
||||
}
|
||||
else if (member.Type.IsGenericType &&
|
||||
(member.Type.GetGenericTypeDefinition() == typeof(List<>) ||
|
||||
typeof(IList<>).IsAssignableFrom(member.Type.GetGenericTypeDefinition())))
|
||||
{
|
||||
var itemProperty = member.Type.GetProperty("Item");
|
||||
if (itemProperty != null)
|
||||
{
|
||||
member = Expression.Property(member, itemProperty, Expression.Constant(index));
|
||||
}
|
||||
}
|
||||
indexed = Expression.ArrayIndex(underlyingCollection, Expression.Constant(index));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"Invalid index format: {indexString}");
|
||||
var itemProp = underlyingCollection.Type.GetProperty("Item");
|
||||
if (itemProp == null)
|
||||
throw new InvalidOperationException($"Type '{underlyingCollection.Type}' has no indexer 'Item'.");
|
||||
|
||||
indexed = Expression.Property(underlyingCollection, itemProp, Expression.Constant(index));
|
||||
}
|
||||
}
|
||||
else if (expression != null && expression.Type != null && expression.Type.IsInterface)
|
||||
{
|
||||
member = Expression.Property(expression,
|
||||
new[] { expression.Type }.Concat(expression.Type.GetInterfaces()).FirstOrDefault(t => t.GetProperty(currentPart) != null)!,
|
||||
currentPart
|
||||
);
|
||||
|
||||
member = NullPropagate(collection, indexed);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (expression == null || string.IsNullOrEmpty(currentPart))
|
||||
{
|
||||
return Expression.Constant(null, typeof(object));
|
||||
var accessed = AccessMember(parentForAccess, currentPart);
|
||||
member = NullPropagate(expression, accessed);
|
||||
}
|
||||
|
||||
var p = expression.Type?.GetProperty(currentPart, BindingFlags.Public | BindingFlags.Instance);
|
||||
member = p != null ? Expression.Property(expression, p) : Expression.PropertyOrField(expression, currentPart);
|
||||
}
|
||||
if (parts.Length > 1)
|
||||
return GetNestedPropertyExpression(member, parts[1], type);
|
||||
|
||||
if (expression != null && expression.Type != null && expression.Type.IsValueType && Nullable.GetUnderlyingType(expression.Type) == null)
|
||||
{
|
||||
expression = Expression.Convert(expression, typeof(object));
|
||||
}
|
||||
|
||||
return parts.Length > 1 ? GetNestedPropertyExpression(member, parts[1], type) :
|
||||
(Nullable.GetUnderlyingType(member.Type) != null || member.Type == typeof(string)) ?
|
||||
expression != null ? Expression.Condition(Expression.Equal(expression, Expression.Constant(null)), Expression.Constant(null, member.Type), member) : member :
|
||||
member;
|
||||
return member;
|
||||
}
|
||||
|
||||
internal static Expression GetExpression<T>(ParameterExpression parameter, FilterDescriptor filter, FilterCaseSensitivity filterCaseSensitivity, Type type)
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<IsPackable>true</IsPackable>
|
||||
<PackageId>Radzen.Blazor</PackageId>
|
||||
<Product>Radzen.Blazor</Product>
|
||||
<Version>8.7.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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,9 +26,12 @@
|
||||
}
|
||||
<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))">
|
||||
<span class="@(Grid.ExpandedGroupItemStyle(this, Grid.allGroupsExpanded != null ? Grid.allGroupsExpanded : rowArgs?.Item1.Expanded))"></span>
|
||||
</a>
|
||||
@if (rowArgs?.Item1.Expandable == true)
|
||||
{
|
||||
<a id="@(Grid.GridId() + Group.GetHashCode())" aria-label=@Grid.ExpandGroupAriaLabel @onclick:preventDefault="true" @onclick="@(_ => Grid.ExpandGroupItem(this, rowArgs?.Item1.Expanded))">
|
||||
<span class="@(Grid.ExpandedGroupItemStyle(this, Grid.allGroupsExpanded != null ? Grid.allGroupsExpanded : rowArgs?.Item1.Expanded))"></span>
|
||||
</a>
|
||||
}
|
||||
</td>
|
||||
}
|
||||
<td colspan="@(TotalColumnCount + (Grid?.Groups.Count ?? 0) - 1 - Group.Level + ((Grid?.Template != null && Grid?.ShowExpandColumn == true) ? 1 : 0))">
|
||||
|
||||
@@ -151,8 +151,8 @@
|
||||
<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>
|
||||
}
|
||||
@@ -197,7 +197,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 +208,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
|
||||
@@ -204,7 +204,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 +212,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@using Radzen
|
||||
@using Radzen
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Microsoft.JSInterop
|
||||
@using Microsoft.AspNetCore.Components.Rendering
|
||||
@@ -65,7 +65,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 +73,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
|
||||
|
||||
@@ -349,6 +349,7 @@ namespace Radzen.Blazor
|
||||
skip = page * PageSize;
|
||||
await InvokeAsync(Reload);
|
||||
await PageChanged.InvokeAsync(new PagerEventArgs() { Skip = skip, Top = PageSize, PageIndex = CurrentPage });
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,5 +1,7 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.JSInterop;
|
||||
using Radzen.Blazor.Rendering;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -155,6 +157,28 @@ namespace Radzen.Blazor
|
||||
/// </summary>
|
||||
protected override string GetComponentCssClass() => "rz-qrcode";
|
||||
private static string Format(double v) => v.ToString(CultureInfo.InvariantCulture);
|
||||
private static (string Color, double Opacity) GetSvgFillParts(string? color)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(color))
|
||||
{
|
||||
return ("none", 1);
|
||||
}
|
||||
|
||||
if (string.Equals(color, "transparent", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return ("rgb(0, 0, 0)", 0);
|
||||
}
|
||||
|
||||
var rgb = RGB.Parse(color);
|
||||
if (rgb == null)
|
||||
{
|
||||
return (color, 1);
|
||||
}
|
||||
|
||||
var opacity = Math.Clamp(rgb.Alpha, 0, 1);
|
||||
var fill = $"rgb({Format(rgb.Red)}, {Format(rgb.Green)}, {Format(rgb.Blue)})";
|
||||
return (fill, opacity);
|
||||
}
|
||||
private static bool IsFinderCell(int r, int c, int n)
|
||||
{
|
||||
bool inTL = r < 7 && c < 7;
|
||||
|
||||
@@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Components;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Radzen.Blazor.Rendering;
|
||||
using System.Text;
|
||||
|
||||
namespace Radzen.Blazor;
|
||||
@@ -185,12 +186,13 @@ public static class RadzenQREncoder
|
||||
|
||||
var sb = new StringBuilder(n * n + 1024);
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"{px}\" height=\"{px}\" viewBox=\"0 0 {vb} {vb}\" shape-rendering=\"crispEdges\">");
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"0\" y=\"0\" width=\"{vb}\" height=\"{vb}\" fill=\"{background}\"/>");
|
||||
var backgroundFill = GetSvgFillParts(background);
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"0\" y=\"0\" width=\"{vb}\" height=\"{vb}\" fill=\"{backgroundFill.Color}\" fill-opacity=\"{Format(backgroundFill.Opacity)}\"/>");
|
||||
|
||||
var baseEyeColor = eyeColor ?? foreground;
|
||||
AppendEye(sb, 4, 4, eyeShapeTopLeft ?? eyeShape, eyeColorTopLeft ?? baseEyeColor, background);
|
||||
AppendEye(sb, vb - 11, 4, eyeShapeTopRight ?? eyeShape, eyeColorTopRight ?? baseEyeColor, background);
|
||||
AppendEye(sb, 4, vb - 11, eyeShapeBottomLeft ?? eyeShape, eyeColorBottomLeft ?? baseEyeColor, background);
|
||||
AppendEye(sb, 4, 4, eyeShapeTopLeft ?? eyeShape, eyeColorTopLeft ?? baseEyeColor, "eye-mask-0");
|
||||
AppendEye(sb, vb - 11, 4, eyeShapeTopRight ?? eyeShape, eyeColorTopRight ?? baseEyeColor, "eye-mask-1");
|
||||
AppendEye(sb, 4, vb - 11, eyeShapeBottomLeft ?? eyeShape, eyeColorBottomLeft ?? baseEyeColor, "eye-mask-2");
|
||||
|
||||
for (int r = 0; r < n; r++)
|
||||
{
|
||||
@@ -1147,28 +1149,60 @@ public static class RadzenQREncoder
|
||||
return inTL || inTR || inBL;
|
||||
}
|
||||
|
||||
private static void AppendEye(StringBuilder sb, double x, double y, QRCodeEyeShape shape, string color, string background)
|
||||
private static void AppendEye(StringBuilder sb, double x, double y, QRCodeEyeShape shape, string color, string maskId)
|
||||
{
|
||||
switch (shape)
|
||||
{
|
||||
case QRCodeEyeShape.Rounded:
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x)}\" y=\"{Format(y)}\" width=\"7\" height=\"7\" rx=\"1.25\" ry=\"1.25\" fill=\"{color}\"/>");
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x + 1)}\" y=\"{Format(y + 1)}\" width=\"5\" height=\"5\" rx=\"0.25\" ry=\"0.25\" fill=\"{background}\"/>");
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<defs><mask id=\"{maskId}\" maskUnits=\"userSpaceOnUse\">");
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x)}\" y=\"{Format(y)}\" width=\"7\" height=\"7\" rx=\"1.25\" ry=\"1.25\" fill=\"white\"/>");
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x + 1)}\" y=\"{Format(y + 1)}\" width=\"5\" height=\"5\" rx=\"0.25\" ry=\"0.25\" fill=\"black\"/>");
|
||||
sb.Append("</mask></defs>");
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x)}\" y=\"{Format(y)}\" width=\"7\" height=\"7\" rx=\"1.25\" ry=\"1.25\" fill=\"{color}\" mask=\"url(#{maskId})\"/>");
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x + 2)}\" y=\"{Format(y + 2)}\" width=\"3\" height=\"3\" rx=\"0.75\" ry=\"0.75\" fill=\"{color}\"/>");
|
||||
break;
|
||||
case QRCodeEyeShape.Framed:
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x)}\" y=\"{Format(y)}\" width=\"7\" height=\"7\" fill=\"{color}\"/>");
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x + 1.2)}\" y=\"{Format(y + 1.2)}\" width=\"{Format(7 - 2 * 1.2)}\" height=\"{Format(7 - 2 * 1.2)}\" fill=\"{background}\"/>");
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<defs><mask id=\"{maskId}\" maskUnits=\"userSpaceOnUse\">");
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x)}\" y=\"{Format(y)}\" width=\"7\" height=\"7\" fill=\"white\"/>");
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x + 1.2)}\" y=\"{Format(y + 1.2)}\" width=\"{Format(7 - 2 * 1.2)}\" height=\"{Format(7 - 2 * 1.2)}\" fill=\"black\"/>");
|
||||
sb.Append("</mask></defs>");
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x)}\" y=\"{Format(y)}\" width=\"7\" height=\"7\" fill=\"{color}\" mask=\"url(#{maskId})\"/>");
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x + 2)}\" y=\"{Format(y + 2)}\" width=\"3\" height=\"3\" fill=\"{color}\"/>");
|
||||
break;
|
||||
case QRCodeEyeShape.Square:
|
||||
default:
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x)}\" y=\"{Format(y)}\" width=\"7\" height=\"7\" fill=\"{color}\"/>");
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x + 1)}\" y=\"{Format(y + 1)}\" width=\"5\" height=\"5\" fill=\"{background}\"/>");
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<defs><mask id=\"{maskId}\" maskUnits=\"userSpaceOnUse\">");
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x)}\" y=\"{Format(y)}\" width=\"7\" height=\"7\" fill=\"white\"/>");
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x + 1)}\" y=\"{Format(y + 1)}\" width=\"5\" height=\"5\" fill=\"black\"/>");
|
||||
sb.Append("</mask></defs>");
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x)}\" y=\"{Format(y)}\" width=\"7\" height=\"7\" fill=\"{color}\" mask=\"url(#{maskId})\"/>");
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x + 2)}\" y=\"{Format(y + 2)}\" width=\"3\" height=\"3\" fill=\"{color}\"/>");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static (string Color, double Opacity) GetSvgFillParts(string? color)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(color))
|
||||
{
|
||||
return ("none", 1);
|
||||
}
|
||||
|
||||
if (string.Equals(color, "transparent", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return ("rgb(0, 0, 0)", 0);
|
||||
}
|
||||
|
||||
var rgb = RGB.Parse(color);
|
||||
if (rgb == null)
|
||||
{
|
||||
return (color, 1);
|
||||
}
|
||||
|
||||
var opacity = Math.Clamp(rgb.Alpha, 0, 1);
|
||||
var fill = $"rgb({Format(rgb.Red)}, {Format(rgb.Green)}, {Format(rgb.Blue)})";
|
||||
return (fill, opacity);
|
||||
}
|
||||
|
||||
private static string Format(double v) => v.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
@@ -155,6 +155,12 @@ namespace Radzen.Blazor
|
||||
/// </summary>
|
||||
public bool IsResizing { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Value indicating if the splitter should call StateHasChanged on resizing.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool ChangeStateOnResize { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Called on pane resizing.
|
||||
/// </summary>
|
||||
@@ -163,7 +169,10 @@ namespace Radzen.Blazor
|
||||
{
|
||||
IsResizing = true;
|
||||
|
||||
StateHasChanged();
|
||||
if (ChangeStateOnResize)
|
||||
{
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -11,8 +11,7 @@
|
||||
ColumnWidth="300px"
|
||||
AllowColumnPicking="true"
|
||||
PickedColumnsChanged="@PickedColumnsChanged"
|
||||
ColumnsPickerAllowFiltering="true"
|
||||
>
|
||||
ColumnsPickerAllowFiltering="true" QueryOnlyVisibleColumns="true">
|
||||
<Columns>
|
||||
<RadzenDataGridColumn
|
||||
Property=@nameof(Employee.EmployeeID)
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
Enable default column picker by setting the <strong>AllowColumnPicking</strong> grid property to true.
|
||||
You can disable picking for specific columns by setting their <strong>Pickable</strong> property to false.
|
||||
The example below also sets <strong>ColumnsPickerAllowFiltering</strong> on the grid to make picking columns easier.
|
||||
Use <strong>QueryOnlyVisibleColumns</strong> to tell the grid to only include currently visible columns in the IQueryable query.
|
||||
</RadzenText>
|
||||
<RadzenText TextStyle="TextStyle.Subtitle1" TagName="TagName.P" class="rz-pb-4">
|
||||
Documentation:
|
||||
|
||||
@@ -85,6 +85,8 @@
|
||||
{
|
||||
args.Expanded = allGroupsExpanded != null ? allGroupsExpanded : false;
|
||||
}
|
||||
|
||||
args.Expandable = args.Group.Data.Key != "Vice President, Sales";
|
||||
}
|
||||
|
||||
void OnGroupRowExpand(Group group)
|
||||
|
||||
Reference in New Issue
Block a user