Skip to content

Commit

Permalink
[FluentKeyCode] Add KeyUp event (#2122)
Browse files Browse the repository at this point in the history
  • Loading branch information
dvoituron authored May 29, 2024
1 parent 5720854 commit 3181105
Show file tree
Hide file tree
Showing 12 changed files with 294 additions and 42 deletions.
71 changes: 67 additions & 4 deletions examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,12 @@
Gets or sets an expression that identifies the bound value.
</summary>
</member>
<member name="P:Microsoft.FluentUI.AspNetCore.Components.FluentInputBase`1.Field">
<summary>
Gets or sets the <see cref="P:Microsoft.FluentUI.AspNetCore.Components.FluentInputBase`1.FieldIdentifier"/> that identifies the bound value.
If set, this parameter takes precedence over <see cref="P:Microsoft.FluentUI.AspNetCore.Components.FluentInputBase`1.ValueExpression"/>.
</summary>
</member>
<member name="P:Microsoft.FluentUI.AspNetCore.Components.FluentInputBase`1.DisplayName">
<summary>
Gets or sets the display name for this field.
Expand All @@ -556,7 +562,7 @@
</member>
<member name="P:Microsoft.FluentUI.AspNetCore.Components.FluentInputBase`1.Embedded">
<summary>
Gets or sets if the derived component is embedded in another component.
Gets or sets if the derived component is embedded in another component.
If true, the ClassValue property will not include the EditContext's FieldCssClass.
</summary>
</member>
Expand Down Expand Up @@ -631,9 +637,9 @@
<summary>
Exposes the elements FocusAsync(bool preventScroll) method.
</summary>
<param name="preventScroll">A Boolean value indicating whether or not the browser should scroll
the document to bring the newly-focused element into view. A value of false for preventScroll (the default)
means that the browser will scroll the element into view after focusing it.
<param name="preventScroll">A Boolean value indicating whether or not the browser should scroll
the document to bring the newly-focused element into view. A value of false for preventScroll (the default)
means that the browser will scroll the element into view after focusing it.
If preventScroll is set to true, no scrolling will occur.</param>
</member>
<member name="M:Microsoft.FluentUI.AspNetCore.Components.FluentInputBase`1.ConvertToDictionary(System.Collections.Generic.IReadOnlyDictionary{System.String,System.Object},System.Collections.Generic.Dictionary{System.String,System.Object}@)">
Expand Down Expand Up @@ -4366,6 +4372,11 @@
The anchor must refer to the ID of an element (or sub-element) accepting the focus.
</summary>
</member>
<member name="F:Microsoft.FluentUI.AspNetCore.Components.FluentKeyCode.PreventMultipleKeyDown">
<summary>
Prevent multiple KeyDown events.
</summary>
</member>
<member name="P:Microsoft.FluentUI.AspNetCore.Components.FluentKeyCode.JSRuntime">
<summary />
</member>
Expand Down Expand Up @@ -4397,6 +4408,11 @@
Event triggered when a KeyDown event is raised.
</summary>
</member>
<member name="P:Microsoft.FluentUI.AspNetCore.Components.FluentKeyCode.OnKeyUp">
<summary>
Event triggered when a KeyUp event is raised.
</summary>
</member>
<member name="P:Microsoft.FluentUI.AspNetCore.Components.FluentKeyCode.IgnoreModifier">
<summary>
Ignore modifier keys (Shift, Alt, Ctrl, Meta) when evaluating the key code.
Expand Down Expand Up @@ -4450,6 +4466,25 @@
<param name="targetId"></param>
<returns></returns>
</member>
<member name="M:Microsoft.FluentUI.AspNetCore.Components.FluentKeyCode.OnKeyUpRaisedAsync(System.Int32,System.String,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Int32,System.String)">
<summary>
Internal method.
</summary>
<param name="keyCode"></param>
<param name="value"></param>
<param name="ctrlKey"></param>
<param name="shiftKey"></param>
<param name="altKey"></param>
<param name="metaKey"></param>
<param name="location"></param>
<param name="targetId"></param>
<returns></returns>
</member>
<member name="P:Microsoft.FluentUI.AspNetCore.Components.FluentKeyCodeEventArgs.Name">
<summary>
Gets the name of the event ("keydown" or "keyup").
</summary>
</member>
<member name="P:Microsoft.FluentUI.AspNetCore.Components.FluentKeyCodeEventArgs.Location">
<summary>
Gets an <see cref="T:Microsoft.FluentUI.AspNetCore.Components.KeyLocation" /> representing the location of the key on the keyboard or other input device.
Expand Down Expand Up @@ -4516,6 +4551,13 @@
<param name="args"></param>
<returns></returns>
</member>
<member name="M:Microsoft.FluentUI.AspNetCore.Components.IKeyCodeListener.OnKeyUpAsync(Microsoft.FluentUI.AspNetCore.Components.FluentKeyCodeEventArgs)">
<summary>
Method called when a key is unpressed.
</summary>
<param name="args"></param>
<returns></returns>
</member>
<member name="T:Microsoft.FluentUI.AspNetCore.Components.IKeyCodeService">
<summary>
Service registered by <see cref="M:Microsoft.FluentUI.AspNetCore.Components.ServiceCollectionExtensions.AddFluentUIComponents(Microsoft.Extensions.DependencyInjection.IServiceCollection,Microsoft.FluentUI.AspNetCore.Components.LibraryConfiguration)"/>
Expand All @@ -4541,6 +4583,14 @@
<param name="handler"></param>
<returns></returns>
</member>
<member name="M:Microsoft.FluentUI.AspNetCore.Components.IKeyCodeService.RegisterListener(System.Func{Microsoft.FluentUI.AspNetCore.Components.FluentKeyCodeEventArgs,System.Threading.Tasks.Task},System.Func{Microsoft.FluentUI.AspNetCore.Components.FluentKeyCodeEventArgs,System.Threading.Tasks.Task})">
<summary>
Register the <paramref name="handlerKeyDown"/> and <paramref name="handlerKeyUp"/> methods as a listener, and returns a unique identifier.
</summary>
<param name="handlerKeyDown"></param>
<param name="handlerKeyUp"></param>
<returns></returns>
</member>
<member name="M:Microsoft.FluentUI.AspNetCore.Components.IKeyCodeService.UnregisterListener(Microsoft.FluentUI.AspNetCore.Components.IKeyCodeListener)">
<summary>
Unregister the listener component or page.
Expand All @@ -4553,6 +4603,13 @@
</summary>
<param name="handler"></param>
</member>
<member name="M:Microsoft.FluentUI.AspNetCore.Components.IKeyCodeService.UnregisterListener(System.Func{Microsoft.FluentUI.AspNetCore.Components.FluentKeyCodeEventArgs,System.Threading.Tasks.Task},System.Func{Microsoft.FluentUI.AspNetCore.Components.FluentKeyCodeEventArgs,System.Threading.Tasks.Task})">
<summary>
Unregister the listener method.
</summary>
<param name="handlerKeyDown"></param>
<param name="handlerKeyUp"></param>
</member>
<member name="M:Microsoft.FluentUI.AspNetCore.Components.IKeyCodeService.Clear">
<summary>
Unregister all listeners.
Expand All @@ -4570,12 +4627,18 @@
<member name="M:Microsoft.FluentUI.AspNetCore.Components.KeyCodeService.RegisterListener(System.Func{Microsoft.FluentUI.AspNetCore.Components.FluentKeyCodeEventArgs,System.Threading.Tasks.Task})">
<inheritdoc cref="M:Microsoft.FluentUI.AspNetCore.Components.IKeyCodeService.RegisterListener(System.Func{Microsoft.FluentUI.AspNetCore.Components.FluentKeyCodeEventArgs,System.Threading.Tasks.Task})" />
</member>
<member name="M:Microsoft.FluentUI.AspNetCore.Components.KeyCodeService.RegisterListener(System.Func{Microsoft.FluentUI.AspNetCore.Components.FluentKeyCodeEventArgs,System.Threading.Tasks.Task},System.Func{Microsoft.FluentUI.AspNetCore.Components.FluentKeyCodeEventArgs,System.Threading.Tasks.Task})">
<inheritdoc cref="M:Microsoft.FluentUI.AspNetCore.Components.IKeyCodeService.RegisterListener(System.Func{Microsoft.FluentUI.AspNetCore.Components.FluentKeyCodeEventArgs,System.Threading.Tasks.Task},System.Func{Microsoft.FluentUI.AspNetCore.Components.FluentKeyCodeEventArgs,System.Threading.Tasks.Task})" />
</member>
<member name="M:Microsoft.FluentUI.AspNetCore.Components.KeyCodeService.UnregisterListener(Microsoft.FluentUI.AspNetCore.Components.IKeyCodeListener)">
<inheritdoc cref="M:Microsoft.FluentUI.AspNetCore.Components.IKeyCodeService.UnregisterListener(Microsoft.FluentUI.AspNetCore.Components.IKeyCodeListener)" />
</member>
<member name="M:Microsoft.FluentUI.AspNetCore.Components.KeyCodeService.UnregisterListener(System.Func{Microsoft.FluentUI.AspNetCore.Components.FluentKeyCodeEventArgs,System.Threading.Tasks.Task})">
<inheritdoc cref="M:Microsoft.FluentUI.AspNetCore.Components.IKeyCodeService.UnregisterListener(System.Func{Microsoft.FluentUI.AspNetCore.Components.FluentKeyCodeEventArgs,System.Threading.Tasks.Task})" />
</member>
<member name="M:Microsoft.FluentUI.AspNetCore.Components.KeyCodeService.UnregisterListener(System.Func{Microsoft.FluentUI.AspNetCore.Components.FluentKeyCodeEventArgs,System.Threading.Tasks.Task},System.Func{Microsoft.FluentUI.AspNetCore.Components.FluentKeyCodeEventArgs,System.Threading.Tasks.Task})">
<inheritdoc cref="M:Microsoft.FluentUI.AspNetCore.Components.IKeyCodeService.UnregisterListener(System.Func{Microsoft.FluentUI.AspNetCore.Components.FluentKeyCodeEventArgs,System.Threading.Tasks.Task},System.Func{Microsoft.FluentUI.AspNetCore.Components.FluentKeyCodeEventArgs,System.Threading.Tasks.Task})" />
</member>
<member name="M:Microsoft.FluentUI.AspNetCore.Components.KeyCodeService.Clear">
<inheritdoc cref="M:Microsoft.FluentUI.AspNetCore.Components.IKeyCodeService.Clear" />
</member>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
@inject IKeyCodeService KeyCodeService
@implements IAsyncDisposable

<FluentSwitch @bind-Value="@IncludeKeyUp"
CheckedMessage="KeyDown and KeyUp"
UncheckedMessage="KeyDown only"
Style="margin-bottom: 10px;" />

<FluentCard>
@if (!KeyPressed.Any())
{
Expand All @@ -9,29 +14,36 @@

@foreach (var key in KeyPressed)
{
<span class="key">@key</span>
<span class="key" type="@key.Event">@key.Key</span>
}
</FluentCard>

@code
{
private List<string> KeyPressed = new();
private bool IncludeKeyUp = false;
private List<(string Key, string Event)> KeyPressed = new();

protected override void OnInitialized()
{
KeyCodeService.RegisterListener(OnKeyDownAsync);
// FluentKeyCode.PreventMultipleKeyDown = true;
KeyCodeService.RegisterListener(OnKeyHandleAsync, OnKeyHandleAsync);
}

private Task OnKeyDownAsync(FluentKeyCodeEventArgs args)
private Task OnKeyHandleAsync(FluentKeyCodeEventArgs args)
{
KeyPressed.Add(args.ToString());
if (!IncludeKeyUp && args.Name == "keyup")
{
return Task.CompletedTask;
}

KeyPressed.Add((args.ToString(), args.Name));
StateHasChanged();
return Task.CompletedTask;
}

public ValueTask DisposeAsync()
{
KeyCodeService.UnregisterListener(OnKeyDownAsync);
KeyCodeService.UnregisterListener(OnKeyHandleAsync, OnKeyHandleAsync);
return ValueTask.CompletedTask;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
cursor: pointer;
}

.key[type="keyup"] {
color: red;
}

/* Hover effect */
.key:hover {
background-color: #e0e0e0;
Expand Down
53 changes: 40 additions & 13 deletions src/Core/Components/KeyCode/FluentKeyCode.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ public partial class FluentKeyCode : IAsyncDisposable
private DotNetObjectReference<FluentKeyCode>? _dotNetHelper = null;
private readonly KeyCode[] _Modifiers = new[] { KeyCode.Shift, KeyCode.Alt, KeyCode.Ctrl, KeyCode.Meta };

/// <summary>
/// Prevent multiple KeyDown events.
/// </summary>
public static bool PreventMultipleKeyDown = false;

/// <summary />
[Inject]
private IJSRuntime JSRuntime { get; set; } = default!;
Expand Down Expand Up @@ -50,6 +55,12 @@ public partial class FluentKeyCode : IAsyncDisposable
[Parameter]
public EventCallback<FluentKeyCodeEventArgs> OnKeyDown { get; set; }

/// <summary>
/// Event triggered when a KeyUp event is raised.
/// </summary>
[Parameter]
public EventCallback<FluentKeyCodeEventArgs> OnKeyUp { get; set; }

/// <summary>
/// Ignore modifier keys (Shift, Alt, Ctrl, Meta) when evaluating the key code.
/// </summary>
Expand Down Expand Up @@ -106,7 +117,13 @@ protected async override Task OnAfterRenderAsync(bool firstRender)
_jsModule ??= await JSRuntime.InvokeAsync<IJSObjectReference>("import", JAVASCRIPT_FILE);
_dotNetHelper = DotNetObjectReference.Create(this);

_javaScriptEventId = await _jsModule.InvokeAsync<string>("RegisterKeyCode", GlobalDocument, Anchor, ChildContent is null ? null : Element, Only, IgnoreModifier ? Ignore.Union(_Modifiers) : Ignore, StopPropagation, PreventDefault, PreventDefaultOnly, _dotNetHelper);
var eventNames = string.Join(";", new[]
{
OnKeyDown.HasDelegate ? "KeyDown" : string.Empty,
OnKeyUp.HasDelegate ? "KeyUp" : string.Empty,
});

_javaScriptEventId = await _jsModule.InvokeAsync<string>("RegisterKeyCode", GlobalDocument, eventNames.Length > 1 ? eventNames : "KeyDown", Anchor, ChildContent is null ? null : Element, Only, IgnoreModifier ? Ignore.Union(_Modifiers) : Ignore, StopPropagation, PreventDefault, PreventDefaultOnly, _dotNetHelper, PreventMultipleKeyDown);
}
}

Expand All @@ -127,18 +144,28 @@ public async Task OnKeyDownRaisedAsync(int keyCode, string value, bool ctrlKey,
{
if (OnKeyDown.HasDelegate)
{
await OnKeyDown.InvokeAsync(new FluentKeyCodeEventArgs
{
Location = Enum.IsDefined(typeof(KeyLocation), location) ? (KeyLocation)location : KeyLocation.Unknown,
Key = Enum.IsDefined(typeof(KeyCode), keyCode) ? (KeyCode)keyCode : KeyCode.Unknown,
KeyCode = keyCode,
Value = value,
CtrlKey = ctrlKey,
ShiftKey = shiftKey,
AltKey = altKey,
MetaKey = metaKey,
TargetId = targetId,
});
await OnKeyDown.InvokeAsync(FluentKeyCodeEventArgs.Instance("keydown", keyCode, value, ctrlKey, shiftKey, altKey, metaKey, location, targetId));
}
}

/// <summary>
/// Internal method.
/// </summary>
/// <param name="keyCode"></param>
/// <param name="value"></param>
/// <param name="ctrlKey"></param>
/// <param name="shiftKey"></param>
/// <param name="altKey"></param>
/// <param name="metaKey"></param>
/// <param name="location"></param>
/// <param name="targetId"></param>
/// <returns></returns>
[JSInvokable]
public async Task OnKeyUpRaisedAsync(int keyCode, string value, bool ctrlKey, bool shiftKey, bool altKey, bool metaKey, int location, string targetId)
{
if (OnKeyUp.HasDelegate)
{
await OnKeyUp.InvokeAsync(FluentKeyCodeEventArgs.Instance("keyup", keyCode, value, ctrlKey, shiftKey, altKey, metaKey, location, targetId));
}
}

Expand Down
38 changes: 31 additions & 7 deletions src/Core/Components/KeyCode/FluentKeyCode.razor.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export function RegisterKeyCode(globalDocument, id, elementRef, onlyCodes, excludeCodes, stopPropagation, preventDefault, preventDefaultOnly, dotNetHelper) {
export function RegisterKeyCode(globalDocument, eventNames, id, elementRef, onlyCodes, excludeCodes, stopPropagation, preventDefault, preventDefaultOnly, dotNetHelper, preventMultipleKeydown) {
const element = globalDocument
? document
: elementRef == null ? document.getElementById(id) : elementRef;
Expand All @@ -10,7 +10,21 @@ export function RegisterKeyCode(globalDocument, id, elementRef, onlyCodes, exclu
if (!!element) {

const eventId = Math.random().toString(36).slice(2);
const handler = function (e) {
let fired = false;

const handlerKeydown = function (e) {
if (!fired || !preventMultipleKeydown) {
fired = true;
return handler(e, "OnKeyDownRaisedAsync");
}
}

const handlerKeyup = function (e) {
fired = false;
return handler(e, "OnKeyUpRaisedAsync");
}

const handler = function (e, netMethod) {
const keyCode = e.which || e.keyCode || e.charCode;

if (!!dotNetHelper && !!dotNetHelper.invokeMethodAsync) {
Expand Down Expand Up @@ -38,14 +52,19 @@ export function RegisterKeyCode(globalDocument, id, elementRef, onlyCodes, exclu
if (isStopPropagation) {
e.stopPropagation();
}
dotNetHelper.invokeMethodAsync("OnKeyDownRaisedAsync", keyCode, e.key, e.ctrlKey, e.shiftKey, e.altKey, e.metaKey, e.location, targetId);
dotNetHelper.invokeMethodAsync(netMethod, keyCode, e.key, e.ctrlKey, e.shiftKey, e.altKey, e.metaKey, e.location, targetId);
return;
}
}
};

element.addEventListener('keydown', handler)
document.fluentKeyCodeEvents[eventId] = { source: element, handler };
if (preventMultipleKeydown || (!!eventNames && eventNames.includes("KeyDown"))) {
element.addEventListener('keydown', handlerKeydown)
}
if (preventMultipleKeydown || (!!eventNames && eventNames.includes("KeyUp"))) {
element.addEventListener('keyup', handlerKeyup)
}
document.fluentKeyCodeEvents[eventId] = { source: element, handlerKeydown, handlerKeyup };

return eventId;
}
Expand All @@ -58,9 +77,14 @@ export function UnregisterKeyCode(eventId) {
if (document.fluentKeyCodeEvents != null) {
const keyEvent = document.fluentKeyCodeEvents[eventId];
const element = keyEvent.source;
const handler = keyEvent.handler;

element.removeEventListener("keydown", handler);
if (!!keyEvent.handlerKeydown) {
element.removeEventListener("keydown", keyEvent.handlerKeydown);
}

if (!!keyEvent.handlerKeyup) {
element.removeEventListener("keyup", keyEvent.handlerKeyup);
}

delete document.fluentKeyCodeEvents[eventId];
}
Expand Down
22 changes: 22 additions & 0 deletions src/Core/Components/KeyCode/FluentKeyCodeEventArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,28 @@ namespace Microsoft.FluentUI.AspNetCore.Components;

public class FluentKeyCodeEventArgs
{
internal static FluentKeyCodeEventArgs Instance(string name, int keyCode, string value, bool ctrlKey, bool shiftKey, bool altKey, bool metaKey, int location, string targetId)
{
return new FluentKeyCodeEventArgs
{
Name = name,
Location = Enum.IsDefined(typeof(KeyLocation), location) ? (KeyLocation)location : KeyLocation.Unknown,
Key = Enum.IsDefined(typeof(KeyCode), keyCode) ? (KeyCode)keyCode : AspNetCore.Components.KeyCode.Unknown,
KeyCode = keyCode,
Value = value,
CtrlKey = ctrlKey,
ShiftKey = shiftKey,
AltKey = altKey,
MetaKey = metaKey,
TargetId = targetId,
};
}

/// <summary>
/// Gets the name of the event ("keydown" or "keyup").
/// </summary>
public string Name { get; init; }

Check warning on line 25 in src/Core/Components/KeyCode/FluentKeyCodeEventArgs.cs

View workflow job for this annotation

GitHub Actions / Build and deploy Demo site

Non-nullable property 'Name' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 25 in src/Core/Components/KeyCode/FluentKeyCodeEventArgs.cs

View workflow job for this annotation

GitHub Actions / Build and Test Code Lib

Non-nullable property 'Name' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 25 in src/Core/Components/KeyCode/FluentKeyCodeEventArgs.cs

View workflow job for this annotation

GitHub Actions / Build and Test Code Lib

Non-nullable property 'Name' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

/// <summary>
/// Gets an <see cref="KeyLocation" /> representing the location of the key on the keyboard or other input device.
/// <see href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/location"/>
Expand Down
Loading

0 comments on commit 3181105

Please sign in to comment.