@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,357 @@
1
+ import { getRegisteredSortableElement, restoreSortableDraggedAttribute, } from "./sortable-registry.js";
2
+ const neutralPlacementBoundaryRatio = 0.5;
3
+ export function snapshotSortableElement(registry, draggableId) {
4
+ const element = getRegisteredSortableElement(registry, draggableId);
5
+ if (!element?.parentElement) {
6
+ return;
7
+ }
8
+ element.dataset.dndDragged = "true";
9
+ registry.snapshots.set(draggableId, {
10
+ element,
11
+ parent: element.parentElement,
12
+ previousSibling: element.previousSibling,
13
+ nextSibling: element.nextSibling,
14
+ childIndex: Array.prototype.indexOf.call(element.parentElement.childNodes, element),
15
+ });
16
+ }
17
+ export function isSortablePreviewTarget(input) {
18
+ return input.activeDropTargetId !== null;
19
+ }
20
+ export function initializeSortablePointerMovement(registry, pointerPosition) {
21
+ registry.pointerMovement = {
22
+ previousPointerPosition: { ...pointerPosition },
23
+ };
24
+ registry.previewPlacement = null;
25
+ }
26
+ export function updateSortablePointerMovement(registry, pointerPosition) {
27
+ const movement = registry.pointerMovement;
28
+ if (!movement) {
29
+ initializeSortablePointerMovement(registry, pointerPosition);
30
+ return;
31
+ }
32
+ movement.previousPointerPosition = { ...pointerPosition };
33
+ }
34
+ export function clearSortablePointerMovement(registry) {
35
+ registry.pointerMovement = null;
36
+ }
37
+ export function clearSortablePreviewPlacement(registry) {
38
+ registry.previewPlacement = null;
39
+ }
40
+ export function moveSortablePreview(input) {
41
+ if (input.activeDropTargetId === null) {
42
+ return;
43
+ }
44
+ const draggedElement = getRegisteredSortableElement(input.registry, input.draggedDraggableId);
45
+ const draggedGroup = input.registry.groups.get(input.draggedDraggableId);
46
+ if (!draggedElement || !draggedGroup) {
47
+ return;
48
+ }
49
+ const activeDropTargetId = input.activeDropTargetId === input.draggedDraggableId
50
+ ? input.registry.previewPlacement?.activeDropTargetId
51
+ : input.activeDropTargetId;
52
+ if (!activeDropTargetId ||
53
+ activeDropTargetId === input.draggedDraggableId) {
54
+ refreshSortableDropTargetMeasurements({
55
+ registry: input.registry,
56
+ runtime: input.runtime,
57
+ group: draggedGroup,
58
+ dropTargetIds: getSortablePreviewMeasurementIds({
59
+ registry: input.registry,
60
+ draggedElement,
61
+ }),
62
+ });
63
+ return;
64
+ }
65
+ const target = input.runtime.getDropTargetRegistration(activeDropTargetId, draggedGroup);
66
+ if (!target) {
67
+ return;
68
+ }
69
+ const measurementIds = getSortablePreviewMeasurementIds({
70
+ registry: input.registry,
71
+ draggedElement,
72
+ targetElement: target.element,
73
+ });
74
+ const draggedRegistration = input.runtime.getDropTargetRegistration(input.draggedDraggableId, draggedGroup);
75
+ addRegistrationContainerId(measurementIds, draggedRegistration);
76
+ addRegistrationContainerId(measurementIds, target);
77
+ if (target.capabilities.container) {
78
+ measurementIds.add(target.id);
79
+ setSortablePreviewPlacementState({
80
+ registry: input.registry,
81
+ activeDropTargetId: target.id,
82
+ placement: null,
83
+ movementDirection: getCurrentAxisMovementDirection({
84
+ placementPosition: input.placementPosition,
85
+ movement: input.registry.pointerMovement,
86
+ axis: input.options.axis,
87
+ }),
88
+ });
89
+ if (!moveSortablePreviewIntoContainer({
90
+ draggedElement,
91
+ targetElement: target.element,
92
+ })) {
93
+ return;
94
+ }
95
+ addSortableMeasurementIdsAroundElement(measurementIds, input.registry, draggedElement);
96
+ refreshSortableDropTargetMeasurements({
97
+ registry: input.registry,
98
+ runtime: input.runtime,
99
+ group: draggedGroup,
100
+ dropTargetIds: measurementIds,
101
+ });
102
+ return;
103
+ }
104
+ const targetElement = target.element;
105
+ const listElement = targetElement.parentElement;
106
+ if (!listElement) {
107
+ return;
108
+ }
109
+ if (!target.capabilities.sortable) {
110
+ return;
111
+ }
112
+ const targetDraggableId = getSortableDraggableId(input.registry, targetElement);
113
+ if (targetDraggableId !== activeDropTargetId) {
114
+ return;
115
+ }
116
+ const placementDecision = getSortablePreviewPlacement({
117
+ activeDropTargetId,
118
+ targetElement,
119
+ placementPosition: input.placementPosition,
120
+ movement: input.registry.pointerMovement,
121
+ previewPlacement: input.registry.previewPlacement,
122
+ options: input.options,
123
+ });
124
+ const placement = placementDecision.placement;
125
+ setSortablePreviewPlacementState({
126
+ registry: input.registry,
127
+ activeDropTargetId,
128
+ placement,
129
+ movementDirection: placementDecision.movementDirection,
130
+ });
131
+ if (isSortablePreviewAlreadyPlaced({
132
+ draggedElement,
133
+ targetElement,
134
+ placement,
135
+ })) {
136
+ return;
137
+ }
138
+ if (placement === "after") {
139
+ targetElement.after(draggedElement);
140
+ }
141
+ else {
142
+ targetElement.before(draggedElement);
143
+ }
144
+ addSortableMeasurementIdsAroundElement(measurementIds, input.registry, draggedElement);
145
+ addSortableMeasurementIdsAroundElement(measurementIds, input.registry, targetElement);
146
+ refreshSortableDropTargetMeasurements({
147
+ registry: input.registry,
148
+ runtime: input.runtime,
149
+ group: draggedGroup,
150
+ dropTargetIds: measurementIds,
151
+ });
152
+ }
153
+ export function getSortablePreviewPlacement(input) {
154
+ const axis = input.options.axis;
155
+ const axisPosition = getAxisPosition(input.placementPosition, axis);
156
+ const targetRect = input.targetElement.getBoundingClientRect();
157
+ const currentDirection = getCurrentAxisMovementDirection({
158
+ placementPosition: input.placementPosition,
159
+ movement: input.movement,
160
+ axis,
161
+ });
162
+ const previousPlacement = input.previewPlacement;
163
+ if (!previousPlacement ||
164
+ previousPlacement.activeDropTargetId !== input.activeDropTargetId ||
165
+ previousPlacement.placement === null ||
166
+ previousPlacement.movementDirection === "none") {
167
+ return {
168
+ placement: getOptimisticPlacement({
169
+ axis,
170
+ axisPosition,
171
+ currentDirection,
172
+ targetRect,
173
+ }),
174
+ movementDirection: currentDirection,
175
+ };
176
+ }
177
+ if (currentDirection === "none" ||
178
+ currentDirection === previousPlacement.movementDirection) {
179
+ return {
180
+ placement: previousPlacement.placement,
181
+ movementDirection: previousPlacement.movementDirection,
182
+ };
183
+ }
184
+ const boundary = getPlacementBoundary({
185
+ targetRect,
186
+ axis,
187
+ direction: currentDirection,
188
+ startRatio: input.options.placementBoundary.start,
189
+ endRatio: input.options.placementBoundary.end,
190
+ });
191
+ const reversalPlacement = getPlacementSideFromBoundary(axisPosition, boundary);
192
+ if (reversalPlacement === previousPlacement.placement) {
193
+ return {
194
+ placement: previousPlacement.placement,
195
+ movementDirection: previousPlacement.movementDirection,
196
+ };
197
+ }
198
+ return {
199
+ placement: reversalPlacement,
200
+ movementDirection: currentDirection,
201
+ };
202
+ }
203
+ function setSortablePreviewPlacementState(input) {
204
+ input.registry.previewPlacement = {
205
+ activeDropTargetId: input.activeDropTargetId,
206
+ placement: input.placement,
207
+ movementDirection: input.movementDirection,
208
+ };
209
+ }
210
+ function getOptimisticPlacement(input) {
211
+ if (input.currentDirection === "forward") {
212
+ return "after";
213
+ }
214
+ if (input.currentDirection === "backward") {
215
+ return "before";
216
+ }
217
+ return getPlacementSideFromBoundary(input.axisPosition, getPlacementBoundary({
218
+ targetRect: input.targetRect,
219
+ axis: input.axis,
220
+ direction: "none",
221
+ startRatio: neutralPlacementBoundaryRatio,
222
+ endRatio: neutralPlacementBoundaryRatio,
223
+ }));
224
+ }
225
+ function getCurrentAxisMovementDirection(input) {
226
+ const axisPosition = getAxisPosition(input.placementPosition, input.axis);
227
+ const previousAxisPosition = input.movement
228
+ ? getAxisPosition(input.movement.previousPointerPosition, input.axis)
229
+ : axisPosition;
230
+ return getAxisMovementDirection(previousAxisPosition, axisPosition);
231
+ }
232
+ export function isSortablePreviewAlreadyPlaced(input) {
233
+ return input.placement === "before"
234
+ ? input.draggedElement.nextElementSibling === input.targetElement
235
+ : input.draggedElement.previousElementSibling === input.targetElement;
236
+ }
237
+ export function restoreSortableSnapshot(registry, draggableId) {
238
+ const snapshot = registry.snapshots.get(draggableId);
239
+ if (!snapshot) {
240
+ return false;
241
+ }
242
+ snapshot.parent.insertBefore(snapshot.element, getSnapshotRestoreReferenceNode(snapshot));
243
+ return true;
244
+ }
245
+ export function clearSortableDraggedState(registry, draggableId) {
246
+ const element = getRegisteredSortableElement(registry, draggableId) ??
247
+ registry.snapshots.get(draggableId)?.element;
248
+ if (element) {
249
+ restoreSortableDraggedAttribute({
250
+ registry,
251
+ draggableId,
252
+ element,
253
+ });
254
+ }
255
+ }
256
+ function getSortableDraggableId(registry, element) {
257
+ const draggableId = registry.elementIds.get(element);
258
+ return draggableId && registry.elements.get(draggableId)?.deref() === element
259
+ ? draggableId
260
+ : null;
261
+ }
262
+ function getSortablePreviewMeasurementIds(input) {
263
+ const dropTargetIds = new Set();
264
+ addSortableMeasurementIdsAroundElement(dropTargetIds, input.registry, input.draggedElement);
265
+ if (input.targetElement) {
266
+ addSortableMeasurementIdsAroundElement(dropTargetIds, input.registry, input.targetElement);
267
+ }
268
+ return dropTargetIds;
269
+ }
270
+ function addSortableMeasurementIdsAroundElement(dropTargetIds, registry, element) {
271
+ addSortableMeasurementId(dropTargetIds, registry, element);
272
+ addSortableMeasurementId(dropTargetIds, registry, element.previousElementSibling);
273
+ addSortableMeasurementId(dropTargetIds, registry, element.nextElementSibling);
274
+ }
275
+ function addSortableMeasurementId(dropTargetIds, registry, element) {
276
+ if (!(element instanceof HTMLElement)) {
277
+ return;
278
+ }
279
+ const draggableId = getSortableDraggableId(registry, element);
280
+ if (draggableId) {
281
+ dropTargetIds.add(draggableId);
282
+ }
283
+ }
284
+ function addRegistrationContainerId(dropTargetIds, registration) {
285
+ if (registration?.containerId) {
286
+ dropTargetIds.add(registration.containerId);
287
+ }
288
+ }
289
+ function refreshSortableDropTargetMeasurements(input) {
290
+ for (const dropTargetId of input.dropTargetIds) {
291
+ const registration = input.runtime.getDropTargetRegistration(dropTargetId, input.group);
292
+ if (!registration) {
293
+ continue;
294
+ }
295
+ if (registration.capabilities.container) {
296
+ input.runtime.registerDropContainer?.(registration.id, registration.element, registration.group);
297
+ continue;
298
+ }
299
+ input.runtime.registerDropTarget(registration.id, registration.element, registration.group, {
300
+ containerId: registration.containerId,
301
+ sortable: registration.capabilities.sortable,
302
+ sortableAxis: input.registry.sortableOptions.get(registration.id)?.axis ?? "vertical",
303
+ });
304
+ }
305
+ }
306
+ function moveSortablePreviewIntoContainer(input) {
307
+ if (input.draggedElement.parentElement === input.targetElement &&
308
+ input.draggedElement.previousSibling === null) {
309
+ return false;
310
+ }
311
+ input.targetElement.insertBefore(input.draggedElement, input.targetElement.firstChild);
312
+ return true;
313
+ }
314
+ function getSnapshotRestoreReferenceNode(snapshot) {
315
+ if (snapshot.nextSibling &&
316
+ snapshot.nextSibling !== snapshot.element &&
317
+ snapshot.nextSibling.parentNode === snapshot.parent) {
318
+ return snapshot.nextSibling;
319
+ }
320
+ if (snapshot.previousSibling &&
321
+ snapshot.previousSibling !== snapshot.element &&
322
+ snapshot.previousSibling.parentNode === snapshot.parent) {
323
+ const referenceNode = snapshot.previousSibling.nextSibling;
324
+ return referenceNode === snapshot.element
325
+ ? snapshot.element.nextSibling
326
+ : referenceNode;
327
+ }
328
+ const indexedReferenceNode = snapshot.parent.childNodes.item(snapshot.childIndex);
329
+ return indexedReferenceNode === snapshot.element
330
+ ? snapshot.element.nextSibling
331
+ : indexedReferenceNode;
332
+ }
333
+ export function getAxisPosition(pointerPosition, axis) {
334
+ return axis === "horizontal" ? pointerPosition.x : pointerPosition.y;
335
+ }
336
+ export function getAxisMovementDirection(previousAxisPosition, currentAxisPosition) {
337
+ if (currentAxisPosition > previousAxisPosition) {
338
+ return "forward";
339
+ }
340
+ if (currentAxisPosition < previousAxisPosition) {
341
+ return "backward";
342
+ }
343
+ return "none";
344
+ }
345
+ export function getPlacementBoundary(input) {
346
+ const targetStart = input.axis === "horizontal" ? input.targetRect.left : input.targetRect.top;
347
+ const targetSize = input.axis === "horizontal" ? input.targetRect.width : input.targetRect.height;
348
+ const boundaryRatio = input.direction === "forward"
349
+ ? input.startRatio
350
+ : input.direction === "backward"
351
+ ? input.endRatio
352
+ : neutralPlacementBoundaryRatio;
353
+ return targetStart + targetSize * boundaryRatio;
354
+ }
355
+ export function getPlacementSideFromBoundary(axisPosition, boundary) {
356
+ return axisPosition < boundary ? "before" : "after";
357
+ }
@@ -0,0 +1,113 @@
1
+ import type { DomDraggableRuntime } from "../draggable/create-draggable.js";
2
+ import type { RemeasureDropTargetsInput } from "../runtime/drop-target-registry.js";
3
+ import { type SortablePreviewPlacementState, type SortablePointerMovementState } from "./sortable-preview.js";
4
+ import { type NormalizedSortableOptions, type SortableOptions } from "./sortable-options.js";
5
+ export type DomSortableRuntime = DomDraggableRuntime & {
6
+ registerDropTarget: (draggableId: string, element: HTMLElement, group: string, options?: {
7
+ containerId?: string | null;
8
+ sortable?: boolean;
9
+ sortableAxis?: NormalizedSortableOptions["axis"];
10
+ }) => void;
11
+ registerDropContainer?: (containerId: string, element: HTMLElement, group: string) => void;
12
+ unregisterDropTarget: (draggableId: string, element?: HTMLElement) => void;
13
+ getDropTargetRegistration: (dropTargetId: string, group?: string) => {
14
+ id: string;
15
+ element: HTMLElement;
16
+ group: string;
17
+ containerId: string | null;
18
+ capabilities: {
19
+ container: boolean;
20
+ sortable: boolean;
21
+ };
22
+ } | null;
23
+ subscribe: (subscription: {
24
+ onDragStart?: (event: {
25
+ draggableId: string;
26
+ source: "pointer" | "keyboard";
27
+ pointerPosition: {
28
+ x: number;
29
+ y: number;
30
+ };
31
+ placementPosition?: {
32
+ x: number;
33
+ y: number;
34
+ };
35
+ }) => void;
36
+ onDragUpdate?: (event: {
37
+ draggableId: string;
38
+ source: "pointer" | "keyboard";
39
+ pointerPosition: {
40
+ x: number;
41
+ y: number;
42
+ };
43
+ placementPosition?: {
44
+ x: number;
45
+ y: number;
46
+ };
47
+ activeDropTargetId: string | null;
48
+ previousDropTargetId: string | null;
49
+ }) => void;
50
+ onDragEnd?: (event: {
51
+ draggableId: string;
52
+ source: "pointer" | "keyboard";
53
+ result: "dropped" | "no-target" | "invalid-target" | "canceled";
54
+ dropTargetId: string | null;
55
+ }) => void;
56
+ onDrop?: (event: {
57
+ draggableId: string;
58
+ source: "pointer" | "keyboard";
59
+ dropTargetId: string;
60
+ }) => void;
61
+ }) => () => void;
62
+ onDispose?: (callback: () => void) => () => void;
63
+ remeasureDropTargets: (input?: RemeasureDropTargetsInput) => void;
64
+ };
65
+ export type SortableRegistry = {
66
+ attributeSnapshots: Map<string, SortableAttributeSnapshot>;
67
+ elementIds: WeakMap<HTMLElement, string>;
68
+ elements: Map<string, WeakRef<HTMLElement>>;
69
+ groups: Map<string, string>;
70
+ previewPlacement: SortablePreviewPlacementState | null;
71
+ pointerMovement: SortablePointerMovementState | null;
72
+ snapshots: Map<string, SortableSnapshot>;
73
+ sortableOptions: Map<string, NormalizedSortableOptions>;
74
+ };
75
+ export type SortableAttributeSnapshot = {
76
+ elementRef: WeakRef<HTMLElement>;
77
+ sortableDraggable: string | null;
78
+ dragged: string | null;
79
+ };
80
+ export type SortableSnapshot = {
81
+ element: HTMLElement;
82
+ parent: HTMLElement;
83
+ previousSibling: ChildNode | null;
84
+ nextSibling: ChildNode | null;
85
+ childIndex: number;
86
+ };
87
+ export declare function getSortableRegistry(runtime: DomSortableRuntime): SortableRegistry;
88
+ export declare function registerSortableElement(input: {
89
+ registry: SortableRegistry;
90
+ runtime: DomSortableRuntime;
91
+ draggableId: string;
92
+ group: string;
93
+ containerId?: string | null;
94
+ element: HTMLElement;
95
+ options?: SortableOptions;
96
+ }): void;
97
+ export declare function unregisterSortableElement(input: {
98
+ registry: SortableRegistry;
99
+ runtime: DomSortableRuntime;
100
+ draggableId: string | null;
101
+ element: HTMLElement | null;
102
+ }): void;
103
+ export declare function restoreSortableInternalAttributes(input: {
104
+ registry: SortableRegistry;
105
+ draggableId: string | null;
106
+ element: HTMLElement;
107
+ }): void;
108
+ export declare function restoreSortableDraggedAttribute(input: {
109
+ registry: SortableRegistry;
110
+ draggableId: string;
111
+ element: HTMLElement;
112
+ }): void;
113
+ export declare function getRegisteredSortableElement(registry: SortableRegistry, draggableId: string): HTMLElement | null;
@@ -0,0 +1,145 @@
1
+ import { clearSortableDraggedState, clearSortablePointerMovement, initializeSortablePointerMovement, moveSortablePreview, restoreSortableSnapshot, snapshotSortableElement, isSortablePreviewTarget, updateSortablePointerMovement, clearSortablePreviewPlacement, } from "./sortable-preview.js";
2
+ import { normalizeSortableOptions, } from "./sortable-options.js";
3
+ const sortableRegistries = new WeakMap();
4
+ export function getSortableRegistry(runtime) {
5
+ const existingRegistry = sortableRegistries.get(runtime);
6
+ if (existingRegistry) {
7
+ return existingRegistry;
8
+ }
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(),
18
+ };
19
+ const unsubscribe = runtime.subscribe({
20
+ onDragStart: (event) => {
21
+ initializeSortablePointerMovement(registry, event.placementPosition ?? event.pointerPosition);
22
+ snapshotSortableElement(registry, event.draggableId);
23
+ },
24
+ onDragUpdate: (event) => {
25
+ if (isSortablePreviewTarget({
26
+ activeDropTargetId: event.activeDropTargetId,
27
+ })) {
28
+ moveSortablePreview({
29
+ registry,
30
+ runtime,
31
+ draggedDraggableId: event.draggableId,
32
+ activeDropTargetId: event.activeDropTargetId,
33
+ pointerPosition: event.pointerPosition,
34
+ placementPosition: event.placementPosition ?? event.pointerPosition,
35
+ options: getSortableOptions(registry, event.draggableId),
36
+ });
37
+ }
38
+ else {
39
+ clearSortablePreviewPlacement(registry);
40
+ }
41
+ updateSortablePointerMovement(registry, event.placementPosition ?? event.pointerPosition);
42
+ },
43
+ onDragEnd: (event) => {
44
+ restoreSortableSnapshot(registry, event.draggableId);
45
+ clearSortableDraggedState(registry, event.draggableId);
46
+ clearSortablePointerMovement(registry);
47
+ clearSortablePreviewPlacement(registry);
48
+ registry.snapshots.delete(event.draggableId);
49
+ },
50
+ onDrop: (event) => {
51
+ clearSortableDraggedState(registry, event.draggableId);
52
+ clearSortablePointerMovement(registry);
53
+ clearSortablePreviewPlacement(registry);
54
+ registry.snapshots.delete(event.draggableId);
55
+ },
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
+ sortableRegistries.set(runtime, registry);
71
+ return registry;
72
+ }
73
+ export function registerSortableElement(input) {
74
+ const existingSnapshot = input.registry.attributeSnapshots.get(input.draggableId);
75
+ const existingSnapshotElement = existingSnapshot?.elementRef.deref() ?? null;
76
+ if (!existingSnapshot || existingSnapshotElement !== input.element) {
77
+ input.registry.attributeSnapshots.set(input.draggableId, {
78
+ elementRef: new WeakRef(input.element),
79
+ sortableDraggable: input.element.getAttribute("data-dnd-sortable-draggable"),
80
+ dragged: input.element.getAttribute("data-dnd-dragged"),
81
+ });
82
+ }
83
+ input.element.setAttribute("data-dnd-sortable-draggable", "true");
84
+ 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);
89
+ input.runtime.registerDropTarget(input.draggableId, input.element, input.group, {
90
+ containerId: input.containerId ?? null,
91
+ sortable: true,
92
+ sortableAxis: normalizedOptions.axis,
93
+ });
94
+ }
95
+ export function unregisterSortableElement(input) {
96
+ if (input.draggableId !== null) {
97
+ input.runtime.unregisterDropTarget(input.draggableId, input.element ?? undefined);
98
+ const registeredElement = input.registry.elements.get(input.draggableId)?.deref() ?? null;
99
+ if (registeredElement === input.element || input.element === null) {
100
+ if (registeredElement) {
101
+ input.registry.elementIds.delete(registeredElement);
102
+ }
103
+ input.registry.elements.delete(input.draggableId);
104
+ input.registry.groups.delete(input.draggableId);
105
+ input.registry.sortableOptions.delete(input.draggableId);
106
+ }
107
+ }
108
+ if (input.element) {
109
+ restoreSortableInternalAttributes({
110
+ registry: input.registry,
111
+ draggableId: input.draggableId,
112
+ element: input.element,
113
+ });
114
+ }
115
+ }
116
+ export function restoreSortableInternalAttributes(input) {
117
+ const snapshot = input.draggableId
118
+ ? input.registry.attributeSnapshots.get(input.draggableId)
119
+ : null;
120
+ const snapshotElement = snapshot?.elementRef.deref() ?? null;
121
+ restoreAttribute(input.element, "data-dnd-sortable-draggable", snapshot && snapshotElement === input.element ? snapshot.sortableDraggable : null);
122
+ restoreAttribute(input.element, "data-dnd-dragged", snapshot && snapshotElement === input.element ? snapshot.dragged : null);
123
+ if (input.draggableId) {
124
+ input.registry.attributeSnapshots.delete(input.draggableId);
125
+ }
126
+ }
127
+ export function restoreSortableDraggedAttribute(input) {
128
+ const snapshot = input.registry.attributeSnapshots.get(input.draggableId);
129
+ const snapshotElement = snapshot?.elementRef.deref() ?? null;
130
+ restoreAttribute(input.element, "data-dnd-dragged", snapshot && snapshotElement === input.element ? snapshot.dragged : null);
131
+ }
132
+ export function getRegisteredSortableElement(registry, draggableId) {
133
+ const element = registry.elements.get(draggableId)?.deref() ?? null;
134
+ return element?.isConnected ? element : null;
135
+ }
136
+ function getSortableOptions(registry, draggableId) {
137
+ return (registry.sortableOptions.get(draggableId) ?? normalizeSortableOptions(undefined));
138
+ }
139
+ function restoreAttribute(element, attribute, value) {
140
+ if (value === null) {
141
+ element.removeAttribute(attribute);
142
+ return;
143
+ }
144
+ element.setAttribute(attribute, value);
145
+ }
@@ -0,0 +1,6 @@
1
+ import type { TargetingAlgorithm } from "./types.js";
2
+ export type BuiltInTargetingAlgorithmKind = "pointer-to-center" | "center-to-center" | "pointer-to-rect-distance";
3
+ export declare const pointerToCenter: TargetingAlgorithm;
4
+ export declare const centerToCenter: TargetingAlgorithm;
5
+ export declare const pointerToRectDistance: TargetingAlgorithm;
6
+ export declare function getBuiltInTargetingAlgorithmKind(targetingAlgorithm: TargetingAlgorithm): BuiltInTargetingAlgorithmKind | null;
@@ -0,0 +1,56 @@
1
+ import { getDistanceToRect } from "./constraints.js";
2
+ const builtInTargetingAlgorithmKinds = new WeakMap();
3
+ export const pointerToCenter = registerBuiltInTargetingAlgorithm(Object.assign(({ pointerPosition, dropTargets }) => findClosestTarget(pointerPosition, dropTargets), { mode: "pointer" }), "pointer-to-center");
4
+ export const centerToCenter = registerBuiltInTargetingAlgorithm(Object.assign(({ overlayRect, dropTargets }) => {
5
+ if (!overlayRect) {
6
+ return null;
7
+ }
8
+ return findClosestTarget(getRectCenter(overlayRect), dropTargets);
9
+ }, { mode: "rect" }), "center-to-center");
10
+ export const pointerToRectDistance = registerBuiltInTargetingAlgorithm(Object.assign(({ pointerPosition, dropTargets }) => findClosestTargetByRectDistance(pointerPosition, dropTargets), { mode: "pointer" }), "pointer-to-rect-distance");
11
+ export function getBuiltInTargetingAlgorithmKind(targetingAlgorithm) {
12
+ return builtInTargetingAlgorithmKinds.get(targetingAlgorithm) ?? null;
13
+ }
14
+ function registerBuiltInTargetingAlgorithm(targetingAlgorithm, kind) {
15
+ builtInTargetingAlgorithmKinds.set(targetingAlgorithm, kind);
16
+ return targetingAlgorithm;
17
+ }
18
+ function findClosestTarget(point, dropTargets) {
19
+ let closestTarget = null;
20
+ let closestDistance = Number.POSITIVE_INFINITY;
21
+ for (const candidateDropTarget of dropTargets) {
22
+ const distance = getSquaredDistance(point, getRectCenter(candidateDropTarget.dropTargetRect));
23
+ if (distance < closestDistance) {
24
+ closestDistance = distance;
25
+ closestTarget = candidateDropTarget;
26
+ }
27
+ }
28
+ return closestTarget;
29
+ }
30
+ function findClosestTargetByRectDistance(point, dropTargets) {
31
+ let closestTarget = null;
32
+ let closestDistance = Number.POSITIVE_INFINITY;
33
+ for (const candidateDropTarget of dropTargets) {
34
+ const distance = getSquaredRectDistance(point, candidateDropTarget.dropTargetRect);
35
+ if (distance < closestDistance) {
36
+ closestDistance = distance;
37
+ closestTarget = candidateDropTarget;
38
+ }
39
+ }
40
+ return closestTarget;
41
+ }
42
+ function getRectCenter(rect) {
43
+ return {
44
+ x: rect.left + rect.width / 2,
45
+ y: rect.top + rect.height / 2,
46
+ };
47
+ }
48
+ function getSquaredDistance(a, b) {
49
+ const deltaX = a.x - b.x;
50
+ const deltaY = a.y - b.y;
51
+ return deltaX * deltaX + deltaY * deltaY;
52
+ }
53
+ function getSquaredRectDistance(point, rect) {
54
+ const distance = getDistanceToRect(point, rect);
55
+ return distance.x * distance.x + distance.y * distance.y;
56
+ }
@@ -0,0 +1,15 @@
1
+ import type { DragPoint, DragRect } from "../geometry/rects.js";
2
+ import type { TargetingConstraint } from "./types.js";
3
+ export type RectDistance = {
4
+ x: number;
5
+ y: number;
6
+ distance: number;
7
+ };
8
+ export type MaxDistanceToRectOptions = {
9
+ maxDistance?: number;
10
+ maxXDistance?: number;
11
+ maxYDistance?: number;
12
+ };
13
+ export declare function maxPointerDistanceToRect(options: MaxDistanceToRectOptions): TargetingConstraint;
14
+ export declare function maxOverlayCenterDistanceToRect(options: MaxDistanceToRectOptions): TargetingConstraint;
15
+ export declare function getDistanceToRect(point: DragPoint, rect: DragRect): RectDistance;