Буфер обмена и DOM из Razor
Операции IJsPoeBlazorUtils для текста файлов фокуса прокрутки и диагностики элементов
Буфер обмена и DOM из Razor
IJsPoeBlazorUtils выполняет операции с браузерной частью Blazor: копирует текст, предлагает загрузку файла, управляет выделением, фокусом и DOM-элементами. Для обычных операций используйте этот сервис; собственный JS-модуль нужен для поведения, которого в нём нет.
Примеры предназначены для Razor-компонента внутри настроенного Blazor-host EyeAuras/PoeShared, где зарегистрирован PoeShared.Blazor.Services.IJsPoeBlazorUtils и доступны ресурсы PoeShared.Blazor. Создание самого окна описано в Blazor Windows. Это другой контекст, чем верхнеуровневый C# Action.
Скопировать текст и предложить загрузку файла
Создайте компонент ReportPanel.razor со следующим содержимым и покажите его в существующем Blazor-окне:
@using System
@using System.Threading.Tasks
@using Microsoft.AspNetCore.Components
@using PoeShared.Blazor.Services
@inherits PoeShared.Blazor.BlazorReactiveComponent
@inject IJsPoeBlazorUtils Js
<input type="text" id="@fieldId" @ref="field" @bind="report" />
<button @onclick="Copy">Копировать</button>
<button @onclick="ReadClipboard">Прочитать буфер</button>
<button @onclick="Download">Скачать отчёт</button>
<button @onclick="Select">Выделить текст</button>
<button @onclick="Scroll">Показать поле</button>
@code {
private readonly string fieldId = "report-" + Guid.NewGuid().ToString("N");
private ElementReference field;
private string report = "Результат проверки";
private Task Copy() => Js.SetClipboardText(report);
private async Task ReadClipboard()
{
report = await Js.GetClipboardText();
await Refresh(nameof(ReadClipboard));
}
private Task Download() =>
Js.DownloadTextFile("report.txt", report, "text/plain;charset=utf-8");
private async Task Select()
{
await Js.FocusElementById(fieldId);
await Js.SelectAllTextInElement(field);
}
private Task Scroll() =>
Js.ScrollElementIntoView(field, behavior: "smooth", block: "center", inline: "nearest");
}
«Копировать» передаёт содержимое поля в буфер. «Прочитать буфер» заменяет текст поля полученной строкой. «Скачать отчёт» предлагает браузерную загрузку UTF-8-файла с указанным именем: это не запись по произвольному пути на диске. Доступ к буферу и загрузкам зависит от возможностей и разрешений host.
Обработчики выполняются после появления поля в DOM, поэтому ElementReference уже можно использовать. Для начального фокуса без кнопки вызывайте операцию после отрисовки компонента, а не в конструкторе или OnInitialized. Условно создаваемое поле также нужно сначала отрисовать.
В этой версии методы выделения принимают только HTMLInputElement, поддерживающий выделение текста, например <input type="text"> из примера. textarea и произвольные контейнеры этими helper-методами не поддерживаются. Это относится и к обеим формам SelectAllTextInElement, и к SelectTextRangeInElement.
Сервис внедрён host и не принадлежит компоненту; не вызывайте его DisposeAsync после копирования. Все операции ожидаются через Task, поэтому ошибки остаются в обычной цепочке обработки события.
Получить элементы под точкой
В DomInspector.razor координаты мыши передаются непосредственно из браузерного события:
@using System.Linq
@using System.Threading.Tasks
@using Microsoft.AspNetCore.Components.Web
@using PoeShared.Blazor.Services
@inherits PoeShared.Blazor.BlazorReactiveComponent
@inject IJsPoeBlazorUtils Js
<div data-role="inspection-area" @onmousedown="Inspect">
Нажмите здесь для диагностики DOM.
</div>
<pre>@summary</pre>
@code {
private string summary = "";
private async Task Inspect(MouseEventArgs e)
{
var hits = await Js.GetElementBreadcrumbsAt(
e.ClientX, e.ClientY, maxDepth: 5,
attributeNames: new[] { "data-role", "id" });
summary = string.Join("\n", hits.Select(hit => hit.Node.Tag));
await Refresh(nameof(Inspect));
}
}
Метод возвращает HtmlElementBreadcrumb[]: для каждого найденного элемента доступны Node и путь предков Path. Координаты — ClientX/ClientY в viewport браузера, а не физические экранные координаты окна.
Параметр maxDepth ограничивает число элементов в стеке попаданий под точкой; он не задаёт глубину цепочки предков. attributeNames определяет, какие атрибуты включить в описания. При необходимости передайте токен операции последним аргументом cancellationToken.
Выделение и прокрутка
| Вызов | Аргументы и результат |
|---|---|
SelectAllTextInElementById(string elementId) | Выделяет текст поддерживаемого input по DOM-id |
SelectAllTextInElement(ElementReference elementRef) | Выделяет текст уже отрисованного input |
SelectTextRangeInElement(ElementReference elementRef, int? start = default, int? end = default, JsSelectionRangeDirection direction = Forward) | Передаёт начало, конец и направление выделения; смещения относятся к поддерживаемому input |
FocusElementById(string elementId) | Устанавливает фокус по id, без символа # |
ClickElementById(string elementId) | Вызывает браузерный click элемента, а не глобальный ввод мыши |
ScrollElementIntoView(ElementReference elementRef, string behavior = "smooth", string block = "nearest", string inline = "nearest") | Прокрутка к элементу; block — вертикальное, inline — горизонтальное выравнивание |
ScrollToTop(string elementSelector) и ScrollToBottom(string elementSelector) | Принимают селектор, например #report-list |
Те же методы с TimeSpan duration | Передают длительность анимации в миллисекундах |
Для ScrollElementIntoView используйте behavior auto или smooth; выравнивание — start, center, end или nearest. Не путайте параметр elementId у методов фокуса/выделения с селектором у методов прокрутки. Метод реализации ScrollToTopById не входит в интерфейс и в рассматриваемом срезе бросает NotImplementedException; используйте ScrollToTop.
CSS и атрибуты
У AddClass, RemoveClass, ToggleClass и HasClass есть перегрузки для строкового селектора и для ElementReference. Первые два принимают params string[] classNames, последние два — одно имя класса. HasClass возвращает Task<bool>.
SetClass(ElementReference, params string[]) заменяет набор классов. SetAttribute(ElementReference, string name, string? value) задаёт атрибут, а при null удаляет его; RemoveAttribute(ElementReference, string name) удаляет явно. Применяйте их после отрисовки; постоянное состояние UI удобнее задавать через Razor и модель.
Например, внутри обработчика компонента с существующим field:
await Js.AddClass(field, "selected");
await Js.SetAttribute(field, "data-state", "ready");
bool selected = await Js.HasClass(field, "selected");
await Js.RemoveAttribute(field, "data-state");
await Js.RemoveClass(field, "selected");
Регистрации с временем жизни
| Операция | Кто завершает регистрацию |
|---|---|
AddKeyboardHook<THandler>(ElementReference, THandler, string methodName) | Возвращает ElementKeyboardHookRef; владелец вызывает DisposeAsync |
Перегрузка AddKeyboardHook<THandler> с DotNetObjectReference<THandler> | Вызывающий сохраняет ссылку, снимает hook через RemoveKeyboardHook и освобождает свой DotNetObjectReference |
SuppressWellKnownBrowserShortcuts(JsBrowserShortcutSuppressionOptions? options = null) | Возвращает JsBrowserShortcutSuppressionRef с асинхронным освобождением |
RegisterWindowTitleBarGestures<THandler>(ElementReference, DotNetObjectReference<THandler>, double? dragThresholdPixels = null) | Возвращает JsWindowTitleBarGestureRef; ссылка на callback также принадлежит вызывающему |
AddRootComponent(string elementId, string componentIdentifier, object initialParameters = default) | Возвращает IDynamicRootComponent; освобождайте созданный компонент вместе с владельцем |
Для keyboard hook передайте объект с указанным JS-invokable методом. У RegisterWindowTitleBarGestures имена фиксированы: HandleTitleBarDragStart, HandleTitleBarDoubleClick и HandleTitleBarContextMenu. Их параметры должны совпадать с вызовами JS-модуля конкретного host. Строка про жесты заголовка — навигация по контракту и владению регистрацией, а не готовая интеграция заголовка: сигнатуры callback и полный пример его подключения здесь не приводятся.
Идентификатор динамического компонента и его metadata должны быть зарегистрированы в host до создания; наличие обычного Razor-типа не заменяет регистрацию. Эти операции предназначены для интеграции компонента с host, а не для неограниченного создания глобальных обработчиков.
У JsBrowserShortcutSuppressionOptions есть Shortcuts, AllowCtrlAInEditable, AllowSelector, PreventDefault и StopPropagation. По умолчанию Ctrl+A сохраняет обычное поведение в редактируемых элементах; поддеревья с data-poe-browser-shortcuts="allow" исключены, PreventDefault включён, StopPropagation выключен. Это регистрация в DOM текущего браузера, а не глобальный keyboard hook ОС.
RegisterAncestorContextMenuTarget(ElementReference, string ancestorSelector, string registrationId) возвращает уникальный селектор отмеченного предка. Для снятия передайте тот же селектор и id в UnregisterAncestorContextMenuTarget(string targetSelector, string registrationId). Не переносите маркер другой регистрации на своего владельца.
Дополнительные операции host
ShowAlert(string message)показывает текстовое сообщение через SweetAlert.LoadCss(string cssPath)подключает CSS; путь должен быть доступен браузеру.LoadScript(string scriptPath)загружает JS-файл. Для собственного кода с API и очисткой предпочитайте ES-модуль.RegisterFileDropTarget(ElementReference)подключает приём файлов через WebView2. Host должен обработатьWebMessageReceivedи переданные объекты; это не готовый callback с путями файлов в Razor. В контракте указан WebView2 SDK не ниже 1.0.1518.46.DisposeAsync()освобождает JS-модуль сервиса. Для внедрённого экземпляра временем жизни управляет host.
Версия и связанные операции
Контракты и реализация сверены с PoeEye 0e02c98dcab4bff263684f1c0647eed120e2700f: IJsPoeBlazorUtils, JsPoeBlazorUtils. Доступность конкретной операции зависит от версии host и поставляемых ресурсов.