@mk-drag-and-drop/dom 0.1.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 (71) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +460 -0
  3. package/dist/controller/controller-internals.d.ts +5 -0
  4. package/dist/controller/controller-internals.js +11 -0
  5. package/dist/controller/create-drag-controller.d.ts +36 -0
  6. package/dist/controller/create-drag-controller.js +232 -0
  7. package/dist/draggable/create-draggable-binding.d.ts +8 -0
  8. package/dist/draggable/create-draggable-binding.js +52 -0
  9. package/dist/draggable/create-draggable.d.ts +46 -0
  10. package/dist/draggable/create-draggable.js +57 -0
  11. package/dist/droppable/create-drop-container-binding.d.ts +8 -0
  12. package/dist/droppable/create-drop-container-binding.js +28 -0
  13. package/dist/droppable/create-drop-container.d.ts +18 -0
  14. package/dist/droppable/create-drop-container.js +36 -0
  15. package/dist/droppable/create-droppable-binding.d.ts +9 -0
  16. package/dist/droppable/create-droppable-binding.js +29 -0
  17. package/dist/droppable/create-droppable.d.ts +17 -0
  18. package/dist/droppable/create-droppable.js +31 -0
  19. package/dist/geometry/measurement.d.ts +4 -0
  20. package/dist/geometry/measurement.js +33 -0
  21. package/dist/geometry/overlay.d.ts +19 -0
  22. package/dist/geometry/overlay.js +32 -0
  23. package/dist/geometry/rects.d.ts +17 -0
  24. package/dist/geometry/rects.js +34 -0
  25. package/dist/index.d.ts +16 -0
  26. package/dist/index.js +9 -0
  27. package/dist/input/config.d.ts +35 -0
  28. package/dist/input/config.js +56 -0
  29. package/dist/input/create-drag-handle.d.ts +4 -0
  30. package/dist/input/create-drag-handle.js +4 -0
  31. package/dist/input/drag-handle.d.ts +10 -0
  32. package/dist/input/drag-handle.js +74 -0
  33. package/dist/input/keyboard-drag.d.ts +28 -0
  34. package/dist/input/keyboard-drag.js +77 -0
  35. package/dist/input/pointer-activation.d.ts +31 -0
  36. package/dist/input/pointer-activation.js +97 -0
  37. package/dist/integration/index.d.ts +10 -0
  38. package/dist/integration/index.js +6 -0
  39. package/dist/modifiers/built-ins.d.ts +6 -0
  40. package/dist/modifiers/built-ins.js +62 -0
  41. package/dist/modifiers/pipeline.d.ts +18 -0
  42. package/dist/modifiers/pipeline.js +34 -0
  43. package/dist/modifiers/types.d.ts +32 -0
  44. package/dist/modifiers/types.js +1 -0
  45. package/dist/runtime/drag-runtime-handle.d.ts +22 -0
  46. package/dist/runtime/drag-runtime-handle.js +33 -0
  47. package/dist/runtime/drag-runtime.d.ts +96 -0
  48. package/dist/runtime/drag-runtime.js +798 -0
  49. package/dist/runtime/drop-target-registry.d.ts +101 -0
  50. package/dist/runtime/drop-target-registry.js +855 -0
  51. package/dist/runtime/lifecycle.d.ts +44 -0
  52. package/dist/runtime/lifecycle.js +1 -0
  53. package/dist/runtime/types.d.ts +65 -0
  54. package/dist/runtime/types.js +1 -0
  55. package/dist/sortable/create-sortable-binding.d.ts +10 -0
  56. package/dist/sortable/create-sortable-binding.js +58 -0
  57. package/dist/sortable/create-sortable.d.ts +19 -0
  58. package/dist/sortable/create-sortable.js +55 -0
  59. package/dist/sortable/sortable-options.d.ts +17 -0
  60. package/dist/sortable/sortable-options.js +20 -0
  61. package/dist/sortable/sortable-preview.d.ts +79 -0
  62. package/dist/sortable/sortable-preview.js +357 -0
  63. package/dist/sortable/sortable-registry.d.ts +113 -0
  64. package/dist/sortable/sortable-registry.js +145 -0
  65. package/dist/targeting/algorithms.d.ts +6 -0
  66. package/dist/targeting/algorithms.js +56 -0
  67. package/dist/targeting/constraints.d.ts +15 -0
  68. package/dist/targeting/constraints.js +58 -0
  69. package/dist/targeting/types.d.ts +23 -0
  70. package/dist/targeting/types.js +1 -0
  71. package/package.json +60 -0
@@ -0,0 +1,798 @@
1
+ import { measureDomElement } from "../geometry/measurement.js";
2
+ import { getOverlayMeasurement, getOverlayRect, } from "../geometry/overlay.js";
3
+ import { defaultKeyboardConfiguration, normalizeKeyboardConfiguration, normalizePointerConfiguration, } from "../input/config.js";
4
+ import { KeyboardDragController, } from "../input/keyboard-drag.js";
5
+ import { PointerActivationController } from "../input/pointer-activation.js";
6
+ import { applyDragModifiers, createActiveDragModifiers, } from "../modifiers/pipeline.js";
7
+ import { getBuiltInTargetingAlgorithmKind, pointerToCenter, } from "../targeting/algorithms.js";
8
+ import { DropTargetRegistry, } from "./drop-target-registry.js";
9
+ const noopUpdateOverlayHost = () => { };
10
+ export class DragRuntime {
11
+ isDragging = false;
12
+ draggedId = null;
13
+ draggedGroup = null;
14
+ pointerPosition = null;
15
+ activeDropTargetId = null;
16
+ session = { status: "idle" };
17
+ modifiers = [];
18
+ activeDragModifiers = [];
19
+ cleanupWindowListeners = null;
20
+ cleanupTextSelectionSuppression = null;
21
+ queuedPointerPosition = null;
22
+ pointerFrameId = null;
23
+ subscriptions = new Set();
24
+ disposeCallbacks = new Set();
25
+ bindingCleanupRecords = new Set();
26
+ lifecycleCallbacks = {};
27
+ lifecycleHelpers = {
28
+ getDropTargetRect: (dropTargetId) => this.getLifecycleDropTargetRect(dropTargetId),
29
+ };
30
+ lifecycleDropTargetRectSnapshot = null;
31
+ dropTargetRegistry = new DropTargetRegistry();
32
+ pointerConfiguration = {
33
+ activationDelay: null,
34
+ activationDistance: null,
35
+ };
36
+ keyboardConfiguration = defaultKeyboardConfiguration;
37
+ updateOverlayHost;
38
+ targetingAlgorithm;
39
+ hasDragOverlay;
40
+ keepOverlayOnDrop;
41
+ targetingConstraint;
42
+ pointerActivation = new PointerActivationController({
43
+ getConfiguration: () => this.pointerConfiguration,
44
+ isDragging: () => this.isDragging,
45
+ startImmediately: (request) => {
46
+ if (!request.element.isConnected) {
47
+ return;
48
+ }
49
+ this.startDragNow({
50
+ draggableId: request.draggableId,
51
+ group: request.group,
52
+ inputType: "pointer",
53
+ pointerId: request.pointerId,
54
+ pointerPosition: request.pointerPosition,
55
+ sourceRect: measureDomElement(request.element),
56
+ });
57
+ },
58
+ activate: (activation) => {
59
+ if (this.isDragging || !activation.element.isConnected) {
60
+ return;
61
+ }
62
+ this.startDragNow({
63
+ draggableId: activation.draggableId,
64
+ group: activation.group,
65
+ inputType: "pointer",
66
+ pointerId: activation.pointerId,
67
+ pointerPosition: activation.initialPointerPosition,
68
+ sourceRect: measureDomElement(activation.element),
69
+ });
70
+ if (activation.latestPointerPosition.x !==
71
+ activation.initialPointerPosition.x ||
72
+ activation.latestPointerPosition.y !==
73
+ activation.initialPointerPosition.y) {
74
+ this.updatePointer(activation.latestPointerPosition);
75
+ }
76
+ },
77
+ });
78
+ keyboardDrag = new KeyboardDragController({
79
+ getConfiguration: () => this.keyboardConfiguration,
80
+ isDragging: () => this.isDragging,
81
+ getActiveInput: () => this.getActiveInput(),
82
+ start: (input) => {
83
+ this.requestKeyboardDragStart(input);
84
+ },
85
+ move: (direction) => {
86
+ this.moveKeyboardDrag(direction);
87
+ },
88
+ drop: () => {
89
+ this.endDrag();
90
+ },
91
+ cancel: () => {
92
+ this.cancelDrag();
93
+ },
94
+ });
95
+ constructor(options = {}) {
96
+ this.updateOverlayHost = options.updateOverlayHost ?? noopUpdateOverlayHost;
97
+ this.targetingAlgorithm = options.targetingAlgorithm ?? pointerToCenter;
98
+ this.hasDragOverlay = options.hasDragOverlay ?? false;
99
+ this.keepOverlayOnDrop = options.keepOverlayOnDrop ?? false;
100
+ this.targetingConstraint = options.targetingConstraint;
101
+ }
102
+ configure(input) {
103
+ this.hasDragOverlay = input.hasDragOverlay;
104
+ this.keepOverlayOnDrop = input.keepOverlayOnDrop;
105
+ this.targetingAlgorithm = input.targetingAlgorithm;
106
+ this.targetingConstraint = input.targetingConstraint;
107
+ this.lifecycleCallbacks = input.lifecycleCallbacks;
108
+ this.keyboardConfiguration = normalizeKeyboardConfiguration(input.keyboardConfiguration);
109
+ this.modifiers = input.modifiers ? Array.from(input.modifiers) : [];
110
+ this.pointerConfiguration = normalizePointerConfiguration(input.pointerConfiguration);
111
+ }
112
+ requestDragStart(input) {
113
+ this.pruneDisconnectedBindingCleanups();
114
+ this.pointerActivation.request(input);
115
+ }
116
+ isKeyboardDragEnabled() {
117
+ return this.keyboardDrag.isEnabled();
118
+ }
119
+ handleSourceKeyboardKeyDown(input) {
120
+ return this.keyboardDrag.handleSourceKeyDown(input);
121
+ }
122
+ requestKeyboardDragStart(input) {
123
+ this.pruneDisconnectedBindingCleanups();
124
+ this.pointerActivation.cancel();
125
+ if (this.isDragging ||
126
+ !this.keyboardDrag.isEnabled() ||
127
+ !input.element.isConnected) {
128
+ return;
129
+ }
130
+ const sourceRect = measureDomElement(input.element);
131
+ const pointerPosition = {
132
+ x: sourceRect.left + sourceRect.width / 2,
133
+ y: sourceRect.top + sourceRect.height / 2,
134
+ };
135
+ this.startDragNow({
136
+ draggableId: input.draggableId,
137
+ group: input.group,
138
+ inputType: "keyboard",
139
+ pointerPosition,
140
+ sourceRect,
141
+ });
142
+ }
143
+ moveKeyboardDrag(direction) {
144
+ const session = this.getDraggingSession();
145
+ if (!session || session.source !== "keyboard") {
146
+ return;
147
+ }
148
+ const distance = this.keyboardConfiguration.moveDistance;
149
+ const pointerPosition = {
150
+ x: session.rawPointerPosition.x,
151
+ y: session.rawPointerPosition.y,
152
+ };
153
+ if (direction === "up") {
154
+ pointerPosition.y -= distance;
155
+ }
156
+ else if (direction === "down") {
157
+ pointerPosition.y += distance;
158
+ }
159
+ else if (direction === "left") {
160
+ pointerPosition.x -= distance;
161
+ }
162
+ else {
163
+ pointerPosition.x += distance;
164
+ }
165
+ this.updatePointerNow(pointerPosition);
166
+ }
167
+ updatePointer(rawPointerPosition) {
168
+ if (this.session.status !== "dragging") {
169
+ return;
170
+ }
171
+ this.queuedPointerPosition = rawPointerPosition;
172
+ if (this.pointerFrameId !== null) {
173
+ return;
174
+ }
175
+ this.pointerFrameId = window.requestAnimationFrame(() => {
176
+ this.pointerFrameId = null;
177
+ const nextPointerPosition = this.queuedPointerPosition;
178
+ this.queuedPointerPosition = null;
179
+ if (nextPointerPosition) {
180
+ this.updatePointerNow(nextPointerPosition);
181
+ }
182
+ });
183
+ }
184
+ updatePointerNow(rawPointerPosition) {
185
+ const session = this.getDraggingSession();
186
+ if (!session) {
187
+ return;
188
+ }
189
+ const previousDropTargetId = session.activeDropTargetId;
190
+ const pointerPosition = applyDragModifiers({
191
+ activeModifiers: this.activeDragModifiers,
192
+ draggableId: session.draggableId,
193
+ group: session.group,
194
+ sourceRect: session.sourceRect,
195
+ initialPointerPosition: session.startPointerPosition,
196
+ rawPointerPosition,
197
+ overlayMeasurement: session.overlayMeasurement,
198
+ });
199
+ const overlayRect = this.hasDragOverlay
200
+ ? this.getCurrentDragRectAt(pointerPosition)
201
+ : null;
202
+ const sortablePlacementPosition = this.getSortablePlacementPosition({
203
+ pointerPosition,
204
+ overlayRect,
205
+ });
206
+ const activeDropTargetId = this.getActiveDropTargetId({
207
+ pointerPosition,
208
+ overlayRect,
209
+ });
210
+ const nextSession = {
211
+ ...session,
212
+ rawPointerPosition,
213
+ pointerPosition,
214
+ activeDropTargetId,
215
+ };
216
+ this.setSession(nextSession);
217
+ this.updateOverlayHost({
218
+ type: "move",
219
+ dragState: this.createDragState(nextSession),
220
+ });
221
+ const updateEvent = {
222
+ draggableId: nextSession.draggableId,
223
+ source: nextSession.source,
224
+ pointerPosition,
225
+ activeDropTargetId: nextSession.activeDropTargetId,
226
+ previousDropTargetId,
227
+ };
228
+ this.notifyDragUpdate(updateEvent, {
229
+ ...updateEvent,
230
+ placementPosition: sortablePlacementPosition,
231
+ });
232
+ }
233
+ endDrag() {
234
+ this.flushQueuedPointerUpdate();
235
+ this.finishDrag({
236
+ reason: "drop",
237
+ keepReleasedOverlay: this.keepOverlayOnDrop,
238
+ });
239
+ }
240
+ cancelDrag() {
241
+ this.flushQueuedPointerUpdate();
242
+ this.finishDrag({
243
+ reason: "cancel",
244
+ keepReleasedOverlay: false,
245
+ });
246
+ }
247
+ registerDropTarget(dropTargetId, element, group, options) {
248
+ const removedTargets = this.dropTargetRegistry.register(dropTargetId, element, group, options);
249
+ this.clearActiveDropTargetIdIfRemoved(removedTargets);
250
+ }
251
+ unregisterDropTarget(dropTargetId, element) {
252
+ const removedTargets = this.dropTargetRegistry.unregister(dropTargetId, element);
253
+ this.clearActiveDropTargetIdIfRemoved(removedTargets);
254
+ }
255
+ registerDropContainer(containerId, element, group) {
256
+ const removedTargets = this.dropTargetRegistry.register(containerId, element, group, {
257
+ containerId,
258
+ container: true,
259
+ });
260
+ this.clearActiveDropTargetIdIfRemoved(removedTargets);
261
+ }
262
+ unregisterDropContainer(containerId, element) {
263
+ const removedTargets = this.dropTargetRegistry.unregister(containerId, element);
264
+ this.clearActiveDropTargetIdIfRemoved(removedTargets);
265
+ }
266
+ cleanup() {
267
+ this.pointerActivation.cancel();
268
+ this.resetActiveDragState();
269
+ this.cleanupActiveDragResources();
270
+ this.updateOverlayHost({ type: "unmount" });
271
+ this.pruneDisconnectedBindingCleanups();
272
+ }
273
+ getDropTargetRect(dropTargetId) {
274
+ return this.dropTargetRegistry.getViewportRect(dropTargetId);
275
+ }
276
+ getDropTargetRegistration(dropTargetId, group) {
277
+ return this.dropTargetRegistry.getDropTargetRegistration(dropTargetId, group);
278
+ }
279
+ getCurrentDragRect() {
280
+ const session = this.getDraggingSession();
281
+ return session
282
+ ? this.getCurrentDragRectAt(session.pointerPosition)
283
+ : null;
284
+ }
285
+ setOverlayRect(overlayRect) {
286
+ const session = this.getDraggingSession();
287
+ if (!session) {
288
+ return;
289
+ }
290
+ const overlayMeasurement = overlayRect
291
+ ? getOverlayMeasurement({
292
+ sourceRect: session.sourceRect,
293
+ initialPointerPosition: session.startPointerPosition,
294
+ pointerPosition: session.pointerPosition,
295
+ overlayRect,
296
+ })
297
+ : null;
298
+ if (areOverlayMeasurementsEqual(session.overlayMeasurement, overlayMeasurement)) {
299
+ return;
300
+ }
301
+ this.setSession({
302
+ ...session,
303
+ overlayMeasurement,
304
+ });
305
+ this.updatePointerNow(session.rawPointerPosition);
306
+ }
307
+ remeasureDropTargets(input) {
308
+ const pruneGroup = input !== undefined && typeof input !== "string" && !Array.isArray(input)
309
+ ? input.group
310
+ : undefined;
311
+ this.pruneDisconnectedBindingCleanups();
312
+ this.removeDisconnectedDropTargets(pruneGroup);
313
+ this.dropTargetRegistry.remeasure(input);
314
+ this.pruneDisconnectedBindingCleanups();
315
+ }
316
+ subscribe(subscription) {
317
+ this.subscriptions.add(subscription);
318
+ return () => {
319
+ this.subscriptions.delete(subscription);
320
+ };
321
+ }
322
+ onDispose(callback) {
323
+ this.disposeCallbacks.add(callback);
324
+ return () => {
325
+ this.disposeCallbacks.delete(callback);
326
+ };
327
+ }
328
+ registerBindingCleanup(record) {
329
+ const storedRecord = {
330
+ record,
331
+ hasBeenConnected: record.isConnected(),
332
+ };
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);
336
+ return () => {
337
+ if (!this.bindingCleanupRecords.delete(storedRecord)) {
338
+ return;
339
+ }
340
+ record.cleanup();
341
+ };
342
+ }
343
+ pruneDisconnectedBindingCleanups() {
344
+ this.pruneDisconnectedBindingCleanupRecords();
345
+ }
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;
361
+ }
362
+ 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: {
371
+ draggableId: input.draggableId,
372
+ group: input.group,
373
+ sourceRect: input.sourceRect,
374
+ 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);
414
+ }
415
+ else {
416
+ this.bindKeyboardWindowListeners();
417
+ }
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
+ }
435
+ finishDrag(input) {
436
+ this.pointerActivation.cancel();
437
+ const session = this.getDraggingSession();
438
+ const releasedDragState = session
439
+ ? this.createDragState(session)
440
+ : null;
441
+ const activeDropTargetId = input.reason === "drop" ? session?.activeDropTargetId ?? null : null;
442
+ const validDropTarget = session && activeDropTargetId
443
+ ? this.dropTargetRegistry.getDropTargetRegistration(activeDropTargetId, session.group)
444
+ : null;
445
+ const dropTargetId = validDropTarget ? activeDropTargetId : null;
446
+ const result = session
447
+ ? input.reason === "cancel"
448
+ ? "canceled"
449
+ : activeDropTargetId === null
450
+ ? "no-target"
451
+ : validDropTarget
452
+ ? "dropped"
453
+ : "invalid-target"
454
+ : null;
455
+ const sortablePlacement = session && dropTargetId
456
+ ? this.getDropEventSortablePlacement(session, dropTargetId)
457
+ : undefined;
458
+ const lifecycleDropTargetRectSnapshot = session
459
+ ? this.createLifecycleDropTargetRectSnapshot({
460
+ session,
461
+ dropTargetId,
462
+ })
463
+ : null;
464
+ this.resetActiveDragState();
465
+ this.cleanupActiveDragResources();
466
+ if (session && result) {
467
+ this.lifecycleDropTargetRectSnapshot = lifecycleDropTargetRectSnapshot;
468
+ try {
469
+ this.notifyDragEnd({
470
+ draggableId: session.draggableId,
471
+ source: session.source,
472
+ result,
473
+ dropTargetId,
474
+ });
475
+ if (result === "dropped" && dropTargetId) {
476
+ this.notifyDrop({
477
+ draggableId: session.draggableId,
478
+ source: session.source,
479
+ dropTargetId,
480
+ ...(sortablePlacement ? { sortablePlacement } : {}),
481
+ });
482
+ }
483
+ }
484
+ finally {
485
+ this.lifecycleDropTargetRectSnapshot = null;
486
+ }
487
+ }
488
+ if (input.keepReleasedOverlay && releasedDragState && this.hasDragOverlay) {
489
+ this.updateOverlayHost({
490
+ type: "release",
491
+ state: {
492
+ dragState: releasedDragState,
493
+ phase: "released",
494
+ },
495
+ });
496
+ }
497
+ else {
498
+ this.updateOverlayHost({ type: "unmount" });
499
+ }
500
+ }
501
+ bindPointerWindowListeners(pointerId) {
502
+ this.cleanupWindowListeners?.();
503
+ const handlePointerMove = (event) => {
504
+ if (event.pointerId !== pointerId) {
505
+ return;
506
+ }
507
+ this.updatePointer({
508
+ x: event.clientX,
509
+ y: event.clientY,
510
+ });
511
+ };
512
+ const handlePointerUp = (event) => {
513
+ if (event.pointerId !== pointerId) {
514
+ return;
515
+ }
516
+ this.endDrag();
517
+ };
518
+ const handlePointerCancel = (event) => {
519
+ if (event.pointerId !== pointerId) {
520
+ return;
521
+ }
522
+ this.cancelDrag();
523
+ };
524
+ window.addEventListener("pointermove", handlePointerMove);
525
+ window.addEventListener("pointerup", handlePointerUp);
526
+ window.addEventListener("pointercancel", handlePointerCancel);
527
+ this.cleanupWindowListeners = () => {
528
+ window.removeEventListener("pointermove", handlePointerMove);
529
+ window.removeEventListener("pointerup", handlePointerUp);
530
+ window.removeEventListener("pointercancel", handlePointerCancel);
531
+ };
532
+ }
533
+ bindKeyboardWindowListeners() {
534
+ this.cleanupWindowListeners?.();
535
+ this.cleanupWindowListeners = this.keyboardDrag.bindWindowListeners();
536
+ }
537
+ suppressTextSelection() {
538
+ this.cleanupTextSelectionSuppression?.();
539
+ const root = document.documentElement;
540
+ const body = document.body;
541
+ const previousRootUserSelect = root.style.userSelect;
542
+ const previousBodyUserSelect = body.style.userSelect;
543
+ root.style.userSelect = "none";
544
+ body.style.userSelect = "none";
545
+ this.cleanupTextSelectionSuppression = () => {
546
+ root.style.userSelect = previousRootUserSelect;
547
+ body.style.userSelect = previousBodyUserSelect;
548
+ };
549
+ }
550
+ resetActiveDragState() {
551
+ this.cancelQueuedPointerUpdate();
552
+ this.setSession({ status: "idle" });
553
+ this.activeDragModifiers = [];
554
+ }
555
+ cleanupActiveDragResources() {
556
+ this.cleanupWindowListeners?.();
557
+ this.cleanupWindowListeners = null;
558
+ this.cleanupTextSelectionSuppression?.();
559
+ this.cleanupTextSelectionSuppression = null;
560
+ }
561
+ flushQueuedPointerUpdate() {
562
+ if (this.pointerFrameId !== null) {
563
+ window.cancelAnimationFrame(this.pointerFrameId);
564
+ this.pointerFrameId = null;
565
+ }
566
+ const nextPointerPosition = this.queuedPointerPosition;
567
+ this.queuedPointerPosition = null;
568
+ if (nextPointerPosition) {
569
+ this.updatePointerNow(nextPointerPosition);
570
+ }
571
+ }
572
+ cancelQueuedPointerUpdate() {
573
+ if (this.pointerFrameId !== null) {
574
+ window.cancelAnimationFrame(this.pointerFrameId);
575
+ this.pointerFrameId = null;
576
+ }
577
+ this.queuedPointerPosition = null;
578
+ }
579
+ setSession(session) {
580
+ this.session = session;
581
+ this.syncPublicDragFields();
582
+ }
583
+ syncPublicDragFields() {
584
+ if (this.session.status === "idle") {
585
+ this.isDragging = false;
586
+ this.draggedId = null;
587
+ this.draggedGroup = null;
588
+ this.pointerPosition = null;
589
+ this.activeDropTargetId = null;
590
+ return;
591
+ }
592
+ this.isDragging = true;
593
+ this.draggedId = this.session.draggableId;
594
+ this.draggedGroup = this.session.group;
595
+ this.pointerPosition = this.session.pointerPosition;
596
+ this.activeDropTargetId = this.session.activeDropTargetId;
597
+ }
598
+ getActiveInput() {
599
+ return this.session.status === "dragging" ? this.session.source : null;
600
+ }
601
+ getDraggingSession() {
602
+ return this.session.status === "dragging" ? this.session : null;
603
+ }
604
+ getDropEventSortablePlacement(session, dropTargetId) {
605
+ const placement = this.dropTargetRegistry.getSortableDropPlacement({
606
+ draggableId: session.draggableId,
607
+ dropTargetId,
608
+ group: session.group,
609
+ sourceContainerId: session.sourceContainerId,
610
+ });
611
+ if (!placement ||
612
+ isSameSortablePlacement(placement, session.sourceSortablePlacement)) {
613
+ return undefined;
614
+ }
615
+ return placement;
616
+ }
617
+ createDragState(session) {
618
+ return {
619
+ draggableId: session.draggableId,
620
+ group: session.group,
621
+ sourceRect: session.sourceRect,
622
+ startPointerPosition: session.startPointerPosition,
623
+ pointerPosition: session.pointerPosition,
624
+ };
625
+ }
626
+ getLifecycleDropTargetRect(dropTargetId) {
627
+ if (this.lifecycleDropTargetRectSnapshot?.has(dropTargetId)) {
628
+ return this.lifecycleDropTargetRectSnapshot.get(dropTargetId) ?? null;
629
+ }
630
+ return this.getDropTargetRect(dropTargetId);
631
+ }
632
+ createLifecycleDropTargetRectSnapshot(input) {
633
+ const dropTargetIds = new Set([input.session.draggableId]);
634
+ if (input.dropTargetId) {
635
+ dropTargetIds.add(input.dropTargetId);
636
+ }
637
+ if (dropTargetIds.size === 0) {
638
+ return null;
639
+ }
640
+ const snapshot = new Map();
641
+ for (const dropTargetId of dropTargetIds) {
642
+ const registration = this.dropTargetRegistry.getDropTargetRegistration(dropTargetId, input.session.group);
643
+ snapshot.set(dropTargetId, registration ? measureDomElement(registration.element) : null);
644
+ }
645
+ return snapshot;
646
+ }
647
+ getActiveDropTargetId(input) {
648
+ const session = this.getDraggingSession();
649
+ if (!session) {
650
+ return null;
651
+ }
652
+ const activeTarget = this.targetingAlgorithm({
653
+ pointerPosition: input.pointerPosition,
654
+ overlayRect: input.overlayRect,
655
+ dropTargets: this.getAvailableDropTargets({
656
+ group: session.group,
657
+ draggingDraggableId: session.draggableId,
658
+ activeDropTargetId: session.activeDropTargetId,
659
+ sourceContainerId: session.sourceContainerId,
660
+ pointerPosition: input.pointerPosition,
661
+ overlayRect: input.overlayRect,
662
+ }),
663
+ });
664
+ return activeTarget?.dropTargetId ?? null;
665
+ }
666
+ getSortablePlacementPosition(input) {
667
+ if (this.targetingAlgorithm.mode === "rect" && input.overlayRect) {
668
+ return getRectCenter(input.overlayRect);
669
+ }
670
+ return input.pointerPosition;
671
+ }
672
+ getAvailableDropTargets(input) {
673
+ return this.dropTargetRegistry.getAvailableDropTargets({
674
+ group: input.group,
675
+ draggingDraggableId: input.draggingDraggableId,
676
+ activeDropTargetId: input.activeDropTargetId,
677
+ sourceContainerId: input.sourceContainerId,
678
+ pointerPosition: input.pointerPosition,
679
+ overlayRect: input.overlayRect,
680
+ targetingAlgorithmKind: getBuiltInTargetingAlgorithmKind(this.targetingAlgorithm),
681
+ targetingConstraint: this.targetingConstraint,
682
+ });
683
+ }
684
+ getCurrentDragRectAt(pointerPosition) {
685
+ const session = this.getDraggingSession();
686
+ if (!session) {
687
+ return null;
688
+ }
689
+ return getOverlayRect({
690
+ sourceRect: session.sourceRect,
691
+ initialPointerPosition: session.startPointerPosition,
692
+ pointerPosition,
693
+ overlayMeasurement: session.overlayMeasurement,
694
+ });
695
+ }
696
+ removeDisconnectedDropTargets(group) {
697
+ const removedTargets = this.dropTargetRegistry.pruneDisconnected(group);
698
+ this.clearActiveDropTargetIdIfRemoved(removedTargets);
699
+ }
700
+ pruneDisconnectedBindingCleanupRecords(skipRecord) {
701
+ for (const storedRecord of Array.from(this.bindingCleanupRecords)) {
702
+ if (storedRecord === skipRecord) {
703
+ continue;
704
+ }
705
+ if (storedRecord.record.isConnected()) {
706
+ storedRecord.hasBeenConnected = true;
707
+ continue;
708
+ }
709
+ if (!storedRecord.hasBeenConnected) {
710
+ continue;
711
+ }
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();
720
+ }
721
+ }
722
+ clearActiveDropTargetIdIfRemoved(removedTargets) {
723
+ if (this.session.status !== "dragging") {
724
+ return;
725
+ }
726
+ const session = this.session;
727
+ const removedActiveTarget = removedTargets.some((removedTarget) => removedTarget.id === session.activeDropTargetId &&
728
+ removedTarget.group === session.group);
729
+ if (!removedActiveTarget) {
730
+ return;
731
+ }
732
+ this.setSession({
733
+ ...session,
734
+ activeDropTargetId: null,
735
+ });
736
+ }
737
+ notifyDragStart(event, subscriptionEvent) {
738
+ this.lifecycleCallbacks.onDragStart?.(event, this.lifecycleHelpers);
739
+ for (const subscription of Array.from(this.subscriptions)) {
740
+ subscription.onDragStart?.(subscriptionEvent);
741
+ }
742
+ }
743
+ notifyDragUpdate(event, subscriptionEvent) {
744
+ this.lifecycleCallbacks.onDragUpdate?.(event, this.lifecycleHelpers);
745
+ for (const subscription of Array.from(this.subscriptions)) {
746
+ subscription.onDragUpdate?.(subscriptionEvent);
747
+ }
748
+ }
749
+ notifyDragEnd(event) {
750
+ for (const subscription of Array.from(this.subscriptions)) {
751
+ subscription.onDragEnd?.(event);
752
+ }
753
+ this.lifecycleCallbacks.onDragEnd?.(event, this.lifecycleHelpers);
754
+ }
755
+ notifyDrop(event) {
756
+ for (const subscription of Array.from(this.subscriptions)) {
757
+ subscription.onDrop?.(event);
758
+ }
759
+ this.lifecycleCallbacks.onDrop?.(event, this.lifecycleHelpers);
760
+ }
761
+ }
762
+ function areOverlayMeasurementsEqual(previous, next) {
763
+ if (previous === next) {
764
+ return true;
765
+ }
766
+ if (!previous || !next) {
767
+ return false;
768
+ }
769
+ return (areNearlyEqual(previous.offsetX, next.offsetX) &&
770
+ areNearlyEqual(previous.offsetY, next.offsetY) &&
771
+ areNearlyEqual(previous.width, next.width) &&
772
+ areNearlyEqual(previous.height, next.height));
773
+ }
774
+ function areNearlyEqual(a, b) {
775
+ return Math.abs(a - b) < 0.5;
776
+ }
777
+ function getRectCenter(rect) {
778
+ return {
779
+ x: rect.left + rect.width / 2,
780
+ y: rect.top + rect.height / 2,
781
+ };
782
+ }
783
+ export function createDragRuntime(options) {
784
+ return new DragRuntime(options);
785
+ }
786
+ function isSameSortablePlacement(placement, sourcePlacement) {
787
+ if (sourcePlacement === null ||
788
+ placement.containerId !== sourcePlacement.containerId ||
789
+ placement.previousDraggableId !== sourcePlacement.previousDraggableId ||
790
+ placement.nextDraggableId !== sourcePlacement.nextDraggableId) {
791
+ return false;
792
+ }
793
+ if (placement.targetDraggableId === null || placement.side === null) {
794
+ return true;
795
+ }
796
+ return sourcePlacement.exactAnchors.some((anchor) => anchor.targetDraggableId === placement.targetDraggableId &&
797
+ anchor.side === placement.side);
798
+ }