As Blazor works with its own virtual copy of the DOM it is a bit tricky (and should be avoided) to combine DOM manipulations via JavaScript with Blazor. Nevertheless, there are some ways to proceed.
By default, you are not able to trigger any input event (keydown, etc.) before Blazor itself has inserted the input into the contenteditable div. You could herefore add an event listener via JavaScript (call it once within OnAfterRender()) and thereby keep the div as global JavaScript variable for further access:
JavaScript:
let _inputElement;
function OnBeforeInput(event) {
// ... put your own 'before logic' to handle event.key
// to manipulate _inputElement here ...
}
function SetListenToInput(elementId) {
_inputElement = document.getElementById(elementId);
_inputElement.addEventListener(
"keydown",
(event) => OnBeforeInput(event),
false
);
}
Blazor:
using Microsoft.JSInterop;
@inject IJSRuntime JSRuntime
<div id="inputEditor" contenteditable="true" tabindex="1" @ref=_inputReference @onkeydown="e => OnKeyDown(e)"></div>
@code {
private ElementReference _inputReference; // can be used for focus, etc.
protected override void OnAfterRender(bool firstRender) {
base.OnAfterRender(firstRender);
if (firstRender) {
_ = JSRuntime.InvokeVoidAsync("SetListenToInput", "inputEditor");
}
}
internal async Task OnKeyDown(KeyboardEventArgs e) {
// ... handle logic for OnAfterInput ....
}
}
Now, you could manipulate the DOM via JavaScript as usual and, depending on the particular key, get/set/clear the selection range and caret position before Blazor additionally inserts the key to the contenteditable div and then also adjust it (again) after the key has been entered.
To prevent Blazor (additionally) entering the key but still triggering the input event you need to set the following to the div:
<div ... @onkeydown:preventDefault="true" @onkeydown:stopPropagation="true">@_input</div>
Thereby you do not need a JavaScript event listener anymore as you do not have to cope with an already entered key by Blazor. But now you have to handle all possible inputs and the navigation yourself. Therefore, you could either use JavaScript (as usual) or manipulate the content directly within Blazor.
You can manipulate text:
<div ...>@_input</div>
@code {
private string _input = string.Empty;
internal async Task OnKeyDown(KeyboardEventArgs e) {
_input += e.Key; // just a simple example
// it's far more complicated as you have to write your own editor logic!
}
}
... or manipulate the text as raw HTML (for styling/formatting):
<div ...>@((MarkupString)_input)</div>
... or use a RenderFragment/Component and call StateHasChanged() afterwards to inform Blazor about your update. E.g.:
<div ...><MyFragment @ref=_inputFragment /></div>
@code {
private MyFragment? _inputFragment;
internal async Task OnKeyDown(KeyboardEventArgs e) {
// ... do whatever you want to do to handle the input
// and adjust your fragment/component ...
StateHasChanged();
}
}
Last, but not least: don't forget to also handle any relevant mouse and clipboard events accordingly.