|
|
|
|
// 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.JSInterop;
|
|
|
|
|
|
|
|
|
|
namespace Connected.Services
|
|
|
|
|
{
|
|
|
|
|
public interface IJsEvent : IDisposable
|
|
|
|
|
{
|
|
|
|
|
Task Connect(string elementId, JsEventOptions options);
|
|
|
|
|
Task Disconnect();
|
|
|
|
|
event Action<int> CaretPositionChanged;
|
|
|
|
|
event Action<string> Paste;
|
|
|
|
|
event Action<int, int> Select;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Subscribe JS events of any element by html id
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class JsEvent : IJsEvent
|
|
|
|
|
{
|
|
|
|
|
private bool _isDisposed = false;
|
|
|
|
|
|
|
|
|
|
private readonly DotNetObjectReference<JsEvent> _dotNetRef;
|
|
|
|
|
private readonly IJSRuntime _jsRuntime;
|
|
|
|
|
private string _elementId;
|
|
|
|
|
private bool _isObserving;
|
|
|
|
|
internal HashSet<string> _subscribedEvents = new HashSet<string>();
|
|
|
|
|
|
|
|
|
|
[DynamicDependency(nameof(OnCaretPositionChanged))]
|
|
|
|
|
[DynamicDependency(nameof(OnPaste))]
|
|
|
|
|
[DynamicDependency(nameof(OnSelect))]
|
|
|
|
|
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(JsEventOptions))]
|
|
|
|
|
public JsEvent(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</param>
|
|
|
|
|
public async Task Connect(string elementId, JsEventOptions options)
|
|
|
|
|
{
|
|
|
|
|
if (_isObserving || _isDisposed)
|
|
|
|
|
return;
|
|
|
|
|
_elementId = elementId;
|
|
|
|
|
_isObserving = await _jsRuntime.InvokeVoidAsyncWithErrorHandling("mudJsEvent.connect", _dotNetRef, elementId, options); ;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Disconnect from the previously connected ancestor and its descendants
|
|
|
|
|
/// </summary>
|
|
|
|
|
public async Task Disconnect()
|
|
|
|
|
{
|
|
|
|
|
if (_elementId == null)
|
|
|
|
|
return;
|
|
|
|
|
await UnsubscribeAll();
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
await _jsRuntime.InvokeVoidAsync($"mudJsEvent.disconnect", _elementId);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception) { /*ignore*/ }
|
|
|
|
|
_isObserving = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal void Subscribe(string eventName)
|
|
|
|
|
{
|
|
|
|
|
if (_elementId == null)
|
|
|
|
|
throw new InvalidOperationException("Call Connect(...) before attaching events!");
|
|
|
|
|
if (_subscribedEvents.Contains(eventName) || _isDisposed)
|
|
|
|
|
return;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
_jsRuntime.InvokeVoidAsync("mudJsEvent.subscribe", _elementId, eventName).AndForget();
|
|
|
|
|
_subscribedEvents.Add(eventName);
|
|
|
|
|
}
|
|
|
|
|
catch (JSDisconnectedException) { }
|
|
|
|
|
catch (TaskCanceledException) { }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal async Task Unsubscribe(string eventName)
|
|
|
|
|
{
|
|
|
|
|
if (_elementId == null)
|
|
|
|
|
return;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
await _jsRuntime.InvokeVoidAsync($"mudJsEvent.unsubscribe", _elementId, eventName);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception) { /*ignore*/ }
|
|
|
|
|
_subscribedEvents.Remove(eventName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal async Task UnsubscribeAll()
|
|
|
|
|
{
|
|
|
|
|
if (_elementId == null)
|
|
|
|
|
return;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
foreach (var eventName in _subscribedEvents)
|
|
|
|
|
await _jsRuntime.InvokeVoidAsync($"mudJsEvent.unsubscribe", _elementId, eventName);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception) { /*ignore*/ }
|
|
|
|
|
_subscribedEvents.Clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
List<Action<int>> _caretPositionChangedHandlers = new List<Action<int>>();
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Subscribe this event to get notified about caret changes in an input on click and on keyup
|
|
|
|
|
/// </summary>
|
|
|
|
|
public event Action<int> CaretPositionChanged
|
|
|
|
|
{
|
|
|
|
|
add
|
|
|
|
|
{
|
|
|
|
|
if (_caretPositionChangedHandlers.Count == 0)
|
|
|
|
|
{
|
|
|
|
|
Subscribe("click");
|
|
|
|
|
Subscribe("keyup");
|
|
|
|
|
}
|
|
|
|
|
_caretPositionChangedHandlers.Add(value);
|
|
|
|
|
}
|
|
|
|
|
remove
|
|
|
|
|
{
|
|
|
|
|
if (_caretPositionChangedHandlers.Count == 0)
|
|
|
|
|
return;
|
|
|
|
|
if (_caretPositionChangedHandlers.Count == 1)
|
|
|
|
|
{
|
|
|
|
|
Unsubscribe("click").AndForget();
|
|
|
|
|
Unsubscribe("keyup").AndForget();
|
|
|
|
|
}
|
|
|
|
|
_caretPositionChangedHandlers.Remove(value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// To be invoked only by JS
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JSInvokable]
|
|
|
|
|
public void OnCaretPositionChanged(int caretPosition)
|
|
|
|
|
{
|
|
|
|
|
foreach (var handler in _caretPositionChangedHandlers)
|
|
|
|
|
{
|
|
|
|
|
handler.Invoke(caretPosition);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
List<Action<string>> _pasteHandlers = new List<Action<string>>();
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Subscribe this event to get notified about paste actions
|
|
|
|
|
/// </summary>
|
|
|
|
|
public event Action<string> Paste
|
|
|
|
|
{
|
|
|
|
|
add
|
|
|
|
|
{
|
|
|
|
|
if (_pasteHandlers.Count == 0)
|
|
|
|
|
Subscribe("paste");
|
|
|
|
|
_pasteHandlers.Add(value);
|
|
|
|
|
}
|
|
|
|
|
remove
|
|
|
|
|
{
|
|
|
|
|
if (_pasteHandlers.Count == 0)
|
|
|
|
|
return;
|
|
|
|
|
if (_pasteHandlers.Count == 1)
|
|
|
|
|
Unsubscribe("paste").AndForget();
|
|
|
|
|
_pasteHandlers.Remove(value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// To be invoked only by JS
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JSInvokable]
|
|
|
|
|
public void OnPaste(string text)
|
|
|
|
|
{
|
|
|
|
|
foreach (var handler in _pasteHandlers)
|
|
|
|
|
{
|
|
|
|
|
handler.Invoke(text);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
List<Action<int, int>> _selectHandlers = new List<Action<int, int>>();
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Subscribe this event to get notified about paste actions
|
|
|
|
|
/// </summary>
|
|
|
|
|
public event Action<int, int> Select
|
|
|
|
|
{
|
|
|
|
|
add
|
|
|
|
|
{
|
|
|
|
|
if (_selectHandlers.Count == 0)
|
|
|
|
|
Subscribe("select");
|
|
|
|
|
_selectHandlers.Add(value);
|
|
|
|
|
}
|
|
|
|
|
remove
|
|
|
|
|
{
|
|
|
|
|
if (_selectHandlers.Count == 0)
|
|
|
|
|
return;
|
|
|
|
|
if (_selectHandlers.Count == 1)
|
|
|
|
|
Unsubscribe("select").AndForget();
|
|
|
|
|
_selectHandlers.Remove(value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// To be invoked only by JS
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JSInvokable]
|
|
|
|
|
public void OnSelect(int start, int end)
|
|
|
|
|
{
|
|
|
|
|
foreach (var handler in _selectHandlers)
|
|
|
|
|
{
|
|
|
|
|
handler.Invoke(start, end);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
|
|
|
{
|
|
|
|
|
if (!disposing || _isDisposed)
|
|
|
|
|
return;
|
|
|
|
|
_isDisposed = true;
|
|
|
|
|
Disconnect().AndForget();
|
|
|
|
|
_dotNetRef.Dispose();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Dispose()
|
|
|
|
|
{
|
|
|
|
|
Dispose(true);
|
|
|
|
|
GC.SuppressFinalize(this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|