You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Connected.Components/Services/KeyInterceptor/KeyInterceptor.cs

106 lines
3.4 KiB

2 years ago
// Copyright (c) MudBlazor 2021
// MudBlazor licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.JSInterop;
namespace Connected.Services;
/// <summary>
/// This transient service binds itself to a parent element to observe the keys of one of its children.
/// It can call preventDefault or stopPropagation directly on the JavaScript side for single key strokes / key combinations as per configuration.
/// Furthermore, you can precisely subscribe single keystrokes or combinations and only the subscribed ones will be forwarded into .NET
/// </summary>
public class KeyInterceptor : IKeyInterceptor, IDisposable
{
private bool _isDisposed = false;
private readonly DotNetObjectReference<KeyInterceptor> _dotNetRef;
private readonly IJSRuntime _jsRuntime;
private bool _isObserving;
private string _elementId;
[DynamicDependency(nameof(OnKeyDown))]
[DynamicDependency(nameof(OnKeyUp))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(KeyboardEvent))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(KeyboardEventArgs))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(KeyOptions))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(KeyInterceptorOptions))]
public KeyInterceptor(IJSRuntime jsRuntime)
{
_dotNetRef = DotNetObjectReference.Create(this);
_jsRuntime = jsRuntime;
}
/// <summary>
/// Connect to the ancestor element of the element(s) that should be observed
/// </summary>
/// <param name="elementId">Ancestor html element id</param>
/// <param name="options">Define here the descendant(s) by setting TargetClass and the keystrokes to be monitored / suppressed</param>
public async Task Connect(string elementId, KeyInterceptorOptions options)
{
if (_isObserving || _isDisposed)
return;
_elementId = elementId;
_isObserving = await _jsRuntime.InvokeVoidAsyncWithErrorHandling("mudKeyInterceptor.connect", _dotNetRef, elementId, options);
}
/// <summary>
/// Update behavior of a registered keyoption
/// The keystrike to update has to be monitored previously
/// </summary>
/// <param name="option">Define KeyOption to update</param>
public async Task UpdateKey(KeyOptions option)
{
await _jsRuntime.InvokeVoidAsync($"mudKeyInterceptor.updatekey", _elementId, option);
}
/// <summary>
/// Disconnect from the previously connected ancestor and its descendants
/// </summary>
public async Task Disconnect()
{
try
{
await _jsRuntime.InvokeVoidAsync($"mudKeyInterceptor.disconnect", _elementId);
}
catch (Exception) { /*ignore*/ }
_isObserving = false;
}
[JSInvokable]
public void OnKeyDown(KeyboardEventArgs args)
{
KeyDown?.Invoke(args);
}
[JSInvokable]
public void OnKeyUp(KeyboardEventArgs args)
{
KeyUp?.Invoke(args);
}
public event KeyboardEvent KeyDown;
public event KeyboardEvent KeyUp;
protected virtual void Dispose(bool disposing)
{
if (!disposing || _isDisposed)
return;
_isDisposed = true;
KeyDown = null;
KeyUp = null;
Disconnect().AndForget();
_dotNetRef.Dispose();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}