@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
@@ -30,7 +30,7 @@ export function createSortable(input) {
30
30
  input.element.addEventListener("keydown", onKeyDown);
31
31
  keyDownAttached = true;
32
32
  }
33
- const cleanup = () => {
33
+ const releaseDomBinding = () => {
34
34
  if (cleanedUp) {
35
35
  return;
36
36
  }
@@ -49,10 +49,10 @@ export function createSortable(input) {
49
49
  element.removeAttribute("tabindex");
50
50
  }
51
51
  }
52
- behavior.cleanup();
52
+ behavior.releaseRegistration();
53
53
  };
54
- runtime.registerBindingCleanup({
55
- cleanup,
54
+ runtime.registerStaleDomBinding({
55
+ release: releaseDomBinding,
56
56
  isConnected: () => elementRef.deref()?.isConnected === true,
57
57
  });
58
58
  }
@@ -11,7 +11,7 @@ export type CreateDomSortableInput = SortableOptions & {
11
11
  };
12
12
  export type DomSortableBehavior = {
13
13
  setElement: (element: HTMLElement | null) => void;
14
- cleanup: () => void;
14
+ releaseRegistration: () => void;
15
15
  onPointerDown: (event: DomDraggablePointerDownEvent) => void;
16
16
  onKeyDown: (event: DomDraggableKeyDownEvent) => void;
17
17
  tabIndex: 0 | undefined;
@@ -13,7 +13,7 @@ export function createDomSortable(input) {
13
13
  });
14
14
  let registeredElementRef = null;
15
15
  let registeredDraggableId = null;
16
- const cleanup = () => {
16
+ const releaseRegistration = () => {
17
17
  const registeredElement = registeredElementRef?.deref() ?? null;
18
18
  unregisterSortableElement({
19
19
  registry,
@@ -31,7 +31,7 @@ export function createDomSortable(input) {
31
31
  registeredDraggableId === input.draggableId) {
32
32
  return;
33
33
  }
34
- cleanup();
34
+ releaseRegistration();
35
35
  if (!element) {
36
36
  return;
37
37
  }
@@ -47,7 +47,7 @@ export function createDomSortable(input) {
47
47
  registeredElementRef = new WeakRef(element);
48
48
  registeredDraggableId = input.draggableId;
49
49
  },
50
- cleanup,
50
+ releaseRegistration,
51
51
  onPointerDown: draggable.onPointerDown,
52
52
  onKeyDown: draggable.onKeyDown,
53
53
  tabIndex: draggable.tabIndex,
@@ -6,7 +6,7 @@ export function snapshotSortableElement(registry, draggableId) {
6
6
  return;
7
7
  }
8
8
  element.dataset.dndDragged = "true";
9
- registry.snapshots.set(draggableId, {
9
+ registry.activeDrag.snapshots.set(draggableId, {
10
10
  element,
11
11
  parent: element.parentElement,
12
12
  previousSibling: element.previousSibling,
@@ -18,13 +18,13 @@ export function isSortablePreviewTarget(input) {
18
18
  return input.activeDropTargetId !== null;
19
19
  }
20
20
  export function initializeSortablePointerMovement(registry, pointerPosition) {
21
- registry.pointerMovement = {
21
+ registry.activeDrag.pointerMovement = {
22
22
  previousPointerPosition: { ...pointerPosition },
23
23
  };
24
- registry.previewPlacement = null;
24
+ registry.activeDrag.previewPlacement = null;
25
25
  }
26
26
  export function updateSortablePointerMovement(registry, pointerPosition) {
27
- const movement = registry.pointerMovement;
27
+ const movement = registry.activeDrag.pointerMovement;
28
28
  if (!movement) {
29
29
  initializeSortablePointerMovement(registry, pointerPosition);
30
30
  return;
@@ -32,22 +32,22 @@ export function updateSortablePointerMovement(registry, pointerPosition) {
32
32
  movement.previousPointerPosition = { ...pointerPosition };
33
33
  }
34
34
  export function clearSortablePointerMovement(registry) {
35
- registry.pointerMovement = null;
35
+ registry.activeDrag.pointerMovement = null;
36
36
  }
37
37
  export function clearSortablePreviewPlacement(registry) {
38
- registry.previewPlacement = null;
38
+ registry.activeDrag.previewPlacement = null;
39
39
  }
40
40
  export function moveSortablePreview(input) {
41
41
  if (input.activeDropTargetId === null) {
42
42
  return;
43
43
  }
44
44
  const draggedElement = getRegisteredSortableElement(input.registry, input.draggedDraggableId);
45
- const draggedGroup = input.registry.groups.get(input.draggedDraggableId);
45
+ const draggedGroup = input.registry.registration.sortableGroups.get(input.draggedDraggableId);
46
46
  if (!draggedElement || !draggedGroup) {
47
47
  return;
48
48
  }
49
49
  const activeDropTargetId = input.activeDropTargetId === input.draggedDraggableId
50
- ? input.registry.previewPlacement?.activeDropTargetId
50
+ ? input.registry.activeDrag.previewPlacement?.activeDropTargetId
51
51
  : input.activeDropTargetId;
52
52
  if (!activeDropTargetId ||
53
53
  activeDropTargetId === input.draggedDraggableId) {
@@ -82,7 +82,7 @@ export function moveSortablePreview(input) {
82
82
  placement: null,
83
83
  movementDirection: getCurrentAxisMovementDirection({
84
84
  placementPosition: input.placementPosition,
85
- movement: input.registry.pointerMovement,
85
+ movement: input.registry.activeDrag.pointerMovement,
86
86
  axis: input.options.axis,
87
87
  }),
88
88
  });
@@ -117,8 +117,8 @@ export function moveSortablePreview(input) {
117
117
  activeDropTargetId,
118
118
  targetElement,
119
119
  placementPosition: input.placementPosition,
120
- movement: input.registry.pointerMovement,
121
- previewPlacement: input.registry.previewPlacement,
120
+ movement: input.registry.activeDrag.pointerMovement,
121
+ previewPlacement: input.registry.activeDrag.previewPlacement,
122
122
  options: input.options,
123
123
  });
124
124
  const placement = placementDecision.placement;
@@ -201,7 +201,7 @@ export function getSortablePreviewPlacement(input) {
201
201
  };
202
202
  }
203
203
  function setSortablePreviewPlacementState(input) {
204
- input.registry.previewPlacement = {
204
+ input.registry.activeDrag.previewPlacement = {
205
205
  activeDropTargetId: input.activeDropTargetId,
206
206
  placement: input.placement,
207
207
  movementDirection: input.movementDirection,
@@ -235,7 +235,7 @@ export function isSortablePreviewAlreadyPlaced(input) {
235
235
  : input.draggedElement.previousElementSibling === input.targetElement;
236
236
  }
237
237
  export function restoreSortableSnapshot(registry, draggableId) {
238
- const snapshot = registry.snapshots.get(draggableId);
238
+ const snapshot = registry.activeDrag.snapshots.get(draggableId);
239
239
  if (!snapshot) {
240
240
  return false;
241
241
  }
@@ -244,7 +244,7 @@ export function restoreSortableSnapshot(registry, draggableId) {
244
244
  }
245
245
  export function clearSortableDraggedState(registry, draggableId) {
246
246
  const element = getRegisteredSortableElement(registry, draggableId) ??
247
- registry.snapshots.get(draggableId)?.element;
247
+ registry.activeDrag.snapshots.get(draggableId)?.element;
248
248
  if (element) {
249
249
  restoreSortableDraggedAttribute({
250
250
  registry,
@@ -254,8 +254,10 @@ export function clearSortableDraggedState(registry, draggableId) {
254
254
  }
255
255
  }
256
256
  function getSortableDraggableId(registry, element) {
257
- const draggableId = registry.elementIds.get(element);
258
- return draggableId && registry.elements.get(draggableId)?.deref() === element
257
+ const draggableId = registry.registration.sortableElementIds.get(element);
258
+ return (draggableId &&
259
+ registry.registration.sortableElementRefs.get(draggableId)?.deref() ===
260
+ element)
259
261
  ? draggableId
260
262
  : null;
261
263
  }
@@ -299,7 +301,8 @@ function refreshSortableDropTargetMeasurements(input) {
299
301
  input.runtime.registerDropTarget(registration.id, registration.element, registration.group, {
300
302
  containerId: registration.containerId,
301
303
  sortable: registration.capabilities.sortable,
302
- sortableAxis: input.registry.sortableOptions.get(registration.id)?.axis ?? "vertical",
304
+ sortableAxis: input.registry.registration.sortableOptions.get(registration.id)
305
+ ?.axis ?? "vertical",
303
306
  });
304
307
  }
305
308
  }
@@ -58,19 +58,28 @@ export type DomSortableRuntime = DomDraggableRuntime & {
58
58
  source: "pointer" | "keyboard";
59
59
  dropTargetId: string;
60
60
  }) => void;
61
+ onActiveDragReset?: (event: {
62
+ draggableId: string;
63
+ source: "pointer" | "keyboard";
64
+ }) => void;
61
65
  }) => () => void;
62
- onDispose?: (callback: () => void) => () => void;
63
66
  remeasureDropTargets: (input?: RemeasureDropTargetsInput) => void;
64
67
  };
65
68
  export type SortableRegistry = {
66
- attributeSnapshots: Map<string, SortableAttributeSnapshot>;
67
- elementIds: WeakMap<HTMLElement, string>;
68
- elements: Map<string, WeakRef<HTMLElement>>;
69
- groups: Map<string, string>;
69
+ registration: SortableRegistrationState;
70
+ activeDrag: SortableActiveDragState;
71
+ };
72
+ export type SortableRegistrationState = {
73
+ registrationAttributeSnapshots: Map<string, SortableAttributeSnapshot>;
74
+ sortableElementIds: WeakMap<HTMLElement, string>;
75
+ sortableElementRefs: Map<string, WeakRef<HTMLElement>>;
76
+ sortableGroups: Map<string, string>;
77
+ sortableOptions: Map<string, NormalizedSortableOptions>;
78
+ };
79
+ export type SortableActiveDragState = {
70
80
  previewPlacement: SortablePreviewPlacementState | null;
71
81
  pointerMovement: SortablePointerMovementState | null;
72
82
  snapshots: Map<string, SortableSnapshot>;
73
- sortableOptions: Map<string, NormalizedSortableOptions>;
74
83
  };
75
84
  export type SortableAttributeSnapshot = {
76
85
  elementRef: WeakRef<HTMLElement>;
@@ -7,16 +7,20 @@ export function getSortableRegistry(runtime) {
7
7
  return existingRegistry;
8
8
  }
9
9
  const registry = {
10
- attributeSnapshots: new Map(),
11
- elementIds: new WeakMap(),
12
- elements: new Map(),
13
- groups: new Map(),
14
- previewPlacement: null,
15
- pointerMovement: null,
16
- snapshots: new Map(),
17
- sortableOptions: new Map(),
10
+ registration: {
11
+ registrationAttributeSnapshots: new Map(),
12
+ sortableElementIds: new WeakMap(),
13
+ sortableElementRefs: new Map(),
14
+ sortableGroups: new Map(),
15
+ sortableOptions: new Map(),
16
+ },
17
+ activeDrag: {
18
+ previewPlacement: null,
19
+ pointerMovement: null,
20
+ snapshots: new Map(),
21
+ },
18
22
  };
19
- const unsubscribe = runtime.subscribe({
23
+ runtime.subscribe({
20
24
  onDragStart: (event) => {
21
25
  initializeSortablePointerMovement(registry, event.placementPosition ?? event.pointerPosition);
22
26
  snapshotSortableElement(registry, event.draggableId);
@@ -41,40 +45,51 @@ export function getSortableRegistry(runtime) {
41
45
  updateSortablePointerMovement(registry, event.placementPosition ?? event.pointerPosition);
42
46
  },
43
47
  onDragEnd: (event) => {
44
- restoreSortableSnapshot(registry, event.draggableId);
45
- clearSortableDraggedState(registry, event.draggableId);
46
- clearSortablePointerMovement(registry);
47
- clearSortablePreviewPlacement(registry);
48
- registry.snapshots.delete(event.draggableId);
48
+ releaseSortableActiveDragState(registry, event.draggableId);
49
49
  },
50
50
  onDrop: (event) => {
51
- clearSortableDraggedState(registry, event.draggableId);
52
- clearSortablePointerMovement(registry);
53
- clearSortablePreviewPlacement(registry);
54
- registry.snapshots.delete(event.draggableId);
51
+ releaseSortableActiveDragState(registry, event.draggableId);
52
+ },
53
+ onActiveDragReset: (event) => {
54
+ releaseSortableActiveDragState(registry, event.draggableId);
55
55
  },
56
56
  });
57
- const cleanupRegistry = () => {
58
- unsubscribe();
59
- unsubscribeDispose?.();
60
- registry.elements.clear();
61
- registry.groups.clear();
62
- registry.pointerMovement = null;
63
- registry.previewPlacement = null;
64
- registry.attributeSnapshots.clear();
65
- registry.snapshots.clear();
66
- registry.sortableOptions.clear();
67
- sortableRegistries.delete(runtime);
68
- };
69
- const unsubscribeDispose = runtime.onDispose?.(cleanupRegistry);
70
57
  sortableRegistries.set(runtime, registry);
71
58
  return registry;
72
59
  }
60
+ function releaseSortableActiveDragState(registry, draggableId) {
61
+ let releaseError;
62
+ let hasReleaseError = false;
63
+ try {
64
+ restoreSortableSnapshot(registry, draggableId);
65
+ }
66
+ catch (error) {
67
+ releaseError = error;
68
+ hasReleaseError = true;
69
+ }
70
+ try {
71
+ clearSortableDraggedState(registry, draggableId);
72
+ }
73
+ catch (error) {
74
+ if (!hasReleaseError) {
75
+ releaseError = error;
76
+ hasReleaseError = true;
77
+ }
78
+ }
79
+ finally {
80
+ clearSortablePointerMovement(registry);
81
+ clearSortablePreviewPlacement(registry);
82
+ registry.activeDrag.snapshots.delete(draggableId);
83
+ }
84
+ if (hasReleaseError) {
85
+ throw releaseError;
86
+ }
87
+ }
73
88
  export function registerSortableElement(input) {
74
- const existingSnapshot = input.registry.attributeSnapshots.get(input.draggableId);
89
+ const existingSnapshot = input.registry.registration.registrationAttributeSnapshots.get(input.draggableId);
75
90
  const existingSnapshotElement = existingSnapshot?.elementRef.deref() ?? null;
76
91
  if (!existingSnapshot || existingSnapshotElement !== input.element) {
77
- input.registry.attributeSnapshots.set(input.draggableId, {
92
+ input.registry.registration.registrationAttributeSnapshots.set(input.draggableId, {
78
93
  elementRef: new WeakRef(input.element),
79
94
  sortableDraggable: input.element.getAttribute("data-dnd-sortable-draggable"),
80
95
  dragged: input.element.getAttribute("data-dnd-dragged"),
@@ -82,10 +97,10 @@ export function registerSortableElement(input) {
82
97
  }
83
98
  input.element.setAttribute("data-dnd-sortable-draggable", "true");
84
99
  const normalizedOptions = normalizeSortableOptions(input.options);
85
- input.registry.elementIds.set(input.element, input.draggableId);
86
- input.registry.elements.set(input.draggableId, new WeakRef(input.element));
87
- input.registry.groups.set(input.draggableId, input.group);
88
- input.registry.sortableOptions.set(input.draggableId, normalizedOptions);
100
+ input.registry.registration.sortableElementIds.set(input.element, input.draggableId);
101
+ input.registry.registration.sortableElementRefs.set(input.draggableId, new WeakRef(input.element));
102
+ input.registry.registration.sortableGroups.set(input.draggableId, input.group);
103
+ input.registry.registration.sortableOptions.set(input.draggableId, normalizedOptions);
89
104
  input.runtime.registerDropTarget(input.draggableId, input.element, input.group, {
90
105
  containerId: input.containerId ?? null,
91
106
  sortable: true,
@@ -95,14 +110,16 @@ export function registerSortableElement(input) {
95
110
  export function unregisterSortableElement(input) {
96
111
  if (input.draggableId !== null) {
97
112
  input.runtime.unregisterDropTarget(input.draggableId, input.element ?? undefined);
98
- const registeredElement = input.registry.elements.get(input.draggableId)?.deref() ?? null;
113
+ const registeredElement = input.registry.registration.sortableElementRefs
114
+ .get(input.draggableId)
115
+ ?.deref() ?? null;
99
116
  if (registeredElement === input.element || input.element === null) {
100
117
  if (registeredElement) {
101
- input.registry.elementIds.delete(registeredElement);
118
+ input.registry.registration.sortableElementIds.delete(registeredElement);
102
119
  }
103
- input.registry.elements.delete(input.draggableId);
104
- input.registry.groups.delete(input.draggableId);
105
- input.registry.sortableOptions.delete(input.draggableId);
120
+ input.registry.registration.sortableElementRefs.delete(input.draggableId);
121
+ input.registry.registration.sortableGroups.delete(input.draggableId);
122
+ input.registry.registration.sortableOptions.delete(input.draggableId);
106
123
  }
107
124
  }
108
125
  if (input.element) {
@@ -115,26 +132,27 @@ export function unregisterSortableElement(input) {
115
132
  }
116
133
  export function restoreSortableInternalAttributes(input) {
117
134
  const snapshot = input.draggableId
118
- ? input.registry.attributeSnapshots.get(input.draggableId)
135
+ ? input.registry.registration.registrationAttributeSnapshots.get(input.draggableId)
119
136
  : null;
120
137
  const snapshotElement = snapshot?.elementRef.deref() ?? null;
121
138
  restoreAttribute(input.element, "data-dnd-sortable-draggable", snapshot && snapshotElement === input.element ? snapshot.sortableDraggable : null);
122
139
  restoreAttribute(input.element, "data-dnd-dragged", snapshot && snapshotElement === input.element ? snapshot.dragged : null);
123
140
  if (input.draggableId) {
124
- input.registry.attributeSnapshots.delete(input.draggableId);
141
+ input.registry.registration.registrationAttributeSnapshots.delete(input.draggableId);
125
142
  }
126
143
  }
127
144
  export function restoreSortableDraggedAttribute(input) {
128
- const snapshot = input.registry.attributeSnapshots.get(input.draggableId);
145
+ const snapshot = input.registry.registration.registrationAttributeSnapshots.get(input.draggableId);
129
146
  const snapshotElement = snapshot?.elementRef.deref() ?? null;
130
147
  restoreAttribute(input.element, "data-dnd-dragged", snapshot && snapshotElement === input.element ? snapshot.dragged : null);
131
148
  }
132
149
  export function getRegisteredSortableElement(registry, draggableId) {
133
- const element = registry.elements.get(draggableId)?.deref() ?? null;
150
+ const element = registry.registration.sortableElementRefs.get(draggableId)?.deref() ?? null;
134
151
  return element?.isConnected ? element : null;
135
152
  }
136
153
  function getSortableOptions(registry, draggableId) {
137
- return (registry.sortableOptions.get(draggableId) ?? normalizeSortableOptions(undefined));
154
+ return (registry.registration.sortableOptions.get(draggableId) ??
155
+ normalizeSortableOptions(undefined));
138
156
  }
139
157
  function restoreAttribute(element, attribute, value) {
140
158
  if (value === null) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mk-drag-and-drop/dom",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Headless DOM drag-and-drop primitives for draggable, droppable, sortable, and grouped interactions.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://mkramer.dev",
@@ -1,22 +0,0 @@
1
- import type { DragRect } from "../geometry/rects.js";
2
- import type { DomDraggableRuntime } from "../draggable/create-draggable.js";
3
- import type { DomDropContainerRuntime } from "../droppable/create-drop-container.js";
4
- import type { DomDroppableRuntime } from "../droppable/create-droppable.js";
5
- import type { DomSortableRuntime } from "../sortable/sortable-registry.js";
6
- import { type BindingCleanupRecord } from "./drag-runtime.js";
7
- import type { DragRuntimeConfigureInput, DragRuntimeOptions } from "./types.js";
8
- export type DragRuntimeHandleOptions = DragRuntimeOptions;
9
- export type DragRuntimeHandleConfigureInput = DragRuntimeConfigureInput;
10
- export type DragRuntimeHandle = DomDraggableRuntime & DomDroppableRuntime & DomDropContainerRuntime & DomSortableRuntime & {
11
- configure: (input: DragRuntimeHandleConfigureInput) => void;
12
- cleanup: () => void;
13
- dispose: () => void;
14
- onDispose: (callback: () => void) => () => void;
15
- setOverlayRect: (overlayRect: DragRect | null) => void;
16
- };
17
- export type InternalBindingCleanupRuntime = {
18
- registerBindingCleanup: (record: BindingCleanupRecord) => () => void;
19
- pruneDisconnectedBindingCleanups: () => void;
20
- getBindingCleanupRecordCount: () => number;
21
- };
22
- export declare function createDragRuntimeHandle(options?: DragRuntimeHandleOptions): DragRuntimeHandle;