@mk-drag-and-drop/dom 0.1.0 → 0.2.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.
Files changed (30) hide show
  1. package/README.md +56 -32
  2. package/dist/controller/controller-internals.d.ts +4 -4
  3. package/dist/controller/create-drag-controller.d.ts +8 -8
  4. package/dist/controller/create-drag-controller.js +23 -53
  5. package/dist/draggable/create-draggable-binding.js +3 -3
  6. package/dist/droppable/create-drop-container-binding.js +4 -4
  7. package/dist/droppable/create-drop-container.d.ts +1 -1
  8. package/dist/droppable/create-drop-container.js +3 -3
  9. package/dist/droppable/create-droppable-binding.js +4 -4
  10. package/dist/droppable/create-droppable.d.ts +1 -1
  11. package/dist/droppable/create-droppable.js +3 -3
  12. package/dist/index.d.ts +1 -0
  13. package/dist/input/pointer-activation.d.ts +4 -2
  14. package/dist/input/pointer-activation.js +28 -16
  15. package/dist/integration/index.d.ts +2 -2
  16. package/dist/integration/index.js +1 -1
  17. package/dist/runtime/drag-runtime-scope.d.ts +21 -0
  18. package/dist/runtime/{drag-runtime-handle.js → drag-runtime-scope.js} +8 -9
  19. package/dist/runtime/drag-runtime.d.ts +21 -17
  20. package/dist/runtime/drag-runtime.js +274 -155
  21. package/dist/runtime/lifecycle.d.ts +5 -0
  22. package/dist/runtime/types.d.ts +3 -2
  23. package/dist/sortable/create-sortable-binding.js +4 -4
  24. package/dist/sortable/create-sortable.d.ts +1 -1
  25. package/dist/sortable/create-sortable.js +3 -3
  26. package/dist/sortable/sortable-preview.js +20 -17
  27. package/dist/sortable/sortable-registry.d.ts +15 -6
  28. package/dist/sortable/sortable-registry.js +65 -47
  29. package/package.json +1 -1
  30. package/dist/runtime/drag-runtime-handle.d.ts +0 -22
@@ -16,13 +16,12 @@ export class DragRuntime {
16
16
  session = { status: "idle" };
17
17
  modifiers = [];
18
18
  activeDragModifiers = [];
19
- cleanupWindowListeners = null;
20
- cleanupTextSelectionSuppression = null;
19
+ releaseActiveInputListeners = null;
20
+ restoreTextSelectionSuppression = null;
21
21
  queuedPointerPosition = null;
22
22
  pointerFrameId = null;
23
23
  subscriptions = new Set();
24
- disposeCallbacks = new Set();
25
- bindingCleanupRecords = new Set();
24
+ staleDomBindingRecords = new Set();
26
25
  lifecycleCallbacks = {};
27
26
  lifecycleHelpers = {
28
27
  getDropTargetRect: (dropTargetId) => this.getLifecycleDropTargetRect(dropTargetId),
@@ -37,7 +36,7 @@ export class DragRuntime {
37
36
  updateOverlayHost;
38
37
  targetingAlgorithm;
39
38
  hasDragOverlay;
40
- keepOverlayOnDrop;
39
+ overlayRelease;
41
40
  targetingConstraint;
42
41
  pointerActivation = new PointerActivationController({
43
42
  getConfiguration: () => this.pointerConfiguration,
@@ -96,12 +95,12 @@ export class DragRuntime {
96
95
  this.updateOverlayHost = options.updateOverlayHost ?? noopUpdateOverlayHost;
97
96
  this.targetingAlgorithm = options.targetingAlgorithm ?? pointerToCenter;
98
97
  this.hasDragOverlay = options.hasDragOverlay ?? false;
99
- this.keepOverlayOnDrop = options.keepOverlayOnDrop ?? false;
98
+ this.overlayRelease = options.overlayRelease ?? "auto";
100
99
  this.targetingConstraint = options.targetingConstraint;
101
100
  }
102
101
  configure(input) {
103
102
  this.hasDragOverlay = input.hasDragOverlay;
104
- this.keepOverlayOnDrop = input.keepOverlayOnDrop;
103
+ this.overlayRelease = input.overlayRelease;
105
104
  this.targetingAlgorithm = input.targetingAlgorithm;
106
105
  this.targetingConstraint = input.targetingConstraint;
107
106
  this.lifecycleCallbacks = input.lifecycleCallbacks;
@@ -110,7 +109,7 @@ export class DragRuntime {
110
109
  this.pointerConfiguration = normalizePointerConfiguration(input.pointerConfiguration);
111
110
  }
112
111
  requestDragStart(input) {
113
- this.pruneDisconnectedBindingCleanups();
112
+ this.pruneDisconnectedDomBindings();
114
113
  this.pointerActivation.request(input);
115
114
  }
116
115
  isKeyboardDragEnabled() {
@@ -120,8 +119,8 @@ export class DragRuntime {
120
119
  return this.keyboardDrag.handleSourceKeyDown(input);
121
120
  }
122
121
  requestKeyboardDragStart(input) {
123
- this.pruneDisconnectedBindingCleanups();
124
- this.pointerActivation.cancel();
122
+ this.pruneDisconnectedDomBindings();
123
+ this.cancelPendingActivation();
125
124
  if (this.isDragging ||
126
125
  !this.keyboardDrag.isEnabled() ||
127
126
  !input.element.isConnected) {
@@ -162,7 +161,7 @@ export class DragRuntime {
162
161
  else {
163
162
  pointerPosition.x += distance;
164
163
  }
165
- this.updatePointerNow(pointerPosition);
164
+ this.updatePointerNowOrRelease(pointerPosition);
166
165
  }
167
166
  updatePointer(rawPointerPosition) {
168
167
  if (this.session.status !== "dragging") {
@@ -177,7 +176,7 @@ export class DragRuntime {
177
176
  const nextPointerPosition = this.queuedPointerPosition;
178
177
  this.queuedPointerPosition = null;
179
178
  if (nextPointerPosition) {
180
- this.updatePointerNow(nextPointerPosition);
179
+ this.updatePointerNowOrRelease(nextPointerPosition);
181
180
  }
182
181
  });
183
182
  }
@@ -230,20 +229,38 @@ export class DragRuntime {
230
229
  placementPosition: sortablePlacementPosition,
231
230
  });
232
231
  }
232
+ updatePointerNowOrRelease(rawPointerPosition) {
233
+ try {
234
+ this.updatePointerNow(rawPointerPosition);
235
+ }
236
+ catch (error) {
237
+ this.releaseActiveDragResourcesAndRethrow(error);
238
+ }
239
+ }
240
+ finishDragAfterQueuedPointerUpdate(input) {
241
+ try {
242
+ this.flushQueuedPointerUpdate();
243
+ }
244
+ catch (error) {
245
+ this.releaseActiveDragResourcesAndRethrow(error);
246
+ }
247
+ this.finishDrag(input);
248
+ }
233
249
  endDrag() {
234
- this.flushQueuedPointerUpdate();
235
- this.finishDrag({
250
+ this.finishDragAfterQueuedPointerUpdate({
236
251
  reason: "drop",
237
- keepReleasedOverlay: this.keepOverlayOnDrop,
252
+ overlayRelease: this.overlayRelease,
238
253
  });
239
254
  }
240
255
  cancelDrag() {
241
- this.flushQueuedPointerUpdate();
242
- this.finishDrag({
256
+ this.finishDragAfterQueuedPointerUpdate({
243
257
  reason: "cancel",
244
- keepReleasedOverlay: false,
258
+ overlayRelease: this.overlayRelease,
245
259
  });
246
260
  }
261
+ cancelPendingActivation() {
262
+ this.pointerActivation.cancelPendingActivation();
263
+ }
247
264
  registerDropTarget(dropTargetId, element, group, options) {
248
265
  const removedTargets = this.dropTargetRegistry.register(dropTargetId, element, group, options);
249
266
  this.clearActiveDropTargetIdIfRemoved(removedTargets);
@@ -263,12 +280,53 @@ export class DragRuntime {
263
280
  const removedTargets = this.dropTargetRegistry.unregister(containerId, element);
264
281
  this.clearActiveDropTargetIdIfRemoved(removedTargets);
265
282
  }
266
- cleanup() {
267
- this.pointerActivation.cancel();
283
+ releaseActiveDragResources() {
284
+ const activeSession = this.getDraggingSession();
285
+ this.cancelPendingActivation();
286
+ let firstReleaseError;
287
+ let hasReleaseError = false;
288
+ if (activeSession) {
289
+ try {
290
+ this.notifyActiveDragReset({
291
+ draggableId: activeSession.draggableId,
292
+ source: activeSession.source,
293
+ });
294
+ }
295
+ catch (error) {
296
+ firstReleaseError = error;
297
+ hasReleaseError = true;
298
+ }
299
+ }
268
300
  this.resetActiveDragState();
269
- this.cleanupActiveDragResources();
270
- this.updateOverlayHost({ type: "unmount" });
271
- this.pruneDisconnectedBindingCleanups();
301
+ try {
302
+ this.releaseActiveInputResources();
303
+ }
304
+ catch (error) {
305
+ firstReleaseError = error;
306
+ hasReleaseError = true;
307
+ }
308
+ try {
309
+ this.clearOverlayHost();
310
+ }
311
+ catch (error) {
312
+ if (!hasReleaseError) {
313
+ firstReleaseError = error;
314
+ hasReleaseError = true;
315
+ }
316
+ }
317
+ if (hasReleaseError) {
318
+ throw firstReleaseError;
319
+ }
320
+ }
321
+ releaseActiveDragResourcesAndRethrow(error) {
322
+ try {
323
+ this.releaseActiveDragResources();
324
+ }
325
+ catch {
326
+ // Preserve the original failure while still making a best effort to
327
+ // release resources that were acquired before it was thrown.
328
+ }
329
+ throw error;
272
330
  }
273
331
  getDropTargetRect(dropTargetId) {
274
332
  return this.dropTargetRegistry.getViewportRect(dropTargetId);
@@ -302,16 +360,16 @@ export class DragRuntime {
302
360
  ...session,
303
361
  overlayMeasurement,
304
362
  });
305
- this.updatePointerNow(session.rawPointerPosition);
363
+ this.updatePointerNowOrRelease(session.rawPointerPosition);
306
364
  }
307
365
  remeasureDropTargets(input) {
308
366
  const pruneGroup = input !== undefined && typeof input !== "string" && !Array.isArray(input)
309
367
  ? input.group
310
368
  : undefined;
311
- this.pruneDisconnectedBindingCleanups();
369
+ this.pruneDisconnectedDomBindings();
312
370
  this.removeDisconnectedDropTargets(pruneGroup);
313
371
  this.dropTargetRegistry.remeasure(input);
314
- this.pruneDisconnectedBindingCleanups();
372
+ this.pruneDisconnectedDomBindings();
315
373
  }
316
374
  subscribe(subscription) {
317
375
  this.subscriptions.add(subscription);
@@ -319,121 +377,110 @@ export class DragRuntime {
319
377
  this.subscriptions.delete(subscription);
320
378
  };
321
379
  }
322
- onDispose(callback) {
323
- this.disposeCallbacks.add(callback);
324
- return () => {
325
- this.disposeCallbacks.delete(callback);
326
- };
327
- }
328
- registerBindingCleanup(record) {
380
+ registerStaleDomBinding(record) {
329
381
  const storedRecord = {
330
382
  record,
331
383
  hasBeenConnected: record.isConnected(),
332
384
  };
333
- // Keep registration O(1). Full cleanup pruning runs on lifecycle/manual
334
- // cleanup paths so bulk renders do not rescan every prior binding.
335
- this.bindingCleanupRecords.add(storedRecord);
385
+ // Keep registration O(1). Full stale DOM pruning runs on drag lifecycle and
386
+ // explicit remeasurement paths so bulk renders do not rescan prior bindings.
387
+ this.staleDomBindingRecords.add(storedRecord);
336
388
  return () => {
337
- if (!this.bindingCleanupRecords.delete(storedRecord)) {
389
+ if (!this.staleDomBindingRecords.delete(storedRecord)) {
338
390
  return;
339
391
  }
340
- record.cleanup();
392
+ record.release();
341
393
  };
342
394
  }
343
- pruneDisconnectedBindingCleanups() {
344
- this.pruneDisconnectedBindingCleanupRecords();
395
+ pruneDisconnectedDomBindings() {
396
+ this.pruneDisconnectedDomBindingRecords();
345
397
  }
346
- getBindingCleanupRecordCount() {
347
- return this.bindingCleanupRecords.size;
348
- }
349
- dispose() {
350
- this.cleanup();
351
- this.disposeBindingCleanupRecords();
352
- for (const disposeCallback of Array.from(this.disposeCallbacks)) {
353
- disposeCallback();
354
- }
355
- this.disposeCallbacks.clear();
356
- this.subscriptions.clear();
357
- this.dropTargetRegistry.clear();
358
- this.lifecycleCallbacks = {};
359
- this.modifiers = [];
360
- this.updateOverlayHost = noopUpdateOverlayHost;
398
+ getStaleDomBindingRecordCount() {
399
+ return this.staleDomBindingRecords.size;
361
400
  }
362
401
  startDragNow(input) {
363
- if (this.targetingAlgorithm.mode === "rect" && !this.hasDragOverlay) {
364
- throw new Error("The selected targeting algorithm requires a drag overlay. Provide dragOverlay or use a pointer-based targeting algorithm.");
365
- }
366
- this.pruneDisconnectedBindingCleanups();
367
- const rawPointerPosition = input.pointerPosition;
368
- const activeDragModifiers = createActiveDragModifiers({
369
- modifiers: this.modifiers,
370
- setupInput: {
402
+ this.cancelPendingActivation();
403
+ try {
404
+ if (this.targetingAlgorithm.mode === "rect" && !this.hasDragOverlay) {
405
+ throw new Error("The selected targeting algorithm requires a drag overlay. Provide dragOverlay or use a pointer-based targeting algorithm.");
406
+ }
407
+ this.pruneDisconnectedDomBindings();
408
+ const rawPointerPosition = input.pointerPosition;
409
+ const activeDragModifiers = createActiveDragModifiers({
410
+ modifiers: this.modifiers,
411
+ setupInput: {
412
+ draggableId: input.draggableId,
413
+ group: input.group,
414
+ sourceRect: input.sourceRect,
415
+ initialPointerPosition: rawPointerPosition,
416
+ },
417
+ });
418
+ this.activeDragModifiers = activeDragModifiers;
419
+ const effectivePointerPosition = applyDragModifiers({
420
+ activeModifiers: activeDragModifiers,
371
421
  draggableId: input.draggableId,
372
422
  group: input.group,
373
423
  sourceRect: input.sourceRect,
374
424
  initialPointerPosition: rawPointerPosition,
375
- },
376
- });
377
- this.activeDragModifiers = activeDragModifiers;
378
- const effectivePointerPosition = applyDragModifiers({
379
- activeModifiers: activeDragModifiers,
380
- draggableId: input.draggableId,
381
- group: input.group,
382
- sourceRect: input.sourceRect,
383
- initialPointerPosition: rawPointerPosition,
384
- rawPointerPosition,
385
- });
386
- const sourceContainerId = this.dropTargetRegistry.getDropTargetRegistration(input.draggableId, input.group)?.containerId ?? null;
387
- const sourceSortablePlacement = this.dropTargetRegistry.getSortableItemPlacement(input.draggableId, input.group);
388
- const nextSession = {
389
- status: "dragging",
390
- source: input.inputType,
391
- draggableId: input.draggableId,
392
- group: input.group,
393
- sourceRect: input.sourceRect,
394
- sourceContainerId,
395
- sourceSortablePlacement,
396
- overlayMeasurement: null,
397
- startPointerPosition: rawPointerPosition,
398
- rawPointerPosition,
399
- pointerPosition: effectivePointerPosition,
400
- activeDropTargetId: null,
401
- };
402
- this.setSession(nextSession);
403
- this.remeasureDropTargets({ group: input.group });
404
- this.updateOverlayHost({
405
- type: "mount",
406
- state: {
407
- dragState: this.createDragState(nextSession),
408
- phase: "dragging",
409
- },
410
- });
411
- this.suppressTextSelection();
412
- if (input.inputType === "pointer") {
413
- this.bindPointerWindowListeners(input.pointerId);
425
+ rawPointerPosition,
426
+ });
427
+ const sourceContainerId = this.dropTargetRegistry.getDropTargetRegistration(input.draggableId, input.group)?.containerId ?? null;
428
+ const sourceSortablePlacement = this.dropTargetRegistry.getSortableItemPlacement(input.draggableId, input.group);
429
+ const nextSession = {
430
+ status: "dragging",
431
+ source: input.inputType,
432
+ draggableId: input.draggableId,
433
+ group: input.group,
434
+ sourceRect: input.sourceRect,
435
+ sourceContainerId,
436
+ sourceSortablePlacement,
437
+ overlayMeasurement: null,
438
+ startPointerPosition: rawPointerPosition,
439
+ rawPointerPosition,
440
+ pointerPosition: effectivePointerPosition,
441
+ activeDropTargetId: null,
442
+ };
443
+ this.setSession(nextSession);
444
+ this.remeasureDropTargets({ group: input.group });
445
+ const dragStartEvent = {
446
+ draggableId: input.draggableId,
447
+ source: input.inputType,
448
+ pointerPosition: effectivePointerPosition,
449
+ sourceRect: input.sourceRect,
450
+ };
451
+ const startOverlayRect = this.hasDragOverlay
452
+ ? this.getCurrentDragRectAt(effectivePointerPosition)
453
+ : null;
454
+ const dragStartSubscriptionEvent = {
455
+ ...dragStartEvent,
456
+ placementPosition: this.getSortablePlacementPosition({
457
+ pointerPosition: effectivePointerPosition,
458
+ overlayRect: startOverlayRect,
459
+ }),
460
+ };
461
+ this.notifyDragStartSubscriptions(dragStartSubscriptionEvent);
462
+ this.updateOverlayHost({
463
+ type: "mount",
464
+ state: {
465
+ dragState: this.createDragState(nextSession),
466
+ phase: "dragging",
467
+ },
468
+ });
469
+ this.suppressTextSelection();
470
+ if (input.inputType === "pointer") {
471
+ this.bindPointerWindowListeners(input.pointerId);
472
+ }
473
+ else {
474
+ this.bindKeyboardWindowListeners();
475
+ }
476
+ this.notifyDragStartLifecycle(dragStartEvent);
414
477
  }
415
- else {
416
- this.bindKeyboardWindowListeners();
478
+ catch (error) {
479
+ this.releaseActiveDragResourcesAndRethrow(error);
417
480
  }
418
- const dragStartEvent = {
419
- draggableId: input.draggableId,
420
- source: input.inputType,
421
- pointerPosition: effectivePointerPosition,
422
- sourceRect: input.sourceRect,
423
- };
424
- const startOverlayRect = this.hasDragOverlay
425
- ? this.getCurrentDragRectAt(effectivePointerPosition)
426
- : null;
427
- this.notifyDragStart(dragStartEvent, {
428
- ...dragStartEvent,
429
- placementPosition: this.getSortablePlacementPosition({
430
- pointerPosition: effectivePointerPosition,
431
- overlayRect: startOverlayRect,
432
- }),
433
- });
434
481
  }
435
482
  finishDrag(input) {
436
- this.pointerActivation.cancel();
483
+ this.cancelPendingActivation();
437
484
  const session = this.getDraggingSession();
438
485
  const releasedDragState = session
439
486
  ? this.createDragState(session)
@@ -462,7 +509,15 @@ export class DragRuntime {
462
509
  })
463
510
  : null;
464
511
  this.resetActiveDragState();
465
- this.cleanupActiveDragResources();
512
+ let finishError;
513
+ let hasFinishError = false;
514
+ try {
515
+ this.releaseActiveInputResources();
516
+ }
517
+ catch (error) {
518
+ finishError = error;
519
+ hasFinishError = true;
520
+ }
466
521
  if (session && result) {
467
522
  this.lifecycleDropTargetRectSnapshot = lifecycleDropTargetRectSnapshot;
468
523
  try {
@@ -481,25 +536,60 @@ export class DragRuntime {
481
536
  });
482
537
  }
483
538
  }
539
+ catch (error) {
540
+ finishError = error;
541
+ hasFinishError = true;
542
+ }
484
543
  finally {
485
544
  this.lifecycleDropTargetRectSnapshot = null;
486
545
  }
487
546
  }
488
- if (input.keepReleasedOverlay && releasedDragState && this.hasDragOverlay) {
489
- this.updateOverlayHost({
490
- type: "release",
491
- state: {
492
- dragState: releasedDragState,
493
- phase: "released",
494
- },
547
+ try {
548
+ this.releaseOverlayAfterDrag({
549
+ overlayRelease: input.overlayRelease,
550
+ releasedDragState,
495
551
  });
496
552
  }
497
- else {
498
- this.updateOverlayHost({ type: "unmount" });
553
+ catch (error) {
554
+ if (!hasFinishError) {
555
+ finishError = error;
556
+ hasFinishError = true;
557
+ }
558
+ }
559
+ if (hasFinishError) {
560
+ throw finishError;
561
+ }
562
+ }
563
+ releaseOverlayAfterDrag(input) {
564
+ if (input.overlayRelease === "manual" &&
565
+ input.releasedDragState &&
566
+ this.hasDragOverlay) {
567
+ try {
568
+ this.updateOverlayHost({
569
+ type: "release",
570
+ state: {
571
+ dragState: input.releasedDragState,
572
+ phase: "released",
573
+ },
574
+ });
575
+ }
576
+ catch (error) {
577
+ try {
578
+ this.clearOverlayHost();
579
+ }
580
+ catch {
581
+ // Preserve the overlay release error while still making a best effort
582
+ // to clear the host state.
583
+ }
584
+ throw error;
585
+ }
586
+ return;
499
587
  }
588
+ this.clearOverlayHost();
500
589
  }
501
590
  bindPointerWindowListeners(pointerId) {
502
- this.cleanupWindowListeners?.();
591
+ this.releaseActiveInputListeners?.();
592
+ let listenersReleased = false;
503
593
  const handlePointerMove = (event) => {
504
594
  if (event.pointerId !== pointerId) {
505
595
  return;
@@ -524,25 +614,29 @@ export class DragRuntime {
524
614
  window.addEventListener("pointermove", handlePointerMove);
525
615
  window.addEventListener("pointerup", handlePointerUp);
526
616
  window.addEventListener("pointercancel", handlePointerCancel);
527
- this.cleanupWindowListeners = () => {
617
+ this.releaseActiveInputListeners = () => {
618
+ if (listenersReleased) {
619
+ return;
620
+ }
621
+ listenersReleased = true;
528
622
  window.removeEventListener("pointermove", handlePointerMove);
529
623
  window.removeEventListener("pointerup", handlePointerUp);
530
624
  window.removeEventListener("pointercancel", handlePointerCancel);
531
625
  };
532
626
  }
533
627
  bindKeyboardWindowListeners() {
534
- this.cleanupWindowListeners?.();
535
- this.cleanupWindowListeners = this.keyboardDrag.bindWindowListeners();
628
+ this.releaseActiveInputListeners?.();
629
+ this.releaseActiveInputListeners = this.keyboardDrag.bindWindowListeners();
536
630
  }
537
631
  suppressTextSelection() {
538
- this.cleanupTextSelectionSuppression?.();
632
+ this.restoreTextSelectionSuppression?.();
539
633
  const root = document.documentElement;
540
634
  const body = document.body;
541
635
  const previousRootUserSelect = root.style.userSelect;
542
636
  const previousBodyUserSelect = body.style.userSelect;
543
637
  root.style.userSelect = "none";
544
638
  body.style.userSelect = "none";
545
- this.cleanupTextSelectionSuppression = () => {
639
+ this.restoreTextSelectionSuppression = () => {
546
640
  root.style.userSelect = previousRootUserSelect;
547
641
  body.style.userSelect = previousBodyUserSelect;
548
642
  };
@@ -552,11 +646,35 @@ export class DragRuntime {
552
646
  this.setSession({ status: "idle" });
553
647
  this.activeDragModifiers = [];
554
648
  }
555
- cleanupActiveDragResources() {
556
- this.cleanupWindowListeners?.();
557
- this.cleanupWindowListeners = null;
558
- this.cleanupTextSelectionSuppression?.();
559
- this.cleanupTextSelectionSuppression = null;
649
+ releaseActiveInputResources() {
650
+ const releaseActiveInputListeners = this.releaseActiveInputListeners;
651
+ this.releaseActiveInputListeners = null;
652
+ const restoreTextSelectionSuppression = this.restoreTextSelectionSuppression;
653
+ this.restoreTextSelectionSuppression = null;
654
+ let releaseError;
655
+ let hasReleaseError = false;
656
+ try {
657
+ releaseActiveInputListeners?.();
658
+ }
659
+ catch (error) {
660
+ releaseError = error;
661
+ hasReleaseError = true;
662
+ }
663
+ try {
664
+ restoreTextSelectionSuppression?.();
665
+ }
666
+ catch (error) {
667
+ if (!hasReleaseError) {
668
+ releaseError = error;
669
+ hasReleaseError = true;
670
+ }
671
+ }
672
+ if (hasReleaseError) {
673
+ throw releaseError;
674
+ }
675
+ }
676
+ clearOverlayHost() {
677
+ this.updateOverlayHost({ type: "unmount" });
560
678
  }
561
679
  flushQueuedPointerUpdate() {
562
680
  if (this.pointerFrameId !== null) {
@@ -697,8 +815,8 @@ export class DragRuntime {
697
815
  const removedTargets = this.dropTargetRegistry.pruneDisconnected(group);
698
816
  this.clearActiveDropTargetIdIfRemoved(removedTargets);
699
817
  }
700
- pruneDisconnectedBindingCleanupRecords(skipRecord) {
701
- for (const storedRecord of Array.from(this.bindingCleanupRecords)) {
818
+ pruneDisconnectedDomBindingRecords(skipRecord) {
819
+ for (const storedRecord of Array.from(this.staleDomBindingRecords)) {
702
820
  if (storedRecord === skipRecord) {
703
821
  continue;
704
822
  }
@@ -709,14 +827,8 @@ export class DragRuntime {
709
827
  if (!storedRecord.hasBeenConnected) {
710
828
  continue;
711
829
  }
712
- this.bindingCleanupRecords.delete(storedRecord);
713
- storedRecord.record.cleanup();
714
- }
715
- }
716
- disposeBindingCleanupRecords() {
717
- for (const storedRecord of Array.from(this.bindingCleanupRecords)) {
718
- this.bindingCleanupRecords.delete(storedRecord);
719
- storedRecord.record.cleanup();
830
+ this.staleDomBindingRecords.delete(storedRecord);
831
+ storedRecord.record.release();
720
832
  }
721
833
  }
722
834
  clearActiveDropTargetIdIfRemoved(removedTargets) {
@@ -734,17 +846,19 @@ export class DragRuntime {
734
846
  activeDropTargetId: null,
735
847
  });
736
848
  }
737
- notifyDragStart(event, subscriptionEvent) {
738
- this.lifecycleCallbacks.onDragStart?.(event, this.lifecycleHelpers);
849
+ notifyDragStartSubscriptions(subscriptionEvent) {
739
850
  for (const subscription of Array.from(this.subscriptions)) {
740
851
  subscription.onDragStart?.(subscriptionEvent);
741
852
  }
742
853
  }
854
+ notifyDragStartLifecycle(event) {
855
+ this.lifecycleCallbacks.onDragStart?.(event, this.lifecycleHelpers);
856
+ }
743
857
  notifyDragUpdate(event, subscriptionEvent) {
744
- this.lifecycleCallbacks.onDragUpdate?.(event, this.lifecycleHelpers);
745
858
  for (const subscription of Array.from(this.subscriptions)) {
746
859
  subscription.onDragUpdate?.(subscriptionEvent);
747
860
  }
861
+ this.lifecycleCallbacks.onDragUpdate?.(event, this.lifecycleHelpers);
748
862
  }
749
863
  notifyDragEnd(event) {
750
864
  for (const subscription of Array.from(this.subscriptions)) {
@@ -758,6 +872,11 @@ export class DragRuntime {
758
872
  }
759
873
  this.lifecycleCallbacks.onDrop?.(event, this.lifecycleHelpers);
760
874
  }
875
+ notifyActiveDragReset(event) {
876
+ for (const subscription of Array.from(this.subscriptions)) {
877
+ subscription.onActiveDragReset?.(event);
878
+ }
879
+ }
761
880
  }
762
881
  function areOverlayMeasurementsEqual(previous, next) {
763
882
  if (previous === next) {
@@ -27,6 +27,10 @@ export type DropEvent = {
27
27
  dropTargetId: string;
28
28
  sortablePlacement?: SortableDropPlacement;
29
29
  };
30
+ export type ActiveDragResetEvent = {
31
+ draggableId: string;
32
+ source: DragSource;
33
+ };
30
34
  export type DragLifecycleHelpers = {
31
35
  getDropTargetRect: (dropTargetId: string) => DragRect | null;
32
36
  };
@@ -41,4 +45,5 @@ export type DragRuntimeSubscription = {
41
45
  onDragUpdate?: (event: DragUpdateEvent) => void;
42
46
  onDragEnd?: (event: DragEndEvent) => void;
43
47
  onDrop?: (event: DropEvent) => void;
48
+ onActiveDragReset?: (event: ActiveDragResetEvent) => void;
44
49
  };
@@ -15,6 +15,7 @@ export type DragState = {
15
15
  pointerPosition: Point;
16
16
  };
17
17
  export type DragOverlayPhase = "dragging" | "released";
18
+ export type OverlayReleaseMode = "auto" | "manual";
18
19
  export type DragOverlayRenderState = {
19
20
  dragState: DragState;
20
21
  phase: DragOverlayPhase;
@@ -50,13 +51,13 @@ export type DragRuntimeOptions = {
50
51
  targetingAlgorithm?: TargetingAlgorithm;
51
52
  targetingConstraint?: TargetingConstraint;
52
53
  hasDragOverlay?: boolean;
53
- keepOverlayOnDrop?: boolean;
54
+ overlayRelease?: OverlayReleaseMode;
54
55
  };
55
56
  export type DragRuntimeConfigureInput = {
56
57
  targetingAlgorithm: TargetingAlgorithm;
57
58
  targetingConstraint: TargetingConstraint | undefined;
58
59
  hasDragOverlay: boolean;
59
- keepOverlayOnDrop: boolean;
60
+ overlayRelease: OverlayReleaseMode;
60
61
  lifecycleCallbacks: DragLifecycleCallbacks;
61
62
  keyboardConfiguration?: KeyboardConfiguration;
62
63
  modifiers?: readonly DragModifierInput[];