Skip to content

Commit 06ae5f5

Browse files
[Docs] Add documentation for nested drag & drop containers (#4050)
* Add sample * Use StopPropagation * Update demo * Add nested drag & drop demo * Fix indention
1 parent 2f4d067 commit 06ae5f5

File tree

2 files changed

+263
-0
lines changed

2 files changed

+263
-0
lines changed

examples/Demo/Shared/Pages/Drag/DragDropPage.razor

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,27 @@
1818

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

21+
<DemoSection Title="Nested drag & drop" Component="@typeof(DragDropNested)">
22+
<Description>
23+
<p>
24+
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.
25+
26+
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.
27+
28+
Each level supports independent drag behavior:
29+
<ul>
30+
<li><strong>Rows</strong> can be reordered vertically.</li>
31+
<li><strong>Columns</strong> can be moved within the same row or between different rows.</li>
32+
<li><strong>Elements</strong> can be rearranged inside a column or moved across columns.</li>
33+
</ul>
34+
35+
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.
36+
37+
This structure allows fully flexible layout editing with deep nesting and drag-and-drop handling at every level.
38+
</p>
39+
</Description>
40+
</DemoSection>
41+
2142
<h2 id="documentation">Documentation</h2>
2243

2344
<ApiDocumentation Component="typeof(FluentDragContainer<>)" InstanceTypes="@(new[] {typeof(object)})" GenericLabel="TItem" />
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
<style>
2+
.form-editor {
3+
color: #000 !important; /* Ignore themes for this demo */
4+
}
5+
6+
.form-row {
7+
border: 1px dashed red;
8+
border-radius: .5em;
9+
background-color: #fff;
10+
padding: 1em;
11+
display: flex;
12+
gap: 1em;
13+
}
14+
15+
.form-column {
16+
flex: 1;
17+
border: 2px dashed orange;
18+
border-radius: .5em;
19+
min-height: 5em;
20+
padding: .5rem;
21+
}
22+
23+
.empty-zone {
24+
width: 100%;
25+
min-height: 50px;
26+
background-color: yellow;
27+
height: 100%;
28+
}
29+
30+
.form-element {
31+
border: 1px solid green;
32+
border-radius: .5em;
33+
padding: 1rem;
34+
}
35+
36+
.form-editor-flex {
37+
display: flex;
38+
flex-direction: column;
39+
gap: .5rem;
40+
}
41+
</style>
42+
<div class="form-editor">
43+
<FluentDragContainer TItem="FormRow" OnDropEnd="OnRowDropEnd">
44+
<FluentDragContainer TItem="FormColumn" OnDropEnd="OnColumnDropEnd">
45+
<FluentDragContainer TItem="FormElement" OnDropEnd="OnDropElement" Class="form-editor-flex">
46+
@foreach (var row in _testForm.Rows)
47+
{
48+
<FluentDropZone StopPropagation="true" Item="row" Class="form-row" Draggable="true" Droppable="true">
49+
<span>Row: @row.RowId</span>
50+
@if (row.Columns.Count == 0)
51+
{
52+
<FluentDropZone StopPropagation="true" Data="@row" TItem="FormColumn" Class="empty-zone" Draggable="false" Droppable="true" />
53+
}
54+
else
55+
{
56+
@foreach (var column in row.Columns)
57+
{
58+
<FluentDropZone StopPropagation="true" Item="column" Data="@row" Class="form-column form-editor-flex" Draggable="true" Droppable="true">
59+
<span>Column: @column.ColumnId</span>
60+
@if (column.Elements.Count == 0)
61+
{
62+
<FluentDropZone StopPropagation="true" Data="@column" TItem="FormElement" Class="empty-zone" Draggable="false" Droppable="true" />
63+
}
64+
else
65+
{
66+
@foreach (var element in column.Elements)
67+
{
68+
<FluentDropZone StopPropagation="true" Item="element" Data="@column" Class="form-element" Draggable="true" Droppable="true">
69+
<span>Element: @element.ElementId</span>
70+
</FluentDropZone>
71+
}
72+
}
73+
</FluentDropZone>
74+
}
75+
}
76+
</FluentDropZone>
77+
}
78+
</FluentDragContainer>
79+
</FluentDragContainer>
80+
</FluentDragContainer>
81+
</div>
82+
83+
@code {
84+
private Form _testForm;
85+
86+
public DragDropNested()
87+
{
88+
_testForm = new Form();
89+
90+
var rows = Enumerable.Range(1, 6)
91+
.Select(id => new FormRow { RowId = id })
92+
.ToList();
93+
94+
var columns = Enumerable.Range(1, 9)
95+
.Select(id => new FormColumn { ColumnId = id })
96+
.ToList();
97+
98+
var elementMap = new Dictionary<int, int>
99+
{
100+
{ 1, 2 },
101+
{ 2, 1 },
102+
{ 3, 3 },
103+
{ 4, 0 },
104+
{ 5, 2 },
105+
{ 6, 1 },
106+
{ 7, 0 },
107+
{ 8, 1 },
108+
{ 9, 0 }
109+
};
110+
111+
int elementIdCounter = 1;
112+
foreach (var column in columns)
113+
{
114+
if (elementMap.TryGetValue(column.ColumnId, out int count))
115+
{
116+
for (int i = 0; i < count; i++)
117+
{
118+
column.Elements.Add(new FormElement
119+
{
120+
ElementId = elementIdCounter++
121+
});
122+
}
123+
}
124+
}
125+
126+
rows[0].Columns.AddRange(new[] { columns[0], columns[1] });
127+
rows[1].Columns.Add(columns[2]);
128+
rows[2].Columns.Add(columns[3]);
129+
rows[3].Columns.AddRange(new[] { columns[4], columns[5] });
130+
rows[4].Columns.AddRange(new[] { columns[6], columns[7] });
131+
rows[5].Columns.Add(columns[8]);
132+
133+
_testForm.Rows.AddRange(rows);
134+
}
135+
136+
private void OnRowDropEnd(FluentDragEventArgs<FormRow> e)
137+
{
138+
var target = e.Target.Item;
139+
var source = e.Source.Item;
140+
141+
int targetIndex = _testForm.Rows.IndexOf(target);
142+
143+
_testForm.Rows.Remove(source);
144+
_testForm.Rows.Insert(targetIndex, source);
145+
}
146+
147+
private void OnColumnDropEnd(FluentDragEventArgs<FormColumn> e)
148+
{
149+
var sourceRow = e.Source.Data as FormRow;
150+
var targetRow = e.Target.Data as FormRow;
151+
152+
if (sourceRow is null || targetRow is null)
153+
{
154+
return;
155+
}
156+
157+
var target = e.Target.Item;
158+
var source = e.Source.Item;
159+
int targetIndex = targetRow.Columns.IndexOf(target);
160+
161+
if (sourceRow == targetRow)
162+
{
163+
sourceRow.Columns.Remove(source);
164+
sourceRow.Columns.Insert(targetIndex, source);
165+
}
166+
else
167+
{
168+
sourceRow.Columns.Remove(source);
169+
if (targetIndex != -1)
170+
{
171+
targetRow.Columns.Insert(targetIndex, source);
172+
}
173+
else
174+
{
175+
targetRow.Columns.Add(source);
176+
}
177+
}
178+
179+
StateHasChanged();
180+
}
181+
182+
private void OnDropElement(FluentDragEventArgs<FormElement> e)
183+
{
184+
var sourceColumn = e.Source.Data as FormColumn;
185+
var targetColumn = e.Target.Data as FormColumn;
186+
187+
if (sourceColumn is null || targetColumn is null)
188+
{
189+
return;
190+
}
191+
192+
var source = e.Source.Item;
193+
var target = e.Target.Item;
194+
195+
196+
int targetIndex = targetColumn.Elements.IndexOf(target);
197+
198+
199+
if (sourceColumn == targetColumn)
200+
{
201+
sourceColumn.Elements.Remove(source);
202+
sourceColumn.Elements.Insert(targetIndex, source);
203+
}
204+
else
205+
{
206+
sourceColumn.Elements.Remove(source);
207+
if (targetIndex != -1)
208+
{
209+
targetColumn.Elements.Insert(targetIndex, source);
210+
}
211+
else
212+
{
213+
targetColumn.Elements.Add(source);
214+
}
215+
}
216+
217+
StateHasChanged();
218+
}
219+
220+
public class Form
221+
{
222+
public int FormId { get; set; }
223+
public List<FormRow> Rows { get; set; } = [];
224+
}
225+
226+
public class FormRow
227+
{
228+
public int RowId { get; set; }
229+
public List<FormColumn> Columns { get; set; } = [];
230+
}
231+
232+
public class FormColumn
233+
{
234+
public int ColumnId { get; set; }
235+
public List<FormElement> Elements { get; set; } = [];
236+
}
237+
238+
public class FormElement
239+
{
240+
public int ElementId { get; set; }
241+
}
242+
}

0 commit comments

Comments
 (0)