* Container-level drag event delegation. * Handles dragenter/dragover/drop/dragleave on the itemsContainer so it * works with both normal and virtual-scrolling rendering. * * IMPORTANT: Both dragenter and dragover must call e.preventDefault() to * tell the browser this container accepts dr
()
| 1235 | * the cursor is between cards or over the dragged card itself. |
| 1236 | */ |
| 1237 | private setupContainerDragHandlers(): void { |
| 1238 | if (!this.itemsContainer) return; |
| 1239 | |
| 1240 | // dragenter: required by the HTML5 DnD spec alongside dragover to |
| 1241 | // indicate this container is a valid drop zone. |
| 1242 | this.itemsContainer.addEventListener("dragenter", (e: DragEvent) => { |
| 1243 | if (!this.draggedTaskPath) return; |
| 1244 | e.preventDefault(); |
| 1245 | if (e.dataTransfer) e.dataTransfer.dropEffect = "move"; |
| 1246 | }); |
| 1247 | |
| 1248 | this.itemsContainer.addEventListener("dragover", (e: DragEvent) => { |
| 1249 | if (!this.draggedTaskPath) return; |
| 1250 | |
| 1251 | // Always accept – must be unconditional so the browser keeps |
| 1252 | // the drop zone active even when the cursor is between cards. |
| 1253 | e.preventDefault(); |
| 1254 | if (e.dataTransfer) e.dataTransfer.dropEffect = "move"; |
| 1255 | |
| 1256 | // Throttle visual updates via rAF |
| 1257 | this.pendingDragClientY = e.clientY; |
| 1258 | if (!this.dragOverRafId) { |
| 1259 | this.dragOverRafId = window.requestAnimationFrame(() => { |
| 1260 | this.dragOverRafId = 0; |
| 1261 | |
| 1262 | const clientY = this.pendingDragClientY; |
| 1263 | this.pendingDragClientY = null; |
| 1264 | if (clientY === null) return; |
| 1265 | |
| 1266 | this.updateResolvedInsertionSlot(clientY); |
| 1267 | }); |
| 1268 | } |
| 1269 | }); |
| 1270 | |
| 1271 | this.itemsContainer.addEventListener("dragleave", (e: DragEvent) => { |
| 1272 | // Only clear if leaving the container entirely (not moving between children) |
| 1273 | const related = e.relatedTarget as HTMLElement | null; |
| 1274 | if (!related || !this.itemsContainer?.contains(related)) { |
| 1275 | this.clearDropIndicators(); |
| 1276 | } |
| 1277 | }); |
| 1278 | |
| 1279 | this.itemsContainer.addEventListener("drop", (e: DragEvent) => { |
| 1280 | void (async () => { |
| 1281 | e.preventDefault(); |
| 1282 | if (!this.draggedTaskPath) return; |
| 1283 | |
| 1284 | if (!this.flushPendingInsertionSlot(e.clientY) && this.currentInsertionIndex < 0) |
| 1285 | return; |
| 1286 | |
| 1287 | const draggedPath = this.draggedTaskPath; |
| 1288 | const sourceGroupKey = this.dragGroupKey; |
| 1289 | const targetGroupKey = this.currentInsertionGroupKey; |
| 1290 | const targetVisiblePaths = this.getVisibleSortScopePathsForDrag(targetGroupKey); |
| 1291 | const insertionSegmentIndex = this.currentInsertionSegmentIndex; |
| 1292 | const insertionIndex = this.currentInsertionIndex; |
| 1293 | const dropTarget = |
| 1294 | insertionSegmentIndex >= 0 && insertionIndex >= 0 |
no test coverage detected