loading
Note:
Two rules, handled for you A drop only fires on an element whose dragover handler called preventDefault, and dropEffect has to be set on every dragover because the browser resets it between events. Getting either wrong produces a drop target that silently never fires - so OnDrop wires both up, and there is nothing else to add.

Support check

IsSupported

DataTransfer is in every engine. During prerender/SSR the check returns false rather than throwing, so defer it to OnAfterRenderAsync.

C#
@inject Bit.Butil.DataTransfer dataTransfer

var supported = await dataTransfer.IsSupported();
Live sample
support check output
Results will appear here when you interact with the samples.

Accepting a drop

OnDrop / DropPayload

Drag files from your file manager onto the box below - or drag text, or a link from another tab. Items are read during the event and handed over whole, because getData outside the drop event answers with an empty string. Files are the other way round: their contents stay readable afterwards, so the handler does not have to do the reading.

C#
private ElementReference _target;

_subscription = await dataTransfer.OnDrop(_target, payload => InvokeAsync(() =>
{
    // payload.Files - each with Name, Size, Type, LastModified and an Id to read by
    // payload.Text  - "text/plain", when the drag carried some
    // payload.Uri   - "text/uri-list", when a link or an image was dragged in
    StateHasChanged();
}), dropEffect: "copy");
Live sample
Drop files, text or a link here.
drop output
Results will appear here when you interact with the samples.

Reading what was dropped

ReadFile / ReadFileText / CreateObjectUrl / ReleaseFile

A dropped file is readable long after the event that delivered it - the file object is held for you, so there is no need to do the work inside the handler. CreateObjectUrl skips .NET entirely for the case where all you want is to show the picture. Release the files when you are done, or they stay in memory for the life of the page.

C#
var bytes = await dataTransfer.ReadFile(file.Id);          // upload this
var text = await dataTransfer.ReadFileText(file.Id);       // for text files

// or show it without reading it at all:
var url = await dataTransfer.CreateObjectUrl(file.Id);     // <img src="@url">

await dataTransfer.ReleaseFile(file.Id);
Live sample
Drop a file here, then read it.
read output
Results will appear here when you interact with the samples.

Being the drag source

ConfigureDragSource

The payload is settled up front rather than produced by a callback, because dragstart has to set its data synchronously and a round trip to .NET is not. A custom MIME type is how a drop target of your own recognises its own drags; text/plain is what other applications read. A drag image has to be rendered and visible when the drag starts - a display:none element produces no image at all, which is the usual reason a custom one does not appear.

C#
_source = await dataTransfer.ConfigureDragSource(_chip,
    new Dictionary<string, string>
    {
        ["text/plain"] = "a chip from the demo",
        ["application/x-butil-demo"] = "{\"id\":42}"
    },
    effectAllowed: "copyMove",
    dragImage: _dragImage);
Live sample
not draggable yet the drag image
drag source output
Results will appear here when you interact with the samples.
Warning:
A dropped file's type is a guess The MIME type comes from the file's name, not from looking inside it - so it is a hint, never a check. A page also never learns where a dropped file lives: the name has no path, by design.

API reference

Member
Signature
Description
IsSupported
ValueTask<bool> IsSupported()
True when the runtime exposes DataTransfer. Returns default (false) during prerender/SSR instead of throwing.
OnDrop
ValueTask<ButilSubscription?> OnDrop(ElementReference target, Action<DropPayload> onDrop, string dropEffect = "copy")
Makes an element a drop target, including the dragover handling that makes a drop possible at all. dropEffect chooses the cursor: copy, move, link or none.
ConfigureDragSource
ValueTask<ButilSubscription?> ConfigureDragSource(ElementReference source, Dictionary<string, string> items, string effectAllowed = "all", ElementReference? dragImage = null, int dragImageX = 0, int dragImageY = 0)
Makes an element draggable and decides what it carries. Disposing the subscription makes it undraggable again.
ReadFile
ValueTask<byte[]?> ReadFile(Guid fileId)
A dropped file's bytes, readable long after the drop. Null once released.
ReadFileText
ValueTask<string?> ReadFileText(Guid fileId)
The same file as UTF-8 text.
CreateObjectUrl
ValueTask<string?> CreateObjectUrl(Guid fileId)
A blob: URL for the file, for showing it without reading its bytes into .NET. You own it - revoke it through ObjectUrls.
ReleaseFile
ValueTask ReleaseFile(Guid fileId)
Forgets a dropped file. Reading it afterwards answers null.
DisposeAsync
ValueTask DisposeAsync()
On scope/circuit teardown, detaches every target and source whose subscription was never disposed, and forgets every held file.
DropPayload
record (DroppedFile[] Files, Dictionary<string, string> Items) { string? Text; string? Uri; }
What the drop carried. Items are keyed by MIME type; Text and Uri are the two everyone wants.
DroppedFile
record (Guid Id, string Name, long Size, string Type, long LastModifiedMilliseconds) { DateTimeOffset LastModified; }
One file. Name has no path - a page never learns where a dropped file lives - and Type is guessed from the name.
An unhandled error has occurred. Reload 🗙