@inject BdndInterop Interop
@Card.Text
@code {
public record CardModel(int Id, string Text);
public class DragItem
{
public int Id { get; init; }
public int Index { get; set; }
}
[Parameter, EditorRequired] public CardModel Card { get; set; } = default!;
[Parameter, EditorRequired] public int Index { get; set; }
/// Invoked with (dragIndex, hoverIndex) when a reorder should occur.
[Parameter] public EventCallback<(int From, int To)> OnMove { get; set; }
/// Invoked when this card begins dragging (passes its current index).
[Parameter] public EventCallback OnDragBegin { get; set; }
/// Invoked when this card stops dragging (passes whether a target handled the drop).
[Parameter] public EventCallback OnDragEnd { get; set; }
private ElementReference _el;
private DragItem _dragItem = new();
private bool _measuring;
private Task HandleBegin(DragItem? _) => OnDragBegin.InvokeAsync(Index);
private Task HandleEnd(BdndDragEndContext e) => OnDragEnd.InvokeAsync(e.DidDrop);
// Any drop on a card already counts as handled (monitor.DidDrop() == true); the result is
// published for the drag source to inspect through monitor.GetDropResult().
private void HandleDrop(BdndDropEventArgs e) => e.Result = "reordered";
protected override void OnParametersSet()
{
// keep the (stable) drag item in sync with the card's current position
_dragItem = _dragItem.Id == Card.Id
? _dragItem
: new DragItem { Id = Card.Id };
_dragItem.Index = Index;
}
private async Task HandleHover(BdndDropEventArgs e)
{
var item = e.Item;
if (item is null || item.Index == Index || _measuring) return;
// Measuring is asynchronous: skip hovers that arrive meanwhile, and re-read the indices
// afterwards because other cards may have reordered the list in between.
BdndDomRect? rect;
_measuring = true;
try
{
rect = await Interop.GetBoundingClientRectAsync(_el);
}
finally
{
_measuring = false;
}
int dragIndex = item.Index;
int hoverIndex = Index;
var clientOffset = e.Monitor.GetClientOffset();
if (rect is null || clientOffset is null || dragIndex == hoverIndex || e.Monitor.IsDragging() is false) return;
// Only move past the halfway line, to avoid flicker (React DnD's algorithm).
double hoverMiddleY = rect.Height / 2;
double hoverClientY = clientOffset.Value.Y - rect.Top;
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) return;
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) return;
await OnMove.InvokeAsync((dragIndex, hoverIndex));
item.Index = hoverIndex; // mutate the monitored item, like React DnD
}
}