Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions examples/Demo/Shared/Pages/Drag/DragDropPage.razor
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,27 @@

<DemoSection Title="Usage examples" Component="@typeof(DragDropBasic)"></DemoSection>

<DemoSection Title="Nested drag & drop" Component="@typeof(DragDropNested)">
<Description>
<p>
This example demonstrates how to nest multiple <code>FluentDragContainer</code> components to enable drag-and-drop interactions across hierarchical structures such as rows, columns, and elements.

The key to making this multi-level drag-and-drop system work is the use of the <code>StopPropagation</code> property. By enabling it where appropriate, each nested <code>FluentDragContainer</code> can handle drag events independently-preventing unintended interference from parent containers.

Each level supports independent drag behavior:
<ul>
<li><strong>Rows</strong> can be reordered vertically.</li>
<li><strong>Columns</strong> can be moved within the same row or between different rows.</li>
<li><strong>Elements</strong> can be rearranged inside a column or moved across columns.</li>
</ul>

The <span style="background-color: yellow; padding: 0 0.2em; color: #000;">yellow area</span> indicates an empty drop zone inside a row. It accepts columns when a row does not contain any yet. Similarly, empty columns behave as drop zones for elements.

This structure allows fully flexible layout editing with deep nesting and drag-and-drop handling at every level.
</p>
</Description>
</DemoSection>

<h2 id="documentation">Documentation</h2>

<ApiDocumentation Component="typeof(FluentDragContainer<>)" InstanceTypes="@(new[] {typeof(object)})" GenericLabel="TItem" />
Expand Down
242 changes: 242 additions & 0 deletions examples/Demo/Shared/Pages/Drag/Examples/DragDropNested.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
ο»Ώ<style>
.form-editor {
color: #000 !important; /* Ignore themes for this demo */
}

.form-row {
border: 1px dashed red;
border-radius: .5em;
background-color: #fff;
padding: 1em;
display: flex;
gap: 1em;
}

.form-column {
flex: 1;
border: 2px dashed orange;
border-radius: .5em;
min-height: 5em;
padding: .5rem;
}

.empty-zone {
width: 100%;
min-height: 50px;
background-color: yellow;
height: 100%;
}

.form-element {
border: 1px solid green;
border-radius: .5em;
padding: 1rem;
}

.form-editor-flex {
display: flex;
flex-direction: column;
gap: .5rem;
}
</style>
<div class="form-editor">
<FluentDragContainer TItem="FormRow" OnDropEnd="OnRowDropEnd">
<FluentDragContainer TItem="FormColumn" OnDropEnd="OnColumnDropEnd">
<FluentDragContainer TItem="FormElement" OnDropEnd="OnDropElement" Class="form-editor-flex">
@foreach (var row in _testForm.Rows)
{
<FluentDropZone StopPropagation="true" Item="row" Class="form-row" Draggable="true" Droppable="true">
<span>Row: @row.RowId</span>
@if (row.Columns.Count == 0)
{
<FluentDropZone StopPropagation="true" Data="@row" TItem="FormColumn" Class="empty-zone" Draggable="false" Droppable="true" />
}
else
{
@foreach (var column in row.Columns)
{
<FluentDropZone StopPropagation="true" Item="column" Data="@row" Class="form-column form-editor-flex" Draggable="true" Droppable="true">
<span>Column: @column.ColumnId</span>
@if (column.Elements.Count == 0)
{
<FluentDropZone StopPropagation="true" Data="@column" TItem="FormElement" Class="empty-zone" Draggable="false" Droppable="true" />
}
else
{
@foreach (var element in column.Elements)
{
<FluentDropZone StopPropagation="true" Item="element" Data="@column" Class="form-element" Draggable="true" Droppable="true">
<span>Element: @element.ElementId</span>
</FluentDropZone>
}
}
</FluentDropZone>
}
}
</FluentDropZone>
}
</FluentDragContainer>
</FluentDragContainer>
</FluentDragContainer>
</div>

@code {
private Form _testForm;

public DragDropNested()
{
_testForm = new Form();

var rows = Enumerable.Range(1, 6)
.Select(id => new FormRow { RowId = id })
.ToList();

var columns = Enumerable.Range(1, 9)
.Select(id => new FormColumn { ColumnId = id })
.ToList();

var elementMap = new Dictionary<int, int>
{
{ 1, 2 },
{ 2, 1 },
{ 3, 3 },
{ 4, 0 },
{ 5, 2 },
{ 6, 1 },
{ 7, 0 },
{ 8, 1 },
{ 9, 0 }
};

int elementIdCounter = 1;
foreach (var column in columns)
{
if (elementMap.TryGetValue(column.ColumnId, out int count))
{
for (int i = 0; i < count; i++)
{
column.Elements.Add(new FormElement
{
ElementId = elementIdCounter++
});
}
}
}

rows[0].Columns.AddRange(new[] { columns[0], columns[1] });
rows[1].Columns.Add(columns[2]);
rows[2].Columns.Add(columns[3]);
rows[3].Columns.AddRange(new[] { columns[4], columns[5] });
rows[4].Columns.AddRange(new[] { columns[6], columns[7] });
rows[5].Columns.Add(columns[8]);

_testForm.Rows.AddRange(rows);
}

private void OnRowDropEnd(FluentDragEventArgs<FormRow> e)
{
var target = e.Target.Item;
var source = e.Source.Item;

int targetIndex = _testForm.Rows.IndexOf(target);

_testForm.Rows.Remove(source);
_testForm.Rows.Insert(targetIndex, source);
}

private void OnColumnDropEnd(FluentDragEventArgs<FormColumn> e)
{
var sourceRow = e.Source.Data as FormRow;
var targetRow = e.Target.Data as FormRow;

if (sourceRow is null || targetRow is null)
{
return;
}

var target = e.Target.Item;
var source = e.Source.Item;
int targetIndex = targetRow.Columns.IndexOf(target);

if (sourceRow == targetRow)
{
sourceRow.Columns.Remove(source);
sourceRow.Columns.Insert(targetIndex, source);
}
else
{
sourceRow.Columns.Remove(source);
if (targetIndex != -1)
{
targetRow.Columns.Insert(targetIndex, source);
}
else
{
targetRow.Columns.Add(source);
}
}

StateHasChanged();
}

private void OnDropElement(FluentDragEventArgs<FormElement> e)
{
var sourceColumn = e.Source.Data as FormColumn;
var targetColumn = e.Target.Data as FormColumn;

if (sourceColumn is null || targetColumn is null)
{
return;
}

var source = e.Source.Item;
var target = e.Target.Item;


int targetIndex = targetColumn.Elements.IndexOf(target);


if (sourceColumn == targetColumn)
{
sourceColumn.Elements.Remove(source);
sourceColumn.Elements.Insert(targetIndex, source);
}
else
{
sourceColumn.Elements.Remove(source);
if (targetIndex != -1)
{
targetColumn.Elements.Insert(targetIndex, source);
}
else
{
targetColumn.Elements.Add(source);
}
}

StateHasChanged();
}

public class Form
{
public int FormId { get; set; }
public List<FormRow> Rows { get; set; } = [];
}

public class FormRow
{
public int RowId { get; set; }
public List<FormColumn> Columns { get; set; } = [];
}

public class FormColumn
{
public int ColumnId { get; set; }
public List<FormElement> Elements { get; set; } = [];
}

public class FormElement
{
public int ElementId { get; set; }
}
}
Loading