Add compiler support for EventCallback (dotnet/aspnetcore-tooling#222)
* Add new APIs to ComponentShim
* Add new APIs to ComponentsApi
* Add EventCallback metadata
* Add discovery of EventCallback properties
* Errata for Runtime APIs
* Add ability to use EventCallback as parameter
* Add support for bind to component
* Use EventCallback for bind-... to elements
* Use EventCallback<T> for event handlers
\n\nCommit migrated from a0b6bc0e52
This commit is contained in:
parent
1243e07e56
commit
54e79153ec
|
|
@ -55,6 +55,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
|
||||||
|
|
||||||
public static readonly string DelegateSignatureKey = "Blazor.DelegateSignature";
|
public static readonly string DelegateSignatureKey = "Blazor.DelegateSignature";
|
||||||
|
|
||||||
|
public static readonly string EventCallbackKey = "Blazor.EventCallback";
|
||||||
|
|
||||||
public static readonly string WeaklyTypedKey = "Blazor.IsWeaklyTyped";
|
public static readonly string WeaklyTypedKey = "Blazor.IsWeaklyTyped";
|
||||||
|
|
||||||
public static readonly string RuntimeName = "Blazor.IComponent";
|
public static readonly string RuntimeName = "Blazor.IComponent";
|
||||||
|
|
|
||||||
|
|
@ -158,9 +158,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
|
||||||
// there will be errors. In general the errors that come from C# in this case are good enough
|
// there will be errors. In general the errors that come from C# in this case are good enough
|
||||||
// to understand the problem.
|
// to understand the problem.
|
||||||
//
|
//
|
||||||
// The BindMethods calls are required in this case because to give us a good experience. They
|
// We also support and encourage the use of EventCallback<> with bind. So in the above example
|
||||||
|
// the ValueChanged property could be an Action<> or an EventCallback<>.
|
||||||
|
//
|
||||||
|
// The BindMethods calls are required with Action<> because to give us a good experience. They
|
||||||
// use overloading to ensure that can get an Action<object> that will convert and set an arbitrary
|
// use overloading to ensure that can get an Action<object> that will convert and set an arbitrary
|
||||||
// value.
|
// value. We have a similar set of APIs to use with EventCallback<>.
|
||||||
//
|
//
|
||||||
// We also assume that the element will be treated as a component for now because
|
// We also assume that the element will be treated as a component for now because
|
||||||
// multiple passes handle 'special' tag helpers. We have another pass that translates
|
// multiple passes handle 'special' tag helpers. We have another pass that translates
|
||||||
|
|
@ -207,67 +210,28 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
|
||||||
format = GetAttributeContent(formatNode);
|
format = GetAttributeContent(formatNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now rewrite the content of the value node to look like:
|
|
||||||
//
|
|
||||||
// BindMethods.GetValue(<code>) OR
|
|
||||||
// BindMethods.GetValue(<code>, <format>)
|
|
||||||
var valueExpressionTokens = new List<IntermediateToken>();
|
var valueExpressionTokens = new List<IntermediateToken>();
|
||||||
valueExpressionTokens.Add(new IntermediateToken()
|
var changeExpressionTokens = new List<IntermediateToken>();
|
||||||
|
if (changeAttribute != null && changeAttribute.IsDelegateProperty())
|
||||||
{
|
{
|
||||||
Content = $"{ComponentsApi.BindMethods.GetValue}(",
|
RewriteNodesForDelegateBind(
|
||||||
Kind = TokenKind.CSharp
|
original,
|
||||||
});
|
format,
|
||||||
valueExpressionTokens.Add(original);
|
valueAttribute,
|
||||||
if (!string.IsNullOrEmpty(format?.Content))
|
changeAttribute,
|
||||||
{
|
valueExpressionTokens,
|
||||||
valueExpressionTokens.Add(new IntermediateToken()
|
changeExpressionTokens);
|
||||||
{
|
|
||||||
Content = ", ",
|
|
||||||
Kind = TokenKind.CSharp,
|
|
||||||
});
|
|
||||||
valueExpressionTokens.Add(format);
|
|
||||||
}
|
|
||||||
valueExpressionTokens.Add(new IntermediateToken()
|
|
||||||
{
|
|
||||||
Content = ")",
|
|
||||||
Kind = TokenKind.CSharp,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Now rewrite the content of the change-handler node. There are two cases we care about
|
|
||||||
// here. If it's a component attribute, then don't use the 'BindMethods wrapper. We expect
|
|
||||||
// component attributes to always 'match' on type.
|
|
||||||
//
|
|
||||||
// __value => <code> = __value
|
|
||||||
//
|
|
||||||
// For general DOM attributes, we need to be able to create a delegate that accepts UIEventArgs
|
|
||||||
// so we use BindMethods.SetValueHandler
|
|
||||||
//
|
|
||||||
// BindMethods.SetValueHandler(__value => <code> = __value, <code>) OR
|
|
||||||
// BindMethods.SetValueHandler(__value => <code> = __value, <code>, <format>)
|
|
||||||
//
|
|
||||||
// Note that the linemappings here are applied to the value attribute, not the change attribute.
|
|
||||||
|
|
||||||
string changeExpressionContent = null;
|
|
||||||
if (changeAttribute == null && format == null)
|
|
||||||
{
|
|
||||||
changeExpressionContent = $"{ComponentsApi.BindMethods.SetValueHandler}(__value => {original.Content} = __value, {original.Content})";
|
|
||||||
}
|
|
||||||
else if (changeAttribute == null && format != null)
|
|
||||||
{
|
|
||||||
changeExpressionContent = $"{ComponentsApi.BindMethods.SetValueHandler}(__value => {original.Content} = __value, {original.Content}, {format.Content})";
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
changeExpressionContent = $"__value => {original.Content} = __value";
|
RewriteNodesForEventCallbackBind(
|
||||||
|
original,
|
||||||
|
format,
|
||||||
|
valueAttribute,
|
||||||
|
changeAttribute,
|
||||||
|
valueExpressionTokens,
|
||||||
|
changeExpressionTokens);
|
||||||
}
|
}
|
||||||
var changeExpressionTokens = new List<IntermediateToken>()
|
|
||||||
{
|
|
||||||
new IntermediateToken()
|
|
||||||
{
|
|
||||||
Content = changeExpressionContent,
|
|
||||||
Kind = TokenKind.CSharp
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (parent is MarkupElementIntermediateNode)
|
if (parent is MarkupElementIntermediateNode)
|
||||||
{
|
{
|
||||||
|
|
@ -521,6 +485,151 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void RewriteNodesForDelegateBind(
|
||||||
|
IntermediateToken original,
|
||||||
|
IntermediateToken format,
|
||||||
|
BoundAttributeDescriptor valueAttribute,
|
||||||
|
BoundAttributeDescriptor changeAttribute,
|
||||||
|
List<IntermediateToken> valueExpressionTokens,
|
||||||
|
List<IntermediateToken> changeExpressionTokens)
|
||||||
|
{
|
||||||
|
// Now rewrite the content of the value node to look like:
|
||||||
|
//
|
||||||
|
// BindMethods.GetValue(<code>) OR
|
||||||
|
// BindMethods.GetValue(<code>, <format>)
|
||||||
|
valueExpressionTokens.Add(new IntermediateToken()
|
||||||
|
{
|
||||||
|
Content = $"{ComponentsApi.BindMethods.GetValue}(",
|
||||||
|
Kind = TokenKind.CSharp
|
||||||
|
});
|
||||||
|
valueExpressionTokens.Add(original);
|
||||||
|
if (!string.IsNullOrEmpty(format?.Content))
|
||||||
|
{
|
||||||
|
valueExpressionTokens.Add(new IntermediateToken()
|
||||||
|
{
|
||||||
|
Content = ", ",
|
||||||
|
Kind = TokenKind.CSharp,
|
||||||
|
});
|
||||||
|
valueExpressionTokens.Add(format);
|
||||||
|
}
|
||||||
|
valueExpressionTokens.Add(new IntermediateToken()
|
||||||
|
{
|
||||||
|
Content = ")",
|
||||||
|
Kind = TokenKind.CSharp,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Now rewrite the content of the change-handler node. There are two cases we care about
|
||||||
|
// here. If it's a component attribute, then don't use the 'BindMethods' wrapper. We expect
|
||||||
|
// component attributes to always 'match' on type.
|
||||||
|
//
|
||||||
|
// __value => <code> = __value
|
||||||
|
//
|
||||||
|
// For general DOM attributes, we need to be able to create a delegate that accepts UIEventArgs
|
||||||
|
// so we use BindMethods.SetValueHandler
|
||||||
|
//
|
||||||
|
// BindMethods.SetValueHandler(__value => <code> = __value, <code>) OR
|
||||||
|
// BindMethods.SetValueHandler(__value => <code> = __value, <code>, <format>)
|
||||||
|
//
|
||||||
|
// Note that the linemappings here are applied to the value attribute, not the change attribute.
|
||||||
|
|
||||||
|
string changeExpressionContent;
|
||||||
|
if (changeAttribute == null && format == null)
|
||||||
|
{
|
||||||
|
// DOM
|
||||||
|
changeExpressionContent = $"{ComponentsApi.BindMethods.SetValueHandler}(__value => {original.Content} = __value, {original.Content})";
|
||||||
|
}
|
||||||
|
else if (changeAttribute == null && format != null)
|
||||||
|
{
|
||||||
|
// DOM + format
|
||||||
|
changeExpressionContent = $"{ComponentsApi.BindMethods.SetValueHandler}(__value => {original.Content} = __value, {original.Content}, {format.Content})";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Component
|
||||||
|
changeExpressionContent = $"__value => {original.Content} = __value";
|
||||||
|
}
|
||||||
|
changeExpressionTokens.Add(new IntermediateToken()
|
||||||
|
{
|
||||||
|
Content = changeExpressionContent,
|
||||||
|
Kind = TokenKind.CSharp
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RewriteNodesForEventCallbackBind(
|
||||||
|
IntermediateToken original,
|
||||||
|
IntermediateToken format,
|
||||||
|
BoundAttributeDescriptor valueAttribute,
|
||||||
|
BoundAttributeDescriptor changeAttribute,
|
||||||
|
List<IntermediateToken> valueExpressionTokens,
|
||||||
|
List<IntermediateToken> changeExpressionTokens)
|
||||||
|
{
|
||||||
|
// Now rewrite the content of the value node to look like:
|
||||||
|
//
|
||||||
|
// BindMethods.GetValue(<code>) OR
|
||||||
|
// BindMethods.GetValue(<code>, <format>)
|
||||||
|
valueExpressionTokens.Add(new IntermediateToken()
|
||||||
|
{
|
||||||
|
Content = $"{ComponentsApi.BindMethods.GetValue}(",
|
||||||
|
Kind = TokenKind.CSharp
|
||||||
|
});
|
||||||
|
valueExpressionTokens.Add(original);
|
||||||
|
if (!string.IsNullOrEmpty(format?.Content))
|
||||||
|
{
|
||||||
|
valueExpressionTokens.Add(new IntermediateToken()
|
||||||
|
{
|
||||||
|
Content = ", ",
|
||||||
|
Kind = TokenKind.CSharp,
|
||||||
|
});
|
||||||
|
valueExpressionTokens.Add(format);
|
||||||
|
}
|
||||||
|
valueExpressionTokens.Add(new IntermediateToken()
|
||||||
|
{
|
||||||
|
Content = ")",
|
||||||
|
Kind = TokenKind.CSharp,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Now rewrite the content of the change-handler node. There are two cases we care about
|
||||||
|
// here. If it's a component attribute, then don't use the 'CreateBinder' wrapper. We expect
|
||||||
|
// component attributes to always 'match' on type.
|
||||||
|
//
|
||||||
|
// The really tricky part of this is that we CANNOT write the type name of of the EventCallback we
|
||||||
|
// intend to create. Doing so would really complicate the story for how we deal with generic types,
|
||||||
|
// since the generic type lowering pass runs after this. To keep this simple we're relying on
|
||||||
|
// the compiler to resolve overloads for us.
|
||||||
|
//
|
||||||
|
// EventCallbackFactory.CreateInferred(this, __value => <code> = __value, <code>)
|
||||||
|
//
|
||||||
|
// For general DOM attributes, we need to be able to create a delegate that accepts UIEventArgs
|
||||||
|
// so we use 'CreateBinder'
|
||||||
|
//
|
||||||
|
// EventCallbackFactory.CreateBinder(this, __value => <code> = __value, <code>) OR
|
||||||
|
// EventCallbackFactory.CreateBinder(this, __value => <code> = __value, <code>, <format>)
|
||||||
|
//
|
||||||
|
// Note that the linemappings here are applied to the value attribute, not the change attribute.
|
||||||
|
|
||||||
|
string changeExpressionContent;
|
||||||
|
if (changeAttribute == null && format == null)
|
||||||
|
{
|
||||||
|
// DOM
|
||||||
|
changeExpressionContent = $"{ComponentsApi.EventCallback.FactoryAccessor}.{ComponentsApi.EventCallbackFactory.CreateBinderMethod}(this, __value => {original.Content} = __value, {original.Content})";
|
||||||
|
}
|
||||||
|
else if (changeAttribute == null && format != null)
|
||||||
|
{
|
||||||
|
// DOM + format
|
||||||
|
changeExpressionContent = $"{ComponentsApi.EventCallback.FactoryAccessor}.{ComponentsApi.EventCallbackFactory.CreateBinderMethod}(this, __value => {original.Content} = __value, {original.Content}, {format.Content})";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Component
|
||||||
|
changeExpressionContent = $"{ComponentsApi.EventCallback.FactoryAccessor}.{ComponentsApi.EventCallbackFactory.CreateInferredMethod}(this, __value => {original.Content} = __value, {original.Content})";
|
||||||
|
}
|
||||||
|
changeExpressionTokens.Add(new IntermediateToken()
|
||||||
|
{
|
||||||
|
Content = changeExpressionContent,
|
||||||
|
Kind = TokenKind.CSharp
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private static IntermediateToken GetAttributeContent(TagHelperPropertyIntermediateNode node)
|
private static IntermediateToken GetAttributeContent(TagHelperPropertyIntermediateNode node)
|
||||||
{
|
{
|
||||||
var template = node.FindDescendantNodes<TemplateIntermediateNode>().FirstOrDefault();
|
var template = node.FindDescendantNodes<TemplateIntermediateNode>().FirstOrDefault();
|
||||||
|
|
|
||||||
|
|
@ -541,6 +541,55 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
|
||||||
context.CodeWriter.Write(")");
|
context.CodeWriter.Write(")");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (node.BoundAttribute?.IsEventCallbackProperty() ?? false)
|
||||||
|
{
|
||||||
|
// This is the case where we are writing an EventCallback (a delegate with super-powers).
|
||||||
|
//
|
||||||
|
// An event callback can either be passed verbatim, or it can be created by the EventCallbackFactory.
|
||||||
|
// Since we don't look at the code the user typed inside the attribute value, this is always
|
||||||
|
// resolved via overloading.
|
||||||
|
|
||||||
|
if (canTypeCheck && NeedsTypeCheck(node))
|
||||||
|
{
|
||||||
|
context.CodeWriter.Write(ComponentsApi.RuntimeHelpers.TypeCheck);
|
||||||
|
context.CodeWriter.Write("<");
|
||||||
|
context.CodeWriter.Write(node.TypeName);
|
||||||
|
context.CodeWriter.Write(">");
|
||||||
|
context.CodeWriter.Write("(");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Microsoft.AspNetCore.Components.EventCallback.Factory.Create(this, ...) OR
|
||||||
|
// Microsoft.AspNetCore.Components.EventCallback.Factory.Create<T>(this, ...)
|
||||||
|
|
||||||
|
context.CodeWriter.Write(ComponentsApi.EventCallback.FactoryAccessor);
|
||||||
|
context.CodeWriter.Write(".");
|
||||||
|
context.CodeWriter.Write(ComponentsApi.EventCallbackFactory.CreateMethod);
|
||||||
|
|
||||||
|
if (node.TryParseEventCallbackTypeArgument(out var argument))
|
||||||
|
{
|
||||||
|
context.CodeWriter.Write("<");
|
||||||
|
context.CodeWriter.Write(argument);
|
||||||
|
context.CodeWriter.Write(">");
|
||||||
|
}
|
||||||
|
|
||||||
|
context.CodeWriter.Write("(");
|
||||||
|
context.CodeWriter.Write("this");
|
||||||
|
context.CodeWriter.Write(", ");
|
||||||
|
|
||||||
|
context.CodeWriter.WriteLine();
|
||||||
|
|
||||||
|
for (var i = 0; i < tokens.Count; i++)
|
||||||
|
{
|
||||||
|
WriteCSharpToken(context, tokens[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.CodeWriter.Write(")");
|
||||||
|
|
||||||
|
if (canTypeCheck && NeedsTypeCheck(node))
|
||||||
|
{
|
||||||
|
context.CodeWriter.Write(")");
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// This is the case when an attribute contains C# code
|
// This is the case when an attribute contains C# code
|
||||||
|
|
|
||||||
|
|
@ -123,16 +123,16 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
|
||||||
|
|
||||||
// Now rewrite the content of the value node to look like:
|
// Now rewrite the content of the value node to look like:
|
||||||
//
|
//
|
||||||
// BindMethods.GetEventHandlerValue<TDelegate>(<code>)
|
// EventCallback.Factory.Create<T>(this, <code>)
|
||||||
//
|
//
|
||||||
// This method is overloaded on string and TDelegate, which means that it will put the code in the
|
// This method is overloaded on string and T, which means that it will put the code in the
|
||||||
// correct context for intellisense when typing in the attribute.
|
// correct context for intellisense when typing in the attribute.
|
||||||
var eventArgsType = node.TagHelper.GetEventArgsType();
|
var eventArgsType = node.TagHelper.GetEventArgsType();
|
||||||
var tokens = new List<IntermediateToken>()
|
var tokens = new List<IntermediateToken>()
|
||||||
{
|
{
|
||||||
new IntermediateToken()
|
new IntermediateToken()
|
||||||
{
|
{
|
||||||
Content = $"{ComponentsApi.BindMethods.GetEventHandlerValue}<{eventArgsType}>(",
|
Content = $"{ComponentsApi.EventCallback.FactoryAccessor}.{ComponentsApi.EventCallbackFactory.CreateMethod}<{eventArgsType}>(this, ",
|
||||||
Kind = TokenKind.CSharp
|
Kind = TokenKind.CSharp
|
||||||
},
|
},
|
||||||
new IntermediateToken()
|
new IntermediateToken()
|
||||||
|
|
|
||||||
|
|
@ -218,6 +218,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
|
||||||
// This is a weakly typed delegate, treat it as Action<object>
|
// This is a weakly typed delegate, treat it as Action<object>
|
||||||
attribute.TypeName = "System.Action<System.Object>";
|
attribute.TypeName = "System.Action<System.Object>";
|
||||||
}
|
}
|
||||||
|
else if (attribute.TypeName == null && (attribute.BoundAttribute?.IsEventCallbackProperty() ?? false))
|
||||||
|
{
|
||||||
|
// This is a weakly typed event-callback, treat it as EventCallback (non-generic)
|
||||||
|
attribute.TypeName = ComponentsApi.EventCallback.FullTypeName;
|
||||||
|
}
|
||||||
else if (attribute.TypeName == null)
|
else if (attribute.TypeName == null)
|
||||||
{
|
{
|
||||||
// This is a weakly typed attribute, treat it as System.Object
|
// This is a weakly typed attribute, treat it as System.Object
|
||||||
|
|
|
||||||
|
|
@ -460,7 +460,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// See comments in BlazorDesignTimeNodeWriter for a description of the cases that are possible.
|
// See comments in ComponentDesignTimeNodeWriter for a description of the cases that are possible.
|
||||||
var tokens = GetCSharpTokens(node);
|
var tokens = GetCSharpTokens(node);
|
||||||
if ((node.BoundAttribute?.IsDelegateProperty() ?? false) ||
|
if ((node.BoundAttribute?.IsDelegateProperty() ?? false) ||
|
||||||
(node.BoundAttribute?.IsChildContentProperty() ?? false))
|
(node.BoundAttribute?.IsChildContentProperty() ?? false))
|
||||||
|
|
@ -482,6 +482,47 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
|
||||||
context.CodeWriter.Write(")");
|
context.CodeWriter.Write(")");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (node.BoundAttribute?.IsEventCallbackProperty() ?? false)
|
||||||
|
{
|
||||||
|
if (canTypeCheck && NeedsTypeCheck(node))
|
||||||
|
{
|
||||||
|
context.CodeWriter.Write(ComponentsApi.RuntimeHelpers.TypeCheck);
|
||||||
|
context.CodeWriter.Write("<");
|
||||||
|
context.CodeWriter.Write(node.TypeName);
|
||||||
|
context.CodeWriter.Write(">");
|
||||||
|
context.CodeWriter.Write("(");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Microsoft.AspNetCore.Components.EventCallback.Factory.Create(this, ...) OR
|
||||||
|
// Microsoft.AspNetCore.Components.EventCallback.Factory.Create<T>(this, ...)
|
||||||
|
|
||||||
|
context.CodeWriter.Write(ComponentsApi.EventCallback.FactoryAccessor);
|
||||||
|
context.CodeWriter.Write(".");
|
||||||
|
context.CodeWriter.Write(ComponentsApi.EventCallbackFactory.CreateMethod);
|
||||||
|
|
||||||
|
if (node.TryParseEventCallbackTypeArgument(out var argument))
|
||||||
|
{
|
||||||
|
context.CodeWriter.Write("<");
|
||||||
|
context.CodeWriter.Write(argument);
|
||||||
|
context.CodeWriter.Write(">");
|
||||||
|
}
|
||||||
|
|
||||||
|
context.CodeWriter.Write("(");
|
||||||
|
context.CodeWriter.Write("this");
|
||||||
|
context.CodeWriter.Write(", ");
|
||||||
|
|
||||||
|
for (var i = 0; i < tokens.Count; i++)
|
||||||
|
{
|
||||||
|
context.CodeWriter.Write(tokens[i].Content);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.CodeWriter.Write(")");
|
||||||
|
|
||||||
|
if (canTypeCheck && NeedsTypeCheck(node))
|
||||||
|
{
|
||||||
|
context.CodeWriter.Write(")");
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (canTypeCheck && NeedsTypeCheck(node))
|
if (canTypeCheck && NeedsTypeCheck(node))
|
||||||
|
|
|
||||||
|
|
@ -129,5 +129,25 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
|
||||||
{
|
{
|
||||||
public static readonly string FullTypeName = "Microsoft.AspNetCore.Components.ElementRef";
|
public static readonly string FullTypeName = "Microsoft.AspNetCore.Components.ElementRef";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class EventCallback
|
||||||
|
{
|
||||||
|
public static readonly string FullTypeName = "Microsoft.AspNetCore.Components.EventCallback";
|
||||||
|
public static readonly string MetadataName = FullTypeName;
|
||||||
|
|
||||||
|
public static readonly string FactoryAccessor = FullTypeName + ".Factory";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class EventCallbackOfT
|
||||||
|
{
|
||||||
|
public static readonly string MetadataName = "Microsoft.AspNetCore.Components.EventCallback`1";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class EventCallbackFactory
|
||||||
|
{
|
||||||
|
public static readonly string CreateMethod = "Create";
|
||||||
|
public static readonly string CreateInferredMethod = "CreateInferred";
|
||||||
|
public static readonly string CreateBinderMethod = "CreateBinder";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,25 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
|
||||||
string.Equals(value, bool.TrueString);
|
string.Equals(value, bool.TrueString);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the attribute is of type <c>EventCallback</c> or
|
||||||
|
/// <c>EventCallback{T}</c>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="attribute">The <see cref="BoundAttributeDescriptor"/>.</param>
|
||||||
|
/// <returns><c>true</c> if the attribute is an event callback, otherwise <c>false</c>.</returns>
|
||||||
|
public static bool IsEventCallbackProperty(this BoundAttributeDescriptor attribute)
|
||||||
|
{
|
||||||
|
if (attribute == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(attribute));
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = BlazorMetadata.Component.EventCallbackKey;
|
||||||
|
return
|
||||||
|
attribute.Metadata.TryGetValue(key, out var value) &&
|
||||||
|
string.Equals(value, bool.TrueString);
|
||||||
|
}
|
||||||
|
|
||||||
public static bool IsGenericTypedProperty(this BoundAttributeDescriptor attribute)
|
public static bool IsGenericTypedProperty(this BoundAttributeDescriptor attribute)
|
||||||
{
|
{
|
||||||
if (attribute == null)
|
if (attribute == null)
|
||||||
|
|
|
||||||
|
|
@ -101,5 +101,42 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
|
||||||
formatter.WriteProperty(nameof(TagHelper), TagHelper?.DisplayName);
|
formatter.WriteProperty(nameof(TagHelper), TagHelper?.DisplayName);
|
||||||
formatter.WriteProperty(nameof(TypeName), TypeName);
|
formatter.WriteProperty(nameof(TypeName), TypeName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool TryParseEventCallbackTypeArgument(out string argument)
|
||||||
|
{
|
||||||
|
// This is ugly and ad-hoc, but for various layering reasons we can't just use Roslyn APIs
|
||||||
|
// to parse this. We need to parse this just before we write it out to the code generator,
|
||||||
|
// so we can't compute it up front either.
|
||||||
|
|
||||||
|
if (BoundAttribute == null || !BoundAttribute.IsEventCallbackProperty())
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("This attribute is not an EventCallback attribute.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(TypeName, ComponentsApi.EventCallback.FullTypeName, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
// Non-Generic
|
||||||
|
argument = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TypeName != null &&
|
||||||
|
TypeName.Length > ComponentsApi.EventCallback.FullTypeName.Length + "<>".Length &&
|
||||||
|
TypeName.StartsWith(ComponentsApi.EventCallback.FullTypeName, StringComparison.Ordinal) &&
|
||||||
|
TypeName[ComponentsApi.EventCallback.FullTypeName.Length] == '<' &&
|
||||||
|
TypeName[TypeName.Length - 1] == '>')
|
||||||
|
{
|
||||||
|
// OK this is promising.
|
||||||
|
//
|
||||||
|
// Chop off leading `...EventCallback<` and let the length so the ending `>` is cut off as well.
|
||||||
|
argument = TypeName.Substring(ComponentsApi.EventCallback.FullTypeName.Length + 1, TypeName.Length - (ComponentsApi.EventCallback.FullTypeName.Length + "<>".Length));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we get here this is a failure. This should only happen if someone manages to mangle the name with extensibility.
|
||||||
|
// We don't really want to crash though.
|
||||||
|
argument = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ namespace Microsoft.CodeAnalysis.Razor
|
||||||
// We generate:
|
// We generate:
|
||||||
// <input type="text"
|
// <input type="text"
|
||||||
// value="@BindMethods.GetValue(FirstName)"
|
// value="@BindMethods.GetValue(FirstName)"
|
||||||
// onchange="@BindMethods.SetValue(__value => FirstName = __value, FirstName)"/>
|
// onchange="@EventCallbackFactory.CreateBinder(this, __value => FirstName = __value, FirstName)"/>
|
||||||
//
|
//
|
||||||
// This isn't very different from code the user could write themselves - thus the pronouncement
|
// This isn't very different from code the user could write themselves - thus the pronouncement
|
||||||
// that bind is very much like a macro.
|
// that bind is very much like a macro.
|
||||||
|
|
@ -356,7 +356,13 @@ namespace Microsoft.CodeAnalysis.Razor
|
||||||
for (var i = 0; i < tagHelper.BoundAttributes.Count; i++)
|
for (var i = 0; i < tagHelper.BoundAttributes.Count; i++)
|
||||||
{
|
{
|
||||||
var changeAttribute = tagHelper.BoundAttributes[i];
|
var changeAttribute = tagHelper.BoundAttributes[i];
|
||||||
if (!changeAttribute.Name.EndsWith("Changed") || !changeAttribute.IsDelegateProperty())
|
if (!changeAttribute.Name.EndsWith("Changed") ||
|
||||||
|
|
||||||
|
// Allow the ValueChanged attribute to be a delegate or EventCallback<>.
|
||||||
|
//
|
||||||
|
// We assume that the Delegate or EventCallback<> has a matching type, and the C# compiler will help
|
||||||
|
// you figure figure it out if you did it wrongly.
|
||||||
|
(!changeAttribute.IsDelegateProperty() && !changeAttribute.IsEventCallbackProperty()))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -367,7 +373,7 @@ namespace Microsoft.CodeAnalysis.Razor
|
||||||
var expressionAttributeName = valueAttributeName + "Expression";
|
var expressionAttributeName = valueAttributeName + "Expression";
|
||||||
for (var j = 0; j < tagHelper.BoundAttributes.Count; j++)
|
for (var j = 0; j < tagHelper.BoundAttributes.Count; j++)
|
||||||
{
|
{
|
||||||
if (tagHelper.BoundAttributes[j].Name == valueAttributeName && !tagHelper.BoundAttributes[j].IsDelegateProperty())
|
if (tagHelper.BoundAttributes[j].Name == valueAttributeName)
|
||||||
{
|
{
|
||||||
valueAttribute = tagHelper.BoundAttributes[j];
|
valueAttribute = tagHelper.BoundAttributes[j];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ namespace Microsoft.CodeAnalysis.Razor
|
||||||
// We need to see private members too
|
// We need to see private members too
|
||||||
compilation = WithMetadataImportOptionsAll(compilation);
|
compilation = WithMetadataImportOptionsAll(compilation);
|
||||||
|
|
||||||
var symbols = BlazorSymbols.Create(compilation);
|
var symbols = ComponentSymbols.Create(compilation);
|
||||||
|
|
||||||
var types = new List<INamedTypeSymbol>();
|
var types = new List<INamedTypeSymbol>();
|
||||||
var visitor = new ComponentTypeVisitor(symbols, types);
|
var visitor = new ComponentTypeVisitor(symbols, types);
|
||||||
|
|
@ -81,7 +81,7 @@ namespace Microsoft.CodeAnalysis.Razor
|
||||||
return compilation.WithOptions(newCompilationOptions);
|
return compilation.WithOptions(newCompilationOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
private TagHelperDescriptor CreateDescriptor(BlazorSymbols symbols, INamedTypeSymbol type)
|
private TagHelperDescriptor CreateDescriptor(ComponentSymbols symbols, INamedTypeSymbol type)
|
||||||
{
|
{
|
||||||
var typeName = type.ToDisplayString(FullNameTypeDisplayFormat);
|
var typeName = type.ToDisplayString(FullNameTypeDisplayFormat);
|
||||||
var assemblyName = type.ContainingAssembly.Identity.Name;
|
var assemblyName = type.ContainingAssembly.Identity.Name;
|
||||||
|
|
@ -157,6 +157,11 @@ namespace Microsoft.CodeAnalysis.Razor
|
||||||
pb.Metadata.Add(BlazorMetadata.Component.ChildContentKey, bool.TrueString);
|
pb.Metadata.Add(BlazorMetadata.Component.ChildContentKey, bool.TrueString);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (kind == PropertyKind.EventCallback)
|
||||||
|
{
|
||||||
|
pb.Metadata.Add(BlazorMetadata.Component.EventCallbackKey, bool.TrueString);
|
||||||
|
}
|
||||||
|
|
||||||
if (kind == PropertyKind.Delegate)
|
if (kind == PropertyKind.Delegate)
|
||||||
{
|
{
|
||||||
pb.Metadata.Add(BlazorMetadata.Component.DelegateSignatureKey, bool.TrueString);
|
pb.Metadata.Add(BlazorMetadata.Component.DelegateSignatureKey, bool.TrueString);
|
||||||
|
|
@ -230,7 +235,7 @@ namespace Microsoft.CodeAnalysis.Razor
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private TagHelperDescriptor CreateChildContentDescriptor(BlazorSymbols symbols, TagHelperDescriptor component, BoundAttributeDescriptor attribute)
|
private TagHelperDescriptor CreateChildContentDescriptor(ComponentSymbols symbols, TagHelperDescriptor component, BoundAttributeDescriptor attribute)
|
||||||
{
|
{
|
||||||
var typeName = component.GetTypeName() + "." + attribute.Name;
|
var typeName = component.GetTypeName() + "." + attribute.Name;
|
||||||
var assemblyName = component.AssemblyName;
|
var assemblyName = component.AssemblyName;
|
||||||
|
|
@ -297,7 +302,7 @@ namespace Microsoft.CodeAnalysis.Razor
|
||||||
// - have the [Parameter] attribute
|
// - have the [Parameter] attribute
|
||||||
// - have a setter, even if private
|
// - have a setter, even if private
|
||||||
// - are not indexers
|
// - are not indexers
|
||||||
private IEnumerable<(IPropertySymbol property, PropertyKind kind)> GetProperties(BlazorSymbols symbols, INamedTypeSymbol type)
|
private IEnumerable<(IPropertySymbol property, PropertyKind kind)> GetProperties(ComponentSymbols symbols, INamedTypeSymbol type)
|
||||||
{
|
{
|
||||||
var properties = new Dictionary<string, (IPropertySymbol, PropertyKind)>(StringComparer.Ordinal);
|
var properties = new Dictionary<string, (IPropertySymbol, PropertyKind)>(StringComparer.Ordinal);
|
||||||
do
|
do
|
||||||
|
|
@ -368,6 +373,19 @@ namespace Microsoft.CodeAnalysis.Razor
|
||||||
kind = PropertyKind.ChildContent;
|
kind = PropertyKind.ChildContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (kind == PropertyKind.Default && property.Type == symbols.EventCallback)
|
||||||
|
{
|
||||||
|
kind = PropertyKind.EventCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kind == PropertyKind.Default &&
|
||||||
|
property.Type is INamedTypeSymbol namedType2 &&
|
||||||
|
namedType2.IsGenericType &&
|
||||||
|
namedType2.ConstructedFrom == symbols.EventCallbackOfT)
|
||||||
|
{
|
||||||
|
kind = PropertyKind.EventCallback;
|
||||||
|
}
|
||||||
|
|
||||||
if (kind == PropertyKind.Default && property.Type.TypeKind == TypeKind.Delegate)
|
if (kind == PropertyKind.Default && property.Type.TypeKind == TypeKind.Delegate)
|
||||||
{
|
{
|
||||||
kind = PropertyKind.Delegate;
|
kind = PropertyKind.Delegate;
|
||||||
|
|
@ -390,13 +408,18 @@ namespace Microsoft.CodeAnalysis.Razor
|
||||||
Enum,
|
Enum,
|
||||||
ChildContent,
|
ChildContent,
|
||||||
Delegate,
|
Delegate,
|
||||||
|
EventCallback,
|
||||||
}
|
}
|
||||||
|
|
||||||
private class BlazorSymbols
|
private class ComponentSymbols
|
||||||
{
|
{
|
||||||
public static BlazorSymbols Create(Compilation compilation)
|
public static ComponentSymbols Create(Compilation compilation)
|
||||||
{
|
{
|
||||||
var symbols = new BlazorSymbols();
|
// We find a bunch of important and fundamental types here that are needed to discover
|
||||||
|
// components. If one of these isn't defined then we just bail, because the results will
|
||||||
|
// be unpredictable.
|
||||||
|
var symbols = new ComponentSymbols();
|
||||||
|
|
||||||
symbols.ComponentBase = compilation.GetTypeByMetadataName(ComponentsApi.ComponentBase.MetadataName);
|
symbols.ComponentBase = compilation.GetTypeByMetadataName(ComponentsApi.ComponentBase.MetadataName);
|
||||||
if (symbols.ComponentBase == null)
|
if (symbols.ComponentBase == null)
|
||||||
{
|
{
|
||||||
|
|
@ -422,18 +445,34 @@ namespace Microsoft.CodeAnalysis.Razor
|
||||||
if (symbols.RenderFragment == null)
|
if (symbols.RenderFragment == null)
|
||||||
{
|
{
|
||||||
// No definition for RenderFragment, nothing to do.
|
// No definition for RenderFragment, nothing to do.
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
symbols.RenderFragmentOfT = compilation.GetTypeByMetadataName(ComponentsApi.RenderFragmentOfT.MetadataName);
|
symbols.RenderFragmentOfT = compilation.GetTypeByMetadataName(ComponentsApi.RenderFragmentOfT.MetadataName);
|
||||||
if (symbols.RenderFragmentOfT == null)
|
if (symbols.RenderFragmentOfT == null)
|
||||||
{
|
{
|
||||||
// No definition for RenderFragment, nothing to do.
|
// No definition for RenderFragment<T>, nothing to do.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
symbols.EventCallback = compilation.GetTypeByMetadataName(ComponentsApi.EventCallback.MetadataName);
|
||||||
|
if (symbols.EventCallback == null)
|
||||||
|
{
|
||||||
|
// No definition for EventCallback, nothing to do.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
symbols.EventCallbackOfT = compilation.GetTypeByMetadataName(ComponentsApi.EventCallbackOfT.MetadataName);
|
||||||
|
if (symbols.EventCallbackOfT == null)
|
||||||
|
{
|
||||||
|
// No definition for EventCallback<T>, nothing to do.
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return symbols;
|
return symbols;
|
||||||
}
|
}
|
||||||
|
|
||||||
private BlazorSymbols()
|
private ComponentSymbols()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -446,14 +485,18 @@ namespace Microsoft.CodeAnalysis.Razor
|
||||||
public INamedTypeSymbol RenderFragment { get; private set; }
|
public INamedTypeSymbol RenderFragment { get; private set; }
|
||||||
|
|
||||||
public INamedTypeSymbol RenderFragmentOfT { get; private set; }
|
public INamedTypeSymbol RenderFragmentOfT { get; private set; }
|
||||||
|
|
||||||
|
public INamedTypeSymbol EventCallback { get; private set; }
|
||||||
|
|
||||||
|
public INamedTypeSymbol EventCallbackOfT { get; private set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ComponentTypeVisitor : SymbolVisitor
|
private class ComponentTypeVisitor : SymbolVisitor
|
||||||
{
|
{
|
||||||
private readonly BlazorSymbols _symbols;
|
private readonly ComponentSymbols _symbols;
|
||||||
private readonly List<INamedTypeSymbol> _results;
|
private readonly List<INamedTypeSymbol> _results;
|
||||||
|
|
||||||
public ComponentTypeVisitor(BlazorSymbols symbols, List<INamedTypeSymbol> results)
|
public ComponentTypeVisitor(ComponentSymbols symbols, List<INamedTypeSymbol> results)
|
||||||
{
|
{
|
||||||
_symbols = symbols;
|
_symbols = symbols;
|
||||||
_results = results;
|
_results = results;
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ namespace Microsoft.CodeAnalysis.Razor
|
||||||
public class BindTagHelperDescriptorProviderTest : BaseTagHelperDescriptorProviderTest
|
public class BindTagHelperDescriptorProviderTest : BaseTagHelperDescriptorProviderTest
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Execute_FindsBindTagHelperOnComponentType_CreatesDescriptor()
|
public void Execute_FindsBindTagHelperOnComponentType_Delegate_CreatesDescriptor()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
|
var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
|
||||||
|
|
@ -130,6 +130,120 @@ namespace Test
|
||||||
Assert.False(attribute.IsEnum);
|
Assert.False(attribute.IsEnum);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Execute_FindsBindTagHelperOnComponentType_EventCallback_CreatesDescriptor()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
public class MyComponent : IComponent
|
||||||
|
{
|
||||||
|
public void Init(RenderHandle renderHandle) { }
|
||||||
|
|
||||||
|
public void SetParameters(ParameterCollection parameters) { }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
string MyProperty { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
EventCallback<string> MyPropertyChanged { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"));
|
||||||
|
|
||||||
|
Assert.Empty(compilation.GetDiagnostics());
|
||||||
|
|
||||||
|
var context = TagHelperDescriptorProviderContext.Create();
|
||||||
|
context.SetCompilation(compilation);
|
||||||
|
|
||||||
|
// We run after component discovery and depend on the results.
|
||||||
|
var componentProvider = new ComponentTagHelperDescriptorProvider();
|
||||||
|
componentProvider.Execute(context);
|
||||||
|
|
||||||
|
var provider = new BindTagHelperDescriptorProvider();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
provider.Execute(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var matches = GetBindTagHelpers(context);
|
||||||
|
var bind = Assert.Single(matches);
|
||||||
|
|
||||||
|
// These are features Bind Tags Helpers don't use. Verifying them once here and
|
||||||
|
// then ignoring them.
|
||||||
|
Assert.Empty(bind.AllowedChildTags);
|
||||||
|
Assert.Null(bind.TagOutputHint);
|
||||||
|
|
||||||
|
// These are features that are invariants of all Bind Tag Helpers. Verifying them once
|
||||||
|
// here and then ignoring them.
|
||||||
|
Assert.Empty(bind.Diagnostics);
|
||||||
|
Assert.False(bind.HasErrors);
|
||||||
|
Assert.Equal(BlazorMetadata.Bind.TagHelperKind, bind.Kind);
|
||||||
|
Assert.Equal(BlazorMetadata.Bind.RuntimeName, bind.Metadata[TagHelperMetadata.Runtime.Name]);
|
||||||
|
Assert.False(bind.IsDefaultKind());
|
||||||
|
Assert.False(bind.KindUsesDefaultTagHelperRuntime());
|
||||||
|
|
||||||
|
Assert.Equal("MyProperty", bind.Metadata[BlazorMetadata.Bind.ValueAttribute]);
|
||||||
|
Assert.Equal("MyPropertyChanged", bind.Metadata[BlazorMetadata.Bind.ChangeAttribute]);
|
||||||
|
|
||||||
|
Assert.Equal(
|
||||||
|
"Binds the provided expression to the 'MyProperty' property and a change event " +
|
||||||
|
"delegate to the 'MyPropertyChanged' property of the component.",
|
||||||
|
bind.Documentation);
|
||||||
|
|
||||||
|
// These are all trivially derived from the assembly/namespace/type name
|
||||||
|
Assert.Equal("TestAssembly", bind.AssemblyName);
|
||||||
|
Assert.Equal("Test.MyComponent", bind.Name);
|
||||||
|
Assert.Equal("Test.MyComponent", bind.DisplayName);
|
||||||
|
Assert.Equal("Test.MyComponent", bind.GetTypeName());
|
||||||
|
|
||||||
|
var rule = Assert.Single(bind.TagMatchingRules);
|
||||||
|
Assert.Empty(rule.Diagnostics);
|
||||||
|
Assert.False(rule.HasErrors);
|
||||||
|
Assert.Null(rule.ParentTag);
|
||||||
|
Assert.Equal("MyComponent", rule.TagName);
|
||||||
|
Assert.Equal(TagStructure.Unspecified, rule.TagStructure);
|
||||||
|
|
||||||
|
var requiredAttribute = Assert.Single(rule.Attributes);
|
||||||
|
Assert.Empty(requiredAttribute.Diagnostics);
|
||||||
|
Assert.Equal("bind-MyProperty", requiredAttribute.DisplayName);
|
||||||
|
Assert.Equal("bind-MyProperty", requiredAttribute.Name);
|
||||||
|
Assert.Equal(RequiredAttributeDescriptor.NameComparisonMode.FullMatch, requiredAttribute.NameComparison);
|
||||||
|
Assert.Null(requiredAttribute.Value);
|
||||||
|
Assert.Equal(RequiredAttributeDescriptor.ValueComparisonMode.None, requiredAttribute.ValueComparison);
|
||||||
|
|
||||||
|
var attribute = Assert.Single(bind.BoundAttributes);
|
||||||
|
|
||||||
|
// Invariants
|
||||||
|
Assert.Empty(attribute.Diagnostics);
|
||||||
|
Assert.False(attribute.HasErrors);
|
||||||
|
Assert.Equal(BlazorMetadata.Bind.TagHelperKind, attribute.Kind);
|
||||||
|
Assert.False(attribute.IsDefaultKind());
|
||||||
|
Assert.False(attribute.HasIndexer);
|
||||||
|
Assert.Null(attribute.IndexerNamePrefix);
|
||||||
|
Assert.Null(attribute.IndexerTypeName);
|
||||||
|
Assert.False(attribute.IsIndexerBooleanProperty);
|
||||||
|
Assert.False(attribute.IsIndexerStringProperty);
|
||||||
|
|
||||||
|
Assert.Equal(
|
||||||
|
"Binds the provided expression to the 'MyProperty' property and a change event " +
|
||||||
|
"delegate to the 'MyPropertyChanged' property of the component.",
|
||||||
|
attribute.Documentation);
|
||||||
|
|
||||||
|
Assert.Equal("bind-MyProperty", attribute.Name);
|
||||||
|
Assert.Equal("MyProperty", attribute.GetPropertyName());
|
||||||
|
Assert.Equal("string Test.MyComponent.MyProperty", attribute.DisplayName);
|
||||||
|
|
||||||
|
// Defined from the property type
|
||||||
|
Assert.Equal("System.String", attribute.TypeName);
|
||||||
|
Assert.True(attribute.IsStringProperty);
|
||||||
|
Assert.False(attribute.IsBooleanProperty);
|
||||||
|
Assert.False(attribute.IsEnum);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Execute_NoMatchedPropertiesOnComponent_IgnoresComponent()
|
public void Execute_NoMatchedPropertiesOnComponent_IgnoresComponent()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -577,6 +577,171 @@ namespace Test
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Execute_EventCallbackProperty_CreatesDescriptor()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
|
||||||
|
var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
public class MyComponent : ComponentBase
|
||||||
|
{
|
||||||
|
[Parameter]
|
||||||
|
EventCallback OnClick { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"));
|
||||||
|
|
||||||
|
Assert.Empty(compilation.GetDiagnostics());
|
||||||
|
|
||||||
|
var context = TagHelperDescriptorProviderContext.Create();
|
||||||
|
context.SetCompilation(compilation);
|
||||||
|
|
||||||
|
var provider = new ComponentTagHelperDescriptorProvider();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
provider.Execute(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var components = ExcludeBuiltInComponents(context);
|
||||||
|
var component = Assert.Single(components);
|
||||||
|
|
||||||
|
Assert.Equal("TestAssembly", component.AssemblyName);
|
||||||
|
Assert.Equal("Test.MyComponent", component.Name);
|
||||||
|
|
||||||
|
var attribute = Assert.Single(component.BoundAttributes);
|
||||||
|
Assert.Equal("OnClick", attribute.Name);
|
||||||
|
Assert.Equal("Microsoft.AspNetCore.Components.EventCallback", attribute.TypeName);
|
||||||
|
|
||||||
|
Assert.False(attribute.HasIndexer);
|
||||||
|
Assert.False(attribute.IsBooleanProperty);
|
||||||
|
Assert.False(attribute.IsEnum);
|
||||||
|
Assert.False(attribute.IsStringProperty);
|
||||||
|
Assert.True(attribute.IsEventCallbackProperty());
|
||||||
|
Assert.False(attribute.IsDelegateProperty());
|
||||||
|
Assert.False(attribute.IsChildContentProperty());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Execute_EventCallbackProperty_CreatesDescriptor_ClosedGeneric()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
|
||||||
|
var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
public class MyComponent : ComponentBase
|
||||||
|
{
|
||||||
|
[Parameter]
|
||||||
|
EventCallback<UIMouseEventArgs> OnClick { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"));
|
||||||
|
|
||||||
|
Assert.Empty(compilation.GetDiagnostics());
|
||||||
|
|
||||||
|
var context = TagHelperDescriptorProviderContext.Create();
|
||||||
|
context.SetCompilation(compilation);
|
||||||
|
|
||||||
|
var provider = new ComponentTagHelperDescriptorProvider();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
provider.Execute(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var components = ExcludeBuiltInComponents(context);
|
||||||
|
var component = Assert.Single(components);
|
||||||
|
|
||||||
|
Assert.Equal("TestAssembly", component.AssemblyName);
|
||||||
|
Assert.Equal("Test.MyComponent", component.Name);
|
||||||
|
|
||||||
|
Assert.Collection(
|
||||||
|
component.BoundAttributes.OrderBy(a => a.Name),
|
||||||
|
a =>
|
||||||
|
{
|
||||||
|
Assert.Equal("OnClick", a.Name);
|
||||||
|
Assert.Equal("Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.UIMouseEventArgs>", a.TypeName);
|
||||||
|
Assert.False(a.HasIndexer);
|
||||||
|
Assert.False(a.IsBooleanProperty);
|
||||||
|
Assert.False(a.IsEnum);
|
||||||
|
Assert.False(a.IsStringProperty);
|
||||||
|
Assert.True(a.IsEventCallbackProperty());
|
||||||
|
Assert.False(a.IsDelegateProperty());
|
||||||
|
Assert.False(a.IsChildContentProperty());
|
||||||
|
Assert.False(a.IsGenericTypedProperty());
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Execute_EventCallbackProperty_CreatesDescriptor_OpenGeneric()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
|
||||||
|
var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
public class MyComponent<T> : ComponentBase
|
||||||
|
{
|
||||||
|
[Parameter]
|
||||||
|
EventCallback<T> OnClick { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"));
|
||||||
|
|
||||||
|
Assert.Empty(compilation.GetDiagnostics());
|
||||||
|
|
||||||
|
var context = TagHelperDescriptorProviderContext.Create();
|
||||||
|
context.SetCompilation(compilation);
|
||||||
|
|
||||||
|
var provider = new ComponentTagHelperDescriptorProvider();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
provider.Execute(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var components = ExcludeBuiltInComponents(context);
|
||||||
|
var component = Assert.Single(components);
|
||||||
|
|
||||||
|
Assert.Equal("TestAssembly", component.AssemblyName);
|
||||||
|
Assert.Equal("Test.MyComponent<T>", component.Name);
|
||||||
|
|
||||||
|
Assert.Collection(
|
||||||
|
component.BoundAttributes.OrderBy(a => a.Name),
|
||||||
|
a =>
|
||||||
|
{
|
||||||
|
Assert.Equal("OnClick", a.Name);
|
||||||
|
Assert.Equal("Microsoft.AspNetCore.Components.EventCallback<T>", a.TypeName);
|
||||||
|
Assert.False(a.HasIndexer);
|
||||||
|
Assert.False(a.IsBooleanProperty);
|
||||||
|
Assert.False(a.IsEnum);
|
||||||
|
Assert.False(a.IsStringProperty);
|
||||||
|
Assert.True(a.IsEventCallbackProperty());
|
||||||
|
Assert.False(a.IsDelegateProperty());
|
||||||
|
Assert.False(a.IsChildContentProperty());
|
||||||
|
Assert.True(a.IsGenericTypedProperty());
|
||||||
|
|
||||||
|
},
|
||||||
|
a =>
|
||||||
|
{
|
||||||
|
Assert.Equal("T", a.Name);
|
||||||
|
Assert.Equal("T", a.GetPropertyName());
|
||||||
|
Assert.Equal("T", a.DisplayName);
|
||||||
|
Assert.Equal("System.Type", a.TypeName);
|
||||||
|
Assert.True(a.IsTypeParameterProperty());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Execute_RenderFragmentProperty_CreatesDescriptors()
|
public void Execute_RenderFragmentProperty_CreatesDescriptors()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,24 @@ namespace Microsoft.AspNetCore.Components
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Not intended to be used directly.
|
||||||
|
/// </summary>
|
||||||
|
public static EventCallback GetEventHandlerValue<T>(EventCallback value)
|
||||||
|
where T : UIEventArgs
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Not intended to be used directly.
|
||||||
|
/// </summary>
|
||||||
|
public static EventCallback<T> GetEventHandlerValue<T>(EventCallback<T> value)
|
||||||
|
where T : UIEventArgs
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Not intended to be used directly.
|
/// Not intended to be used directly.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components
|
||||||
|
{
|
||||||
|
public readonly struct EventCallback
|
||||||
|
{
|
||||||
|
public static readonly EventCallbackFactory Factory = new EventCallbackFactory();
|
||||||
|
|
||||||
|
internal readonly MulticastDelegate Delegate;
|
||||||
|
internal readonly IHandleEvent Receiver;
|
||||||
|
|
||||||
|
public EventCallback(IHandleEvent receiver, MulticastDelegate @delegate)
|
||||||
|
{
|
||||||
|
Receiver = receiver;
|
||||||
|
Delegate = @delegate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct EventCallback<T>
|
||||||
|
{
|
||||||
|
internal readonly MulticastDelegate Delegate;
|
||||||
|
internal readonly IHandleEvent Receiver;
|
||||||
|
|
||||||
|
public EventCallback(IHandleEvent receiver, MulticastDelegate @delegate)
|
||||||
|
{
|
||||||
|
Receiver = receiver;
|
||||||
|
Delegate = @delegate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components
|
||||||
|
{
|
||||||
|
public sealed class EventCallbackFactory
|
||||||
|
{
|
||||||
|
public EventCallback Create(object receiver, EventCallback callback) => default;
|
||||||
|
|
||||||
|
public EventCallback Create(object receiver, Action callback) => default;
|
||||||
|
|
||||||
|
public EventCallback Create(object receiver, Action<object> callback) => default;
|
||||||
|
|
||||||
|
public EventCallback Create(object receiver, Func<Task> callback) => default;
|
||||||
|
|
||||||
|
public EventCallback Create(object receiver, Func<object, Task> callback) => default;
|
||||||
|
|
||||||
|
public EventCallback<T> Create<T>(object receiver, string callback) => default;
|
||||||
|
|
||||||
|
public EventCallback<T> Create<T>(object receiver, EventCallback<T> callback) => default;
|
||||||
|
|
||||||
|
public EventCallback<T> Create<T>(object receiver, Action callback) => default;
|
||||||
|
|
||||||
|
public EventCallback<T> Create<T>(object receiver, Action<T> callback) => default;
|
||||||
|
|
||||||
|
public EventCallback<T> Create<T>(object receiver, Func<Task> callback) => default;
|
||||||
|
|
||||||
|
public EventCallback<T> Create<T>(object receiver, Func<T, Task> callback) => default;
|
||||||
|
|
||||||
|
public EventCallback<T> CreateInferred<T>(object receiver, Action<T> callback, T value)
|
||||||
|
{
|
||||||
|
return Create(receiver, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EventCallback<T> CreateInferred<T>(object receiver, Func<T, Task> callback, T value)
|
||||||
|
{
|
||||||
|
return Create(receiver, callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components
|
||||||
|
{
|
||||||
|
public static class EventCallbackFactoryBinderExtensions
|
||||||
|
{
|
||||||
|
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||||
|
this EventCallbackFactory factory,
|
||||||
|
object receiver,
|
||||||
|
Action<string> setter,
|
||||||
|
string existingValue) => default;
|
||||||
|
|
||||||
|
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||||
|
this EventCallbackFactory factory,
|
||||||
|
object receiver,
|
||||||
|
Action<bool> setter,
|
||||||
|
bool existingValue) => default;
|
||||||
|
|
||||||
|
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||||
|
this EventCallbackFactory factory,
|
||||||
|
object receiver,
|
||||||
|
Action<bool?> setter,
|
||||||
|
bool? existingValue) => default;
|
||||||
|
|
||||||
|
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||||
|
this EventCallbackFactory factory,
|
||||||
|
object receiver,
|
||||||
|
Action<int> setter,
|
||||||
|
int existingValue) => default;
|
||||||
|
|
||||||
|
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||||
|
this EventCallbackFactory factory,
|
||||||
|
object receiver,
|
||||||
|
Action<int?> setter,
|
||||||
|
int? existingValue) => default;
|
||||||
|
|
||||||
|
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||||
|
this EventCallbackFactory factory,
|
||||||
|
object receiver,
|
||||||
|
Action<long> setter,
|
||||||
|
long existingValue) => default;
|
||||||
|
|
||||||
|
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||||
|
this EventCallbackFactory factory,
|
||||||
|
object receiver,
|
||||||
|
Action<long?> setter,
|
||||||
|
long? existingValue) => default;
|
||||||
|
|
||||||
|
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||||
|
this EventCallbackFactory factory,
|
||||||
|
object receiver,
|
||||||
|
Action<float> setter,
|
||||||
|
float existingValue) => default;
|
||||||
|
|
||||||
|
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||||
|
this EventCallbackFactory factory,
|
||||||
|
object receiver,
|
||||||
|
Action<float?> setter,
|
||||||
|
float? existingValue) => default;
|
||||||
|
|
||||||
|
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||||
|
this EventCallbackFactory factory,
|
||||||
|
object receiver,
|
||||||
|
Action<double> setter,
|
||||||
|
double existingValue) => default;
|
||||||
|
|
||||||
|
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||||
|
this EventCallbackFactory factory,
|
||||||
|
object receiver,
|
||||||
|
Action<double?> setter,
|
||||||
|
double? existingValue) => default;
|
||||||
|
|
||||||
|
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||||
|
this EventCallbackFactory factory,
|
||||||
|
object receiver,
|
||||||
|
Action<decimal> setter,
|
||||||
|
decimal existingValue) => default;
|
||||||
|
|
||||||
|
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||||
|
this EventCallbackFactory factory,
|
||||||
|
object receiver,
|
||||||
|
Action<decimal?> setter,
|
||||||
|
decimal? existingValue) => default;
|
||||||
|
|
||||||
|
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||||
|
this EventCallbackFactory factory,
|
||||||
|
object receiver,
|
||||||
|
Action<DateTime> setter,
|
||||||
|
DateTime existingValue) => default;
|
||||||
|
|
||||||
|
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||||
|
this EventCallbackFactory factory,
|
||||||
|
object receiver,
|
||||||
|
Action<DateTime> setter,
|
||||||
|
DateTime existingValue,
|
||||||
|
string format) => default;
|
||||||
|
|
||||||
|
public static EventCallback<UIChangeEventArgs> CreateBinder<T>(
|
||||||
|
this EventCallbackFactory factory,
|
||||||
|
object receiver,
|
||||||
|
Action<T> setter,
|
||||||
|
T existingValue) where T : Enum => default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components
|
||||||
|
{
|
||||||
|
public static class EventCallbackFactoryUIEventArgsExtensions
|
||||||
|
{
|
||||||
|
public static EventCallback<UIEventArgs> Create(this EventCallbackFactory factory, object receiver, Action<UIEventArgs> callback) => default;
|
||||||
|
|
||||||
|
public static EventCallback<UIEventArgs> Create(this EventCallbackFactory factory, object receiver, Func<UIEventArgs, Task> callback) => default;
|
||||||
|
|
||||||
|
public static EventCallback<UIChangeEventArgs> Create(this EventCallbackFactory factory, object receiver, Action<UIChangeEventArgs> callback) => default;
|
||||||
|
|
||||||
|
public static EventCallback<UIChangeEventArgs> Create(this EventCallbackFactory factory, object receiver, Func<UIChangeEventArgs, Task> callback) => default;
|
||||||
|
|
||||||
|
public static EventCallback<UIClipboardEventArgs> Create(this EventCallbackFactory factory, object receiver, Action<UIClipboardEventArgs> callback) => default;
|
||||||
|
|
||||||
|
public static EventCallback<UIClipboardEventArgs> Create(this EventCallbackFactory factory, object receiver, Func<UIClipboardEventArgs, Task> callback) => default;
|
||||||
|
|
||||||
|
public static EventCallback<UIDragEventArgs> Create(this EventCallbackFactory factory, object receiver, Action<UIDragEventArgs> callback) => default;
|
||||||
|
|
||||||
|
public static EventCallback<UIDragEventArgs> Create(this EventCallbackFactory factory, object receiver, Func<UIDragEventArgs, Task> callback) => default;
|
||||||
|
|
||||||
|
public static EventCallback<UIErrorEventArgs> Create(this EventCallbackFactory factory, object receiver, Action<UIErrorEventArgs> callback) => default;
|
||||||
|
|
||||||
|
public static EventCallback<UIErrorEventArgs> Create(this EventCallbackFactory factory, object receiver, Func<UIErrorEventArgs, Task> callback) => default;
|
||||||
|
|
||||||
|
public static EventCallback<UIFocusEventArgs> Create(this EventCallbackFactory factory, object receiver, Action<UIFocusEventArgs> callback) => default;
|
||||||
|
|
||||||
|
public static EventCallback<UIFocusEventArgs> Create(this EventCallbackFactory factory, object receiver, Func<UIFocusEventArgs, Task> callback) => default;
|
||||||
|
|
||||||
|
public static EventCallback<UIKeyboardEventArgs> Create(this EventCallbackFactory factory, object receiver, Action<UIKeyboardEventArgs> callback) => default;
|
||||||
|
|
||||||
|
public static EventCallback<UIKeyboardEventArgs> Create(this EventCallbackFactory factory, object receiver, Func<UIKeyboardEventArgs, Task> callback) => default;
|
||||||
|
|
||||||
|
public static EventCallback<UIMouseEventArgs> Create(this EventCallbackFactory factory, object receiver, Action<UIMouseEventArgs> callback) => default;
|
||||||
|
|
||||||
|
public static EventCallback<UIMouseEventArgs> Create(this EventCallbackFactory factory, object receiver, Func<UIMouseEventArgs, Task> callback) => default;
|
||||||
|
|
||||||
|
public static EventCallback<UIPointerEventArgs> Create(this EventCallbackFactory factory, object receiver, Action<UIPointerEventArgs> callback) => default;
|
||||||
|
|
||||||
|
public static EventCallback<UIPointerEventArgs> Create(this EventCallbackFactory factory, object receiver, Func<UIPointerEventArgs, Task> callback) => default;
|
||||||
|
|
||||||
|
public static EventCallback<UIProgressEventArgs> Create(this EventCallbackFactory factory, object receiver, Action<UIProgressEventArgs> callback) => default;
|
||||||
|
|
||||||
|
public static EventCallback<UIProgressEventArgs> Create(this EventCallbackFactory factory, object receiver, Func<UIProgressEventArgs, Task> callback) => default;
|
||||||
|
|
||||||
|
public static EventCallback<UITouchEventArgs> Create(this EventCallbackFactory factory, object receiver, Action<UITouchEventArgs> callback) => default;
|
||||||
|
|
||||||
|
public static EventCallback<UITouchEventArgs> Create(this EventCallbackFactory factory, object receiver, Func<UITouchEventArgs, Task> callback) => default;
|
||||||
|
|
||||||
|
public static EventCallback<UIWheelEventArgs> Create(this EventCallbackFactory factory, object receiver, Action<UIWheelEventArgs> callback) => default;
|
||||||
|
|
||||||
|
public static EventCallback<UIWheelEventArgs> Create(this EventCallbackFactory factory, object receiver, Func<UIWheelEventArgs, Task> callback) => default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components
|
||||||
|
{
|
||||||
|
public interface IHandleEvent
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -72,6 +72,14 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void AddAttribute(int sequence, string name, EventCallback value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddAttribute<T>(int sequence, string name, EventCallback<T> value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public void AddAttribute(int sequence, string name, object value)
|
public void AddAttribute(int sequence, string name, object value)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue