@libs-ui/components-drag-drop 0.2.355-8 → 0.2.356-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +299 -280
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,28 +1,70 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @libs-ui/components-drag-drop
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Thư viện Angular directives hỗ trợ kéo thả (drag and drop) linh hoạt, hỗ trợ kéo thả giữa các container, virtual scrolling, auto-scroll và custom boundary.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Version
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
`0.2.355-14`
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
## Author
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
Mobio Dev Team
|
|
12
|
+
|
|
13
|
+
## Khi nào sử dụng
|
|
14
|
+
|
|
15
|
+
- Khi cần sắp xếp lại thứ tự các phần tử trong một danh sách bằng thao tác kéo thả.
|
|
16
|
+
- Khi cần di chuyển phần tử giữa nhiều container (cross-container drag and drop).
|
|
17
|
+
- Khi cần triển khai Kanban board với nhiều cột có thể kéo thả qua lại.
|
|
18
|
+
- Khi danh sách lớn cần kết hợp virtual scroll để tối ưu hiệu năng.
|
|
19
|
+
- Khi cần giới hạn vùng kéo thả trong một boundary cụ thể.
|
|
20
|
+
- Khi cần auto-scroll khi kéo phần tử gần mép container.
|
|
21
|
+
|
|
22
|
+
## Lưu ý quan trọng
|
|
23
|
+
|
|
24
|
+
⚠️ **Two-way binding**: `[(items)]` là bắt buộc trên Container directive. Danh sách items sẽ tự động cập nhật khi kéo thả.
|
|
25
|
+
|
|
26
|
+
⚠️ **Group Name**: Khi kéo thả giữa các container, cần thiết lập `[groupName]` và `[dropToGroupName]` để xác định nguồn và đích.
|
|
27
|
+
|
|
28
|
+
⚠️ **Virtual Scroll**: Khi sử dụng virtual scroll, cần bật `[itemInContainerVirtualScroll]="true"` trên Item directive và cung cấp `[fieldId]` để định danh item.
|
|
29
|
+
|
|
30
|
+
⚠️ **ViewEncapsulation**: Mặc định là `'emulated'`. Chuyển sang `'none'` nếu cần style xuyên qua shadow DOM.
|
|
31
|
+
|
|
32
|
+
⚠️ **Mode copy**: Khi `[mode]="'copy'"`, item sẽ được sao chép thay vì di chuyển sang container đích.
|
|
33
|
+
|
|
34
|
+
⚠️ **Performance**: Sử dụng `[throttleTimeHandlerDraggingEvent]` để throttle event dragging, giảm tải khi danh sách lớn.
|
|
35
|
+
|
|
36
|
+
⚠️ **Padding thay vì Margin**: Khoảng cách giữa các drag item **PHẢI** dùng `padding` (ví dụ `pb-2`), **KHÔNG** dùng `margin` (ví dụ `mb-2`). Khi dùng margin, vùng margin giữa các item không thuộc bất kỳ element nào, khiến thư viện không phát hiện được vị trí hover chính xác và sẽ thêm phần tử xuống cuối danh sách thay vì vị trí đang hover.
|
|
37
|
+
|
|
38
|
+
## Cài đặt & Import
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import {
|
|
42
|
+
LibsUiComponentsDragContainerDirective,
|
|
43
|
+
LibsUiDragItemDirective,
|
|
44
|
+
LibsUiComponentsDragScrollDirective,
|
|
45
|
+
LibsUiDragItemInContainerVirtualScrollDirective,
|
|
46
|
+
} from '@libs-ui/components-drag-drop';
|
|
13
47
|
```
|
|
14
48
|
|
|
15
|
-
|
|
49
|
+
## Ví dụ sử dụng
|
|
50
|
+
|
|
51
|
+
### Basic - Sắp xếp danh sách đơn giản
|
|
16
52
|
|
|
17
53
|
```typescript
|
|
18
|
-
import {
|
|
54
|
+
import { Component } from '@angular/core';
|
|
55
|
+
import {
|
|
56
|
+
LibsUiComponentsDragContainerDirective,
|
|
57
|
+
LibsUiDragItemDirective,
|
|
58
|
+
} from '@libs-ui/components-drag-drop';
|
|
19
59
|
|
|
20
60
|
@Component({
|
|
21
|
-
selector: 'app-drag-drop
|
|
61
|
+
selector: 'app-basic-drag-drop',
|
|
62
|
+
standalone: true,
|
|
63
|
+
imports: [LibsUiComponentsDragContainerDirective, LibsUiDragItemDirective],
|
|
22
64
|
template: `
|
|
23
65
|
<div
|
|
24
66
|
LibsUiComponentsDragContainerDirective
|
|
25
|
-
[items]="items">
|
|
67
|
+
[(items)]="items">
|
|
26
68
|
<div
|
|
27
69
|
*ngFor="let item of items"
|
|
28
70
|
LibsUiDragItemDirective
|
|
@@ -31,10 +73,8 @@ import { LibsUiComponentsDragContainerDirective, LibsUiDragItemDirective } from
|
|
|
31
73
|
</div>
|
|
32
74
|
</div>
|
|
33
75
|
`,
|
|
34
|
-
standalone: true,
|
|
35
|
-
imports: [LibsUiComponentsDragContainerDirective, LibsUiDragItemDirective],
|
|
36
76
|
})
|
|
37
|
-
export class
|
|
77
|
+
export class BasicDragDropComponent {
|
|
38
78
|
items = [
|
|
39
79
|
{ id: 1, name: 'Item 1' },
|
|
40
80
|
{ id: 2, name: 'Item 2' },
|
|
@@ -43,204 +83,296 @@ export class DragDropDemoComponent {
|
|
|
43
83
|
}
|
|
44
84
|
```
|
|
45
85
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
### 1. Container-to-Container Drag
|
|
86
|
+
### Cross Container - Kéo thả giữa hai container
|
|
49
87
|
|
|
50
88
|
```typescript
|
|
51
89
|
@Component({
|
|
90
|
+
standalone: true,
|
|
91
|
+
imports: [LibsUiComponentsDragContainerDirective, LibsUiDragItemDirective],
|
|
52
92
|
template: `
|
|
53
|
-
<div class="
|
|
54
|
-
|
|
55
|
-
<div
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
93
|
+
<div class="flex gap-4">
|
|
94
|
+
<!-- Container nguồn -->
|
|
95
|
+
<div
|
|
96
|
+
LibsUiComponentsDragContainerDirective
|
|
97
|
+
[(items)]="sourceItems"
|
|
98
|
+
[groupName]="'source'"
|
|
99
|
+
[dropToGroupName]="['target']"
|
|
100
|
+
(outDroppedContainer)="onDrop($event)">
|
|
101
|
+
<div
|
|
102
|
+
*ngFor="let item of sourceItems"
|
|
103
|
+
LibsUiDragItemDirective
|
|
104
|
+
[item]="item">
|
|
62
105
|
{{ item.name }}
|
|
63
106
|
</div>
|
|
64
107
|
</div>
|
|
65
|
-
</div>
|
|
66
108
|
|
|
67
|
-
|
|
68
|
-
<
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
109
|
+
<!-- Container đích -->
|
|
110
|
+
<div
|
|
111
|
+
LibsUiComponentsDragContainerDirective
|
|
112
|
+
[(items)]="targetItems"
|
|
113
|
+
[groupName]="'target'"
|
|
114
|
+
[dropToGroupName]="['source']"
|
|
115
|
+
(outDroppedContainer)="onDrop($event)">
|
|
116
|
+
<div
|
|
117
|
+
*ngFor="let item of targetItems"
|
|
118
|
+
LibsUiDragItemDirective
|
|
119
|
+
[item]="item">
|
|
76
120
|
{{ item.name }}
|
|
77
121
|
</div>
|
|
78
122
|
</div>
|
|
79
123
|
</div>
|
|
80
124
|
`,
|
|
81
|
-
styles: [`
|
|
82
|
-
.container {
|
|
83
|
-
border: 1px solid #ccc;
|
|
84
|
-
padding: 10px;
|
|
85
|
-
margin: 10px;
|
|
86
|
-
min-height: 200px;
|
|
87
|
-
}
|
|
88
|
-
`]
|
|
89
125
|
})
|
|
126
|
+
export class CrossContainerComponent {
|
|
127
|
+
sourceItems = [
|
|
128
|
+
{ id: 1, name: 'Source 1' },
|
|
129
|
+
{ id: 2, name: 'Source 2' },
|
|
130
|
+
];
|
|
131
|
+
targetItems = [
|
|
132
|
+
{ id: 3, name: 'Target 1' },
|
|
133
|
+
{ id: 4, name: 'Target 2' },
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
onDrop(event: IDrop) {
|
|
137
|
+
console.log('Dropped:', event);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Kanban Board
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
@Component({
|
|
146
|
+
standalone: true,
|
|
147
|
+
imports: [LibsUiComponentsDragContainerDirective, LibsUiDragItemDirective],
|
|
148
|
+
template: `
|
|
149
|
+
<div class="flex gap-4">
|
|
150
|
+
@for (column of columns; track column.id) {
|
|
151
|
+
<div class="flex-1 bg-gray-100 p-4 rounded-lg">
|
|
152
|
+
<h3>{{ column.title }}</h3>
|
|
153
|
+
<div
|
|
154
|
+
LibsUiComponentsDragContainerDirective
|
|
155
|
+
[(items)]="column.items"
|
|
156
|
+
[groupName]="column.id"
|
|
157
|
+
[dropToGroupName]="allGroupNames"
|
|
158
|
+
[directionDrag]="'vertical'">
|
|
159
|
+
@for (item of column.items; track item.id) {
|
|
160
|
+
<div
|
|
161
|
+
class="bg-white p-3 mb-2 rounded shadow-sm"
|
|
162
|
+
LibsUiDragItemDirective
|
|
163
|
+
[item]="item">
|
|
164
|
+
{{ item.title }}
|
|
165
|
+
</div>
|
|
166
|
+
}
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
}
|
|
170
|
+
</div>
|
|
171
|
+
`,
|
|
172
|
+
})
|
|
173
|
+
export class KanbanBoardComponent {
|
|
174
|
+
columns = [
|
|
175
|
+
{ id: 'todo', title: 'To Do', items: [{ id: 1, title: 'Task 1' }] },
|
|
176
|
+
{ id: 'doing', title: 'Doing', items: [{ id: 2, title: 'Task 2' }] },
|
|
177
|
+
{ id: 'done', title: 'Done', items: [{ id: 3, title: 'Task 3' }] },
|
|
178
|
+
];
|
|
179
|
+
allGroupNames = ['todo', 'doing', 'done'];
|
|
180
|
+
}
|
|
90
181
|
```
|
|
91
182
|
|
|
92
|
-
###
|
|
183
|
+
### Virtual Scroll
|
|
93
184
|
|
|
94
185
|
```typescript
|
|
95
186
|
@Component({
|
|
187
|
+
standalone: true,
|
|
188
|
+
imports: [
|
|
189
|
+
LibsUiComponentsDragContainerDirective,
|
|
190
|
+
LibsUiDragItemDirective,
|
|
191
|
+
LibsUiComponentsDragScrollDirective,
|
|
192
|
+
LibsUiDragItemInContainerVirtualScrollDirective,
|
|
193
|
+
],
|
|
96
194
|
template: `
|
|
97
|
-
<virtual-scroller [items]="items">
|
|
98
|
-
<div
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
195
|
+
<virtual-scroller #scroll [items]="items">
|
|
196
|
+
<div
|
|
197
|
+
LibsUiComponentsDragContainerDirective
|
|
198
|
+
LibsUiComponentsDragScrollDirective
|
|
199
|
+
[(items)]="items">
|
|
200
|
+
<div
|
|
201
|
+
*ngFor="let item of scroll.viewPortItems"
|
|
202
|
+
LibsUiDragItemDirective
|
|
203
|
+
[item]="item"
|
|
204
|
+
[itemInContainerVirtualScroll]="true"
|
|
205
|
+
[fieldId]="'id'">
|
|
104
206
|
{{ item.name }}
|
|
105
207
|
</div>
|
|
106
208
|
</div>
|
|
107
209
|
</virtual-scroller>
|
|
108
|
-
|
|
210
|
+
`,
|
|
109
211
|
})
|
|
212
|
+
export class VirtualScrollDragDropComponent {
|
|
213
|
+
items = Array.from({ length: 1000 }, (_, i) => ({ id: i, name: `Item ${i}` }));
|
|
214
|
+
}
|
|
110
215
|
```
|
|
111
216
|
|
|
112
|
-
###
|
|
217
|
+
### Custom Boundary - Giới hạn vùng kéo thả
|
|
113
218
|
|
|
114
219
|
```typescript
|
|
115
220
|
@Component({
|
|
221
|
+
standalone: true,
|
|
222
|
+
imports: [LibsUiComponentsDragContainerDirective, LibsUiDragItemDirective],
|
|
116
223
|
template: `
|
|
117
|
-
<div class="
|
|
118
|
-
<div
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
<div
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
224
|
+
<div class="relative w-[500px] h-[500px] border-2 border-gray-800">
|
|
225
|
+
<div
|
|
226
|
+
LibsUiComponentsDragContainerDirective
|
|
227
|
+
[(items)]="items">
|
|
228
|
+
<div
|
|
229
|
+
*ngFor="let item of items"
|
|
230
|
+
LibsUiDragItemDirective
|
|
231
|
+
[item]="item"
|
|
232
|
+
[dragBoundary]="true">
|
|
125
233
|
{{ item.name }}
|
|
126
234
|
</div>
|
|
127
235
|
</div>
|
|
128
236
|
</div>
|
|
129
237
|
`,
|
|
130
|
-
styles: [`
|
|
131
|
-
.boundary-container {
|
|
132
|
-
position: relative;
|
|
133
|
-
width: 500px;
|
|
134
|
-
height: 500px;
|
|
135
|
-
border: 2px solid #333;
|
|
136
|
-
}
|
|
137
|
-
`]
|
|
138
238
|
})
|
|
239
|
+
export class CustomBoundaryComponent {
|
|
240
|
+
items = [
|
|
241
|
+
{ id: 1, name: 'Bounded Item 1' },
|
|
242
|
+
{ id: 2, name: 'Bounded Item 2' },
|
|
243
|
+
];
|
|
244
|
+
}
|
|
139
245
|
```
|
|
140
246
|
|
|
141
|
-
##
|
|
247
|
+
## API Reference
|
|
142
248
|
|
|
143
|
-
###
|
|
249
|
+
### LibsUiComponentsDragContainerDirective
|
|
144
250
|
|
|
145
|
-
|
|
146
|
-
| ---------------------- | ------------------------------ | ------------------------- | ---------------------------- |
|
|
147
|
-
| `mode` | 'move' \| 'copy' \| 'deepCopy' | 'move' | Drag operation mode |
|
|
148
|
-
| `directionDrag` | 'horizontal' \| 'vertical' | undefined | Drag direction |
|
|
149
|
-
| `groupName` | string | 'groupDragAndDropDefault' | Group identifier |
|
|
150
|
-
| `dropToGroupName` | string[] | null | Allowed drop targets |
|
|
151
|
-
| `disableDragContainer` | boolean | false | Disable drag functionality |
|
|
152
|
-
| `placeholder` | boolean | true | Show placeholder during drag |
|
|
251
|
+
Selector: `[LibsUiComponentsDragContainerDirective]`
|
|
153
252
|
|
|
154
|
-
|
|
253
|
+
#### Inputs
|
|
155
254
|
|
|
156
|
-
| Property
|
|
157
|
-
|
|
158
|
-
| `
|
|
159
|
-
| `
|
|
160
|
-
| `
|
|
161
|
-
| `
|
|
162
|
-
| `
|
|
255
|
+
| Property | Type | Default | Mô tả |
|
|
256
|
+
|---|---|---|---|
|
|
257
|
+
| `[acceptDragSameGroup]` | `boolean` | `false` | Cho phép kéo thả trong cùng group. |
|
|
258
|
+
| `[directionDrag]` | `'horizontal' \| 'vertical'` | `undefined` | Hướng kéo thả (ngang hoặc dọc). |
|
|
259
|
+
| `[disableDragContainer]` | `boolean` | `undefined` | Vô hiệu hóa toàn bộ kéo thả trong container. |
|
|
260
|
+
| `[dropToGroupName]` | `Array<string> \| null` | `null` | Danh sách group name được phép drop vào container này. |
|
|
261
|
+
| `[groupName]` | `string` | `'groupDragAndDropDefault'` | Tên group để định danh container. |
|
|
262
|
+
| `[(items)]` | `Array<unknown>` | *bắt buộc* | Danh sách items (two-way binding). |
|
|
263
|
+
| `[mode]` | `'move' \| 'copy'` | `'move'` | Chế độ kéo thả: di chuyển hoặc sao chép. |
|
|
264
|
+
| `[placeholder]` | `boolean` | `true` | Hiển thị placeholder tại vị trí drop. |
|
|
265
|
+
| `[stylesOverride]` | `Array<{className, styles}>` | `undefined` | Ghi đè styles cho container. |
|
|
266
|
+
| `[viewEncapsulation]` | `'emulated' \| 'none'` | `'emulated'` | Chế độ View Encapsulation cho styles. |
|
|
163
267
|
|
|
164
|
-
|
|
268
|
+
#### Outputs
|
|
165
269
|
|
|
166
|
-
|
|
270
|
+
| Property | Type | Mô tả |
|
|
271
|
+
|---|---|---|
|
|
272
|
+
| `(outDragEndContainer)` | `IDragEnd` | Emit khi kết thúc kéo phần tử trong container. |
|
|
273
|
+
| `(outDragLeaveContainer)` | `IDragLeave` | Emit khi phần tử kéo rời khỏi container. |
|
|
274
|
+
| `(outDragOverContainer)` | `IDragOver` | Emit khi phần tử kéo di chuyển qua container. |
|
|
275
|
+
| `(outDragStartContainer)` | `IDragStart` | Emit khi bắt đầu kéo phần tử trong container. |
|
|
276
|
+
| `(outDroppedContainer)` | `IDrop` | Emit khi thả phần tử vào container. |
|
|
277
|
+
| `(outDroppedContainerEmpty)` | `IDrop` | Emit khi thả phần tử vào container rỗng. |
|
|
278
|
+
| `(outFunctionControl)` | `IDragDropFunctionControlEvent` | Emit object chứa các hàm điều khiển container. |
|
|
167
279
|
|
|
168
|
-
|
|
169
|
-
@Component({
|
|
170
|
-
template: `
|
|
171
|
-
<div LibsUiComponentsDragContainerDirective
|
|
172
|
-
(outDragStartContainer)="onDragStart($event)"
|
|
173
|
-
(outDragOverContainer)="onDragOver($event)"
|
|
174
|
-
(outDragLeaveContainer)="onDragLeave($event)"
|
|
175
|
-
(outDragEndContainer)="onDragEnd($event)"
|
|
176
|
-
(outDroppedContainer)="onDrop($event)">
|
|
177
|
-
<!-- Items -->
|
|
178
|
-
</div>
|
|
179
|
-
`
|
|
180
|
-
})
|
|
181
|
-
```
|
|
280
|
+
### LibsUiDragItemDirective
|
|
182
281
|
|
|
183
|
-
|
|
282
|
+
Selector: `[LibsUiDragItemDirective]`
|
|
184
283
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
284
|
+
#### Inputs
|
|
285
|
+
|
|
286
|
+
| Property | Type | Default | Mô tả |
|
|
287
|
+
|---|---|---|---|
|
|
288
|
+
| `[disable]` | `boolean` | `undefined` | Vô hiệu hóa kéo thả cho item này. |
|
|
289
|
+
| `[dragBoundary]` | `boolean` | `undefined` | Giới hạn kéo trong vùng boundary của container. |
|
|
290
|
+
| `[dragBoundaryAcceptMouseLeaveContainer]` | `boolean` | `undefined` | Cho phép chuột rời container khi đang giới hạn boundary. |
|
|
291
|
+
| `[dragRootElement]` | `boolean` | `undefined` | Sử dụng root element làm gốc kéo. |
|
|
292
|
+
| `[elementContainer]` | `HTMLElement` | `undefined` | Element container tùy chỉnh cho item. |
|
|
293
|
+
| `[fieldId]` | `string` | `''` | Tên field dùng làm ID định danh item. |
|
|
294
|
+
| `[ignoreStopEvent]` | `boolean` | `undefined` | Bỏ qua stop event mặc định. |
|
|
295
|
+
| `[ignoreUserSelectNone]` | `boolean` | `undefined` | Bỏ qua việc set `user-select: none` khi kéo. |
|
|
296
|
+
| `[item]` | `any` | `undefined` | Dữ liệu của item. |
|
|
297
|
+
| `[itemInContainerVirtualScroll]` | `boolean` | `undefined` | Đánh dấu item nằm trong virtual scroll container. |
|
|
298
|
+
| `[onlyMouseDownStopEvent]` | `boolean` | `undefined` | Chỉ stop event khi mousedown. |
|
|
299
|
+
| `[throttleTimeHandlerDraggingEvent]` | `number` | `0` | Thời gian throttle (ms) cho dragging event. |
|
|
300
|
+
| `[zIndex]` | `number` | `1300` | z-index của phần tử khi đang kéo. |
|
|
301
|
+
|
|
302
|
+
#### Outputs
|
|
303
|
+
|
|
304
|
+
| Property | Type | Mô tả |
|
|
305
|
+
|---|---|---|
|
|
306
|
+
| `(outDragEnd)` | `IDragEnd` | Emit khi kết thúc kéo item. |
|
|
307
|
+
| `(outDragLeave)` | `IDragLeave` | Emit khi item rời khỏi vùng drop. |
|
|
308
|
+
| `(outDragOver)` | `IDragOver` | Emit khi item di chuyển qua vùng drop. |
|
|
309
|
+
| `(outDragStart)` | `IDragStart` | Emit khi bắt đầu kéo item. |
|
|
310
|
+
| `(outDropped)` | `IDrop` | Emit khi item được thả. |
|
|
311
|
+
|
|
312
|
+
### LibsUiComponentsDragScrollDirective
|
|
313
|
+
|
|
314
|
+
Selector: `[LibsUiComponentsDragScrollDirective]`
|
|
315
|
+
|
|
316
|
+
Directive tự động scroll container khi phần tử kéo di chuyển gần mép.
|
|
317
|
+
|
|
318
|
+
#### Inputs
|
|
199
319
|
|
|
200
|
-
|
|
320
|
+
| Property | Type | Default | Mô tả |
|
|
321
|
+
|---|---|---|---|
|
|
322
|
+
| `[ignoreAutoScroll]` | `boolean` | `undefined` | Tắt tính năng auto scroll. |
|
|
323
|
+
| `[widthZoneDetect]` | `number` | `16` | Chiều rộng vùng phát hiện (px) gần mép để kích hoạt scroll. |
|
|
324
|
+
| `[movementLength]` | `number` | `6` | Khoảng cách scroll mỗi lần (px). |
|
|
325
|
+
| `[rootElementScroll]` | `HTMLElement` | `undefined` | Element gốc để scroll (mặc định là host element). |
|
|
326
|
+
| `[virtualScrollerComponent]` | `VirtualScrollerComponent` | `undefined` | Tham chiếu đến VirtualScrollerComponent. |
|
|
201
327
|
|
|
202
|
-
###
|
|
328
|
+
### LibsUiDragItemInContainerVirtualScrollDirective
|
|
329
|
+
|
|
330
|
+
Selector: `[LibsUiDragItemInContainerVirtualScrollDirective]`
|
|
331
|
+
|
|
332
|
+
Directive hỗ trợ kéo thả item trong container sử dụng virtual scroll (`@iharbeck/ngx-virtual-scroller`).
|
|
333
|
+
|
|
334
|
+
## Types & Interfaces
|
|
203
335
|
|
|
204
336
|
```typescript
|
|
205
|
-
interface IDragging {
|
|
337
|
+
export interface IDragging {
|
|
206
338
|
mousePosition: IMousePosition;
|
|
207
339
|
elementDrag: HTMLElement;
|
|
208
340
|
elementKeepContainer?: boolean;
|
|
209
341
|
itemDragInfo?: IItemDragInfo;
|
|
210
342
|
}
|
|
211
343
|
|
|
212
|
-
interface IDragStart {
|
|
344
|
+
export interface IDragStart {
|
|
213
345
|
mousePosition: IMousePosition;
|
|
214
346
|
elementDrag: HTMLElement;
|
|
215
347
|
itemDragInfo?: IItemDragInfo;
|
|
216
348
|
}
|
|
217
349
|
|
|
218
|
-
interface IDragOver {
|
|
350
|
+
export interface IDragOver {
|
|
219
351
|
mousePosition: IMousePosition;
|
|
220
352
|
elementDrag: HTMLElement;
|
|
221
353
|
elementDragOver: HTMLElement;
|
|
222
354
|
itemDragInfo?: IItemDragInfo;
|
|
223
355
|
}
|
|
224
356
|
|
|
225
|
-
interface IDragLeave {
|
|
357
|
+
export interface IDragLeave {
|
|
226
358
|
elementDrag: HTMLElement;
|
|
227
359
|
elementDragLeave: HTMLElement;
|
|
228
360
|
itemDragInfo?: IItemDragInfo;
|
|
229
361
|
}
|
|
230
362
|
|
|
231
|
-
interface IDragEnd {
|
|
363
|
+
export interface IDragEnd {
|
|
232
364
|
mousePosition: IMousePosition;
|
|
233
365
|
elementDrag: HTMLElement;
|
|
234
366
|
itemDragInfo?: IItemDragInfo;
|
|
235
367
|
}
|
|
236
368
|
|
|
237
|
-
interface IDrop {
|
|
369
|
+
export interface IDrop {
|
|
238
370
|
elementDrag: HTMLElement;
|
|
239
371
|
elementDrop: HTMLElement;
|
|
240
372
|
itemDragInfo?: IItemDragInfo;
|
|
241
373
|
}
|
|
242
374
|
|
|
243
|
-
interface IItemDragInfo {
|
|
375
|
+
export interface IItemDragInfo {
|
|
244
376
|
item: object;
|
|
245
377
|
itemsMove?: WritableSignal<Array<unknown>>;
|
|
246
378
|
indexDrag?: number;
|
|
@@ -251,7 +383,7 @@ interface IItemDragInfo {
|
|
|
251
383
|
containerDrop?: HTMLElement;
|
|
252
384
|
}
|
|
253
385
|
|
|
254
|
-
interface IDragItemInContainerVirtualScroll {
|
|
386
|
+
export interface IDragItemInContainerVirtualScroll {
|
|
255
387
|
itemDragInfo?: IItemDragInfo;
|
|
256
388
|
elementDrag: HTMLElement;
|
|
257
389
|
distanceStartElementAndMouseTop: number;
|
|
@@ -260,69 +392,72 @@ interface IDragItemInContainerVirtualScroll {
|
|
|
260
392
|
dragBoundary?: boolean;
|
|
261
393
|
dragBoundaryAcceptMouseLeaveContainer?: boolean;
|
|
262
394
|
ignoreStopEvent?: boolean;
|
|
395
|
+
ignoreUserSelectNone: boolean;
|
|
263
396
|
}
|
|
264
397
|
|
|
265
|
-
interface IMousePosition {
|
|
398
|
+
export interface IMousePosition {
|
|
266
399
|
clientX: number;
|
|
267
400
|
clientY: number;
|
|
268
401
|
}
|
|
269
402
|
|
|
270
|
-
interface IDragDropFunctionControlEvent {
|
|
403
|
+
export interface IDragDropFunctionControlEvent {
|
|
271
404
|
setAttributeElementAndItemDrag: () => Promise<void>;
|
|
272
405
|
}
|
|
273
406
|
```
|
|
274
407
|
|
|
275
|
-
## Styling
|
|
276
|
-
|
|
277
|
-
### Default Classes
|
|
408
|
+
## Tùy chỉnh giao diện (Styling)
|
|
278
409
|
|
|
279
|
-
|
|
280
|
-
/* Container */
|
|
281
|
-
.libs-ui-drag-drop-container {
|
|
282
|
-
position: relative;
|
|
283
|
-
min-height: 100px;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/* Item */
|
|
287
|
-
.libs-ui-drag-drop-item {
|
|
288
|
-
cursor: move;
|
|
289
|
-
transition: transform 0.2s;
|
|
290
|
-
}
|
|
410
|
+
### CSS Classes mặc định
|
|
291
411
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
412
|
+
| Class | Mô tả |
|
|
413
|
+
|---|---|
|
|
414
|
+
| `.libs-ui-drag-drop-container` | Class cho container kéo thả. |
|
|
415
|
+
| `.libs-ui-drag-drop-item` | Class cho mỗi item kéo thả. |
|
|
416
|
+
| `.libs-ui-drag-drop-item-dragging` | Class được thêm vào item đang được kéo. |
|
|
417
|
+
| `.libs-ui-drag-drop-item-placeholder` | Class cho placeholder tại vị trí drop. |
|
|
297
418
|
|
|
298
|
-
|
|
299
|
-
.libs-ui-drag-drop-item-placeholder {
|
|
300
|
-
background: #f0f0f0;
|
|
301
|
-
border: 2px dashed #ccc;
|
|
302
|
-
}
|
|
303
|
-
```
|
|
419
|
+
### Sử dụng stylesOverride
|
|
304
420
|
|
|
305
|
-
|
|
421
|
+
Ghi đè styles thông qua input `[stylesOverride]` trên Container directive:
|
|
306
422
|
|
|
307
423
|
```typescript
|
|
308
424
|
@Component({
|
|
425
|
+
standalone: true,
|
|
426
|
+
imports: [LibsUiComponentsDragContainerDirective, LibsUiDragItemDirective],
|
|
309
427
|
template: `
|
|
310
428
|
<div
|
|
311
429
|
LibsUiComponentsDragContainerDirective
|
|
430
|
+
[(items)]="items"
|
|
312
431
|
[stylesOverride]="customStyles">
|
|
313
|
-
|
|
432
|
+
<div
|
|
433
|
+
*ngFor="let item of items"
|
|
434
|
+
LibsUiDragItemDirective
|
|
435
|
+
[item]="item">
|
|
436
|
+
{{ item.name }}
|
|
437
|
+
</div>
|
|
314
438
|
</div>
|
|
315
439
|
`,
|
|
316
440
|
})
|
|
317
441
|
export class CustomStyledComponent {
|
|
442
|
+
items = [{ id: 1, name: 'Item 1' }];
|
|
443
|
+
|
|
318
444
|
customStyles = [
|
|
319
445
|
{
|
|
320
|
-
className: 'custom-container',
|
|
446
|
+
className: 'custom-drag-container',
|
|
321
447
|
styles: `
|
|
322
|
-
.custom-container {
|
|
448
|
+
.custom-drag-container {
|
|
323
449
|
background: #f5f5f5;
|
|
324
450
|
border-radius: 8px;
|
|
325
|
-
padding:
|
|
451
|
+
padding: 16px;
|
|
452
|
+
}
|
|
453
|
+
`,
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
className: 'custom-drag-item-dragging',
|
|
457
|
+
styles: `
|
|
458
|
+
.custom-drag-item-dragging {
|
|
459
|
+
opacity: 0.6;
|
|
460
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
326
461
|
}
|
|
327
462
|
`,
|
|
328
463
|
},
|
|
@@ -330,122 +465,6 @@ export class CustomStyledComponent {
|
|
|
330
465
|
}
|
|
331
466
|
```
|
|
332
467
|
|
|
333
|
-
##
|
|
468
|
+
## Demo
|
|
334
469
|
|
|
335
|
-
|
|
336
|
-
- Use virtual scrolling for lists with more than 100 items
|
|
337
|
-
- Implement proper event throttling
|
|
338
|
-
- Clean up event listeners in ngOnDestroy
|
|
339
|
-
|
|
340
|
-
2. **User Experience**
|
|
341
|
-
- Provide clear visual feedback during drag operations
|
|
342
|
-
- Implement smooth animations
|
|
343
|
-
- Handle edge cases (container boundaries, scrolling)
|
|
344
|
-
|
|
345
|
-
3. **Accessibility**
|
|
346
|
-
- Ensure keyboard navigation support
|
|
347
|
-
- Provide proper ARIA labels
|
|
348
|
-
- Support screen readers
|
|
349
|
-
|
|
350
|
-
## Troubleshooting
|
|
351
|
-
|
|
352
|
-
### Common Issues
|
|
353
|
-
|
|
354
|
-
1. **Drag Not Working**
|
|
355
|
-
- Verify container and item configurations
|
|
356
|
-
- Check event bindings
|
|
357
|
-
- Ensure proper group settings
|
|
358
|
-
|
|
359
|
-
2. **Performance Issues**
|
|
360
|
-
- Implement virtual scrolling
|
|
361
|
-
- Use event throttling
|
|
362
|
-
- Optimize event handlers
|
|
363
|
-
|
|
364
|
-
3. **Container Boundaries**
|
|
365
|
-
- Verify dragBoundary settings
|
|
366
|
-
- Check container dimensions
|
|
367
|
-
- Ensure proper event handling
|
|
368
|
-
|
|
369
|
-
## Examples
|
|
370
|
-
|
|
371
|
-
### Basic List
|
|
372
|
-
|
|
373
|
-
```typescript
|
|
374
|
-
@Component({
|
|
375
|
-
template: `
|
|
376
|
-
<div class="list-container">
|
|
377
|
-
<div LibsUiComponentsDragContainerDirective [items]="items">
|
|
378
|
-
<div *ngFor="let item of items"
|
|
379
|
-
class="list-item"
|
|
380
|
-
LibsUiDragItemDirective
|
|
381
|
-
[item]="item">
|
|
382
|
-
<span class="item-icon">📋</span>
|
|
383
|
-
<span class="item-text">{{ item.name }}</span>
|
|
384
|
-
</div>
|
|
385
|
-
</div>
|
|
386
|
-
</div>
|
|
387
|
-
`,
|
|
388
|
-
styles: [`
|
|
389
|
-
.list-container {
|
|
390
|
-
max-width: 400px;
|
|
391
|
-
margin: 20px auto;
|
|
392
|
-
}
|
|
393
|
-
.list-item {
|
|
394
|
-
padding: 10px;
|
|
395
|
-
margin: 5px 0;
|
|
396
|
-
background: white;
|
|
397
|
-
border: 1px solid #ddd;
|
|
398
|
-
border-radius: 4px;
|
|
399
|
-
display: flex;
|
|
400
|
-
align-items: center;
|
|
401
|
-
}
|
|
402
|
-
.item-icon {
|
|
403
|
-
margin-right: 10px;
|
|
404
|
-
}
|
|
405
|
-
`]
|
|
406
|
-
})
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
### Kanban Board
|
|
410
|
-
|
|
411
|
-
```typescript
|
|
412
|
-
@Component({
|
|
413
|
-
template: `
|
|
414
|
-
<div class="kanban-board">
|
|
415
|
-
<div *ngFor="let column of columns"
|
|
416
|
-
class="kanban-column"
|
|
417
|
-
LibsUiComponentsDragContainerDirective
|
|
418
|
-
[items]="column.items"
|
|
419
|
-
[groupName]="column.id">
|
|
420
|
-
<h3>{{ column.title }}</h3>
|
|
421
|
-
<div *ngFor="let item of column.items"
|
|
422
|
-
class="kanban-item"
|
|
423
|
-
LibsUiDragItemDirective
|
|
424
|
-
[item]="item">
|
|
425
|
-
{{ item.title }}
|
|
426
|
-
</div>
|
|
427
|
-
</div>
|
|
428
|
-
</div>
|
|
429
|
-
`,
|
|
430
|
-
styles: [`
|
|
431
|
-
.kanban-board {
|
|
432
|
-
display: flex;
|
|
433
|
-
gap: 20px;
|
|
434
|
-
padding: 20px;
|
|
435
|
-
}
|
|
436
|
-
.kanban-column {
|
|
437
|
-
flex: 1;
|
|
438
|
-
background: #f5f5f5;
|
|
439
|
-
padding: 15px;
|
|
440
|
-
border-radius: 8px;
|
|
441
|
-
}
|
|
442
|
-
.kanban-item {
|
|
443
|
-
background: white;
|
|
444
|
-
padding: 10px;
|
|
445
|
-
margin: 5px 0;
|
|
446
|
-
border-radius: 4px;
|
|
447
|
-
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
448
|
-
}
|
|
449
|
-
`]
|
|
450
|
-
})
|
|
451
|
-
```
|
|
470
|
+
- Local: http://localhost:4500/drag-drop
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@libs-ui/components-drag-drop",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.356-0",
|
|
4
4
|
"peerDependencies": {
|
|
5
5
|
"@angular/core": ">=18.0.0",
|
|
6
|
-
"@libs-ui/utils": "0.2.
|
|
6
|
+
"@libs-ui/utils": "0.2.356-0",
|
|
7
7
|
"rxjs": "~7.8.0",
|
|
8
|
-
"@libs-ui/interfaces-types": "0.2.
|
|
8
|
+
"@libs-ui/interfaces-types": "0.2.356-0",
|
|
9
9
|
"@iharbeck/ngx-virtual-scroller": "15.2.0"
|
|
10
10
|
},
|
|
11
11
|
"sideEffects": false,
|