@mk-drag-and-drop/dom 0.4.4 → 0.4.5

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.
package/README.md CHANGED
@@ -374,6 +374,10 @@ sortable axis: above or left of midpoint places before, while below or right of
374
374
  midpoint places after. Pointer-based targeting uses the pointer as that position;
375
375
  rect-based targeting such as `centerToCenter` uses the overlay center.
376
376
 
377
+ A container-only preview keeps that cross-container entry state through active
378
+ recomputes until the first item target in the destination list receives a
379
+ before/after placement.
380
+
377
381
  `placementBoundary` is used after a preview placement already has an established
378
382
  movement direction to control same-target reversal and hysteresis thresholds. It
379
383
  does not decide the initial side for cross-container sortable entry or delay the
@@ -388,7 +392,10 @@ Modifiers transform pointer movement before drag state updates. Built-ins:
388
392
  - `restrictToContainer(resolver)`
389
393
 
390
394
  `restrictToContainer` receives a resolver function with
391
- `DragModifierSetupInput` and returns an `HTMLElement | null`.
395
+ `DragModifierSetupInput` and returns an `HTMLElement | null`. The resolver runs
396
+ at drag setup to choose the bounds element; the selected element's rect is
397
+ remeasured during movement so growing or shrinking containers keep constraining
398
+ against current bounds.
392
399
 
393
400
  Custom modifiers can provide:
394
401
 
@@ -1,4 +1,5 @@
1
1
  import { rectToDragRect } from "../geometry/rects.js";
2
+ const restrictToContainerElements = new WeakMap();
2
3
  export function lockToXAxis() {
3
4
  return {
4
5
  transform: (input) => ({
@@ -19,33 +20,43 @@ export function restrictToContainer(getContainer) {
19
20
  return {
20
21
  setup: (input) => {
21
22
  const container = getContainer(input);
22
- return container
23
- ? rectToDragRect(container.getBoundingClientRect())
24
- : null;
23
+ if (!container) {
24
+ return null;
25
+ }
26
+ const rect = rectToDragRect(container.getBoundingClientRect());
27
+ restrictToContainerElements.set(rect, container);
28
+ return rect;
25
29
  },
26
30
  transform: (input) => {
27
31
  if (input.state === null) {
28
32
  return input.pointerPosition;
29
33
  }
34
+ const containerRect = getRestrictToContainerRect(input.state);
30
35
  return {
31
36
  x: clampPointerAxis({
32
37
  pointerPosition: input.pointerPosition.x,
33
38
  overlayStart: input.overlayRect.left,
34
39
  overlayEnd: input.overlayRect.right,
35
- containerStart: input.state.left,
36
- containerEnd: input.state.right,
40
+ containerStart: containerRect.left,
41
+ containerEnd: containerRect.right,
37
42
  }),
38
43
  y: clampPointerAxis({
39
44
  pointerPosition: input.pointerPosition.y,
40
45
  overlayStart: input.overlayRect.top,
41
46
  overlayEnd: input.overlayRect.bottom,
42
- containerStart: input.state.top,
43
- containerEnd: input.state.bottom,
47
+ containerStart: containerRect.top,
48
+ containerEnd: containerRect.bottom,
44
49
  }),
45
50
  };
46
51
  },
47
52
  };
48
53
  }
54
+ function getRestrictToContainerRect(state) {
55
+ const container = restrictToContainerElements.get(state);
56
+ return container
57
+ ? rectToDragRect(container.getBoundingClientRect())
58
+ : state;
59
+ }
49
60
  function clampPointerAxis(input) {
50
61
  const overlaySize = input.overlayEnd - input.overlayStart;
51
62
  const containerSize = input.containerEnd - input.containerStart;
@@ -235,6 +235,7 @@ export class DragRuntime {
235
235
  this.notifyDragUpdate(updateEvent, {
236
236
  ...updateEvent,
237
237
  placementPosition: sortablePlacementPosition,
238
+ movementPosition: rawPointerPosition,
238
239
  });
239
240
  }
240
241
  updatePointerNowOrRelease(rawPointerPosition) {
@@ -465,6 +466,7 @@ export class DragRuntime {
465
466
  pointerPosition: effectivePointerPosition,
466
467
  overlayRect: startOverlayRect,
467
468
  }),
469
+ movementPosition: rawPointerPosition,
468
470
  };
469
471
  this.notifyDragStartSubscriptions(dragStartSubscriptionEvent);
470
472
  this.updateOverlayHost({
@@ -7,6 +7,10 @@ export type SortablePointerMovementState = {
7
7
  x: number;
8
8
  y: number;
9
9
  };
10
+ previousPlacementPosition?: {
11
+ x: number;
12
+ y: number;
13
+ };
10
14
  };
11
15
  export type SortablePreviewPlacementState = {
12
16
  activeDropTargetId: string | null;
@@ -26,10 +30,16 @@ export declare function isSortablePreviewTarget(input: {
26
30
  export declare function initializeSortablePointerMovement(registry: SortableRegistry, pointerPosition: {
27
31
  x: number;
28
32
  y: number;
33
+ }, placementPosition?: {
34
+ x: number;
35
+ y: number;
29
36
  }): void;
30
37
  export declare function updateSortablePointerMovement(registry: SortableRegistry, pointerPosition: {
31
38
  x: number;
32
39
  y: number;
40
+ }, placementPosition?: {
41
+ x: number;
42
+ y: number;
33
43
  }): void;
34
44
  export declare function clearSortablePointerMovement(registry: SortableRegistry): void;
35
45
  export declare function clearSortablePreviewPlacement(registry: SortableRegistry): void;
@@ -42,6 +52,10 @@ export declare function moveSortablePreview(input: {
42
52
  x: number;
43
53
  y: number;
44
54
  };
55
+ movementPosition: {
56
+ x: number;
57
+ y: number;
58
+ };
45
59
  options: NormalizedSortableOptions;
46
60
  }): void;
47
61
  export declare function getSortablePreviewPlacement(input: {
@@ -51,6 +65,10 @@ export declare function getSortablePreviewPlacement(input: {
51
65
  x: number;
52
66
  y: number;
53
67
  };
68
+ movementPosition?: {
69
+ x: number;
70
+ y: number;
71
+ };
54
72
  movement: SortablePointerMovementState | null;
55
73
  previewPlacement: SortablePreviewPlacementState | null;
56
74
  options: NormalizedSortableOptions;
@@ -17,19 +17,21 @@ export function snapshotSortableElement(registry, draggableId) {
17
17
  export function isSortablePreviewTarget(input) {
18
18
  return input.activeDropTargetId !== null;
19
19
  }
20
- export function initializeSortablePointerMovement(registry, pointerPosition) {
20
+ export function initializeSortablePointerMovement(registry, pointerPosition, placementPosition = pointerPosition) {
21
21
  registry.activeDrag.pointerMovement = {
22
22
  previousPointerPosition: { ...pointerPosition },
23
+ previousPlacementPosition: { ...placementPosition },
23
24
  };
24
25
  registry.activeDrag.previewPlacement = null;
25
26
  }
26
- export function updateSortablePointerMovement(registry, pointerPosition) {
27
+ export function updateSortablePointerMovement(registry, pointerPosition, placementPosition = pointerPosition) {
27
28
  const movement = registry.activeDrag.pointerMovement;
28
29
  if (!movement) {
29
- initializeSortablePointerMovement(registry, pointerPosition);
30
+ initializeSortablePointerMovement(registry, pointerPosition, placementPosition);
30
31
  return;
31
32
  }
32
33
  movement.previousPointerPosition = { ...pointerPosition };
34
+ movement.previousPlacementPosition = { ...placementPosition };
33
35
  }
34
36
  export function clearSortablePointerMovement(registry) {
35
37
  registry.activeDrag.pointerMovement = null;
@@ -75,10 +77,17 @@ export function moveSortablePreview(input) {
75
77
  addRegistrationContainerId(measurementIds, draggedRegistration);
76
78
  addRegistrationContainerId(measurementIds, target);
77
79
  if (target.capabilities.container) {
80
+ const sourceListElement = draggedElement.parentElement;
78
81
  measurementIds.add(target.id);
79
82
  const previousListElement = input.registry.activeDrag.previewPlacement?.containerElement ?? null;
80
83
  const pendingMidpointInitialPlacement = draggedElement.parentElement !== target.element ||
81
84
  (previousListElement !== null && previousListElement !== target.element);
85
+ if (!moveSortablePreviewIntoContainer({
86
+ draggedElement,
87
+ targetElement: target.element,
88
+ })) {
89
+ return;
90
+ }
82
91
  setSortablePreviewPlacementState({
83
92
  registry: input.registry,
84
93
  activeDropTargetId: target.id,
@@ -87,17 +96,18 @@ export function moveSortablePreview(input) {
87
96
  pendingMidpointInitialPlacement,
88
97
  movementDirection: getCurrentAxisMovementDirection({
89
98
  placementPosition: input.placementPosition,
99
+ movementPosition: input.movementPosition,
90
100
  movement: input.registry.activeDrag.pointerMovement,
91
101
  axis: input.options.axis,
92
102
  }),
93
103
  });
94
- if (!moveSortablePreviewIntoContainer({
95
- draggedElement,
96
- targetElement: target.element,
97
- })) {
98
- return;
104
+ if (sourceListElement && sourceListElement !== target.element) {
105
+ addSortableMeasurementIdsInContainer(measurementIds, input.registry, sourceListElement);
106
+ addSortableMeasurementIdsInContainer(measurementIds, input.registry, target.element);
107
+ }
108
+ else {
109
+ addSortableMeasurementIdsAroundElement(measurementIds, input.registry, draggedElement);
99
110
  }
100
- addSortableMeasurementIdsAroundElement(measurementIds, input.registry, draggedElement);
101
111
  refreshSortableDropTargetMeasurements({
102
112
  registry: input.registry,
103
113
  runtime: input.runtime,
@@ -108,6 +118,7 @@ export function moveSortablePreview(input) {
108
118
  }
109
119
  const targetElement = target.element;
110
120
  const listElement = targetElement.parentElement;
121
+ const sourceListElement = draggedElement.parentElement;
111
122
  if (!listElement) {
112
123
  return;
113
124
  }
@@ -129,6 +140,7 @@ export function moveSortablePreview(input) {
129
140
  activeDropTargetId,
130
141
  targetElement,
131
142
  placementPosition: input.placementPosition,
143
+ movementPosition: input.movementPosition,
132
144
  movement: input.registry.activeDrag.pointerMovement,
133
145
  previewPlacement: input.registry.activeDrag.previewPlacement,
134
146
  options: input.options,
@@ -156,8 +168,14 @@ export function moveSortablePreview(input) {
156
168
  else {
157
169
  targetElement.before(draggedElement);
158
170
  }
159
- addSortableMeasurementIdsAroundElement(measurementIds, input.registry, draggedElement);
160
- addSortableMeasurementIdsAroundElement(measurementIds, input.registry, targetElement);
171
+ if (sourceListElement && sourceListElement !== listElement) {
172
+ addSortableMeasurementIdsInContainer(measurementIds, input.registry, sourceListElement);
173
+ addSortableMeasurementIdsInContainer(measurementIds, input.registry, listElement);
174
+ }
175
+ else {
176
+ addSortableMeasurementIdsAroundElement(measurementIds, input.registry, draggedElement);
177
+ addSortableMeasurementIdsAroundElement(measurementIds, input.registry, targetElement);
178
+ }
161
179
  refreshSortableDropTargetMeasurements({
162
180
  registry: input.registry,
163
181
  runtime: input.runtime,
@@ -171,14 +189,14 @@ export function getSortablePreviewPlacement(input) {
171
189
  const targetRect = input.targetElement.getBoundingClientRect();
172
190
  const currentDirection = getCurrentAxisMovementDirection({
173
191
  placementPosition: input.placementPosition,
192
+ movementPosition: input.movementPosition,
174
193
  movement: input.movement,
175
194
  axis,
176
195
  });
177
196
  const previousPlacement = input.previewPlacement;
178
197
  if (!previousPlacement ||
179
198
  previousPlacement.activeDropTargetId !== input.activeDropTargetId ||
180
- previousPlacement.placement === null ||
181
- previousPlacement.movementDirection === "none") {
199
+ previousPlacement.placement === null) {
182
200
  if (input.useMidpointInitialPlacement) {
183
201
  const placement = getPlacementSideFromTargetMidpoint({
184
202
  axis,
@@ -200,6 +218,32 @@ export function getSortablePreviewPlacement(input) {
200
218
  movementDirection: currentDirection,
201
219
  };
202
220
  }
221
+ if (previousPlacement.movementDirection === "none") {
222
+ if (currentDirection === "none") {
223
+ return {
224
+ placement: previousPlacement.placement,
225
+ movementDirection: previousPlacement.movementDirection,
226
+ };
227
+ }
228
+ const boundary = getPlacementBoundary({
229
+ targetRect,
230
+ axis,
231
+ direction: currentDirection,
232
+ startRatio: input.options.placementBoundary.start,
233
+ endRatio: input.options.placementBoundary.end,
234
+ });
235
+ const reversalPlacement = getPlacementSideFromBoundary(axisPosition, boundary);
236
+ if (reversalPlacement === previousPlacement.placement) {
237
+ return {
238
+ placement: previousPlacement.placement,
239
+ movementDirection: previousPlacement.movementDirection,
240
+ };
241
+ }
242
+ return {
243
+ placement: reversalPlacement,
244
+ movementDirection: currentDirection,
245
+ };
246
+ }
203
247
  if (currentDirection === "none" ||
204
248
  currentDirection === previousPlacement.movementDirection) {
205
249
  return {
@@ -257,11 +301,16 @@ function getPlacementSideFromTargetMidpoint(input) {
257
301
  return input.axisPosition < midpoint ? "before" : "after";
258
302
  }
259
303
  function getCurrentAxisMovementDirection(input) {
260
- const axisPosition = getAxisPosition(input.placementPosition, input.axis);
261
- const previousAxisPosition = input.movement
262
- ? getAxisPosition(input.movement.previousPointerPosition, input.axis)
263
- : axisPosition;
264
- return getAxisMovementDirection(previousAxisPosition, axisPosition);
304
+ const movementPosition = input.movementPosition ?? input.placementPosition;
305
+ const previousMovementPosition = input.movement?.previousPointerPosition ?? movementPosition;
306
+ const inputDirection = getAxisMovementDirection(getAxisPosition(previousMovementPosition, input.axis), getAxisPosition(movementPosition, input.axis));
307
+ if (inputDirection === "none") {
308
+ return "none";
309
+ }
310
+ const previousPlacementPosition = input.movement?.previousPlacementPosition ??
311
+ input.movement?.previousPointerPosition ??
312
+ input.placementPosition;
313
+ return getAxisMovementDirection(getAxisPosition(previousPlacementPosition, input.axis), getAxisPosition(input.placementPosition, input.axis));
265
314
  }
266
315
  export function isSortablePreviewAlreadyPlaced(input) {
267
316
  return input.placement === "before"
@@ -308,6 +357,11 @@ function addSortableMeasurementIdsAroundElement(dropTargetIds, registry, element
308
357
  addSortableMeasurementId(dropTargetIds, registry, element.previousElementSibling);
309
358
  addSortableMeasurementId(dropTargetIds, registry, element.nextElementSibling);
310
359
  }
360
+ function addSortableMeasurementIdsInContainer(dropTargetIds, registry, containerElement) {
361
+ for (let index = 0; index < containerElement.children.length; index += 1) {
362
+ addSortableMeasurementId(dropTargetIds, registry, containerElement.children.item(index));
363
+ }
364
+ }
311
365
  function addSortableMeasurementId(dropTargetIds, registry, element) {
312
366
  if (!(element instanceof HTMLElement)) {
313
367
  return;
@@ -32,6 +32,10 @@ export type DomSortableRuntime = DomDraggableRuntime & {
32
32
  x: number;
33
33
  y: number;
34
34
  };
35
+ movementPosition?: {
36
+ x: number;
37
+ y: number;
38
+ };
35
39
  }) => void;
36
40
  onDragUpdate?: (event: {
37
41
  draggableId: string;
@@ -44,6 +48,10 @@ export type DomSortableRuntime = DomDraggableRuntime & {
44
48
  x: number;
45
49
  y: number;
46
50
  };
51
+ movementPosition?: {
52
+ x: number;
53
+ y: number;
54
+ };
47
55
  activeDropTargetId: string | null;
48
56
  previousDropTargetId: string | null;
49
57
  }) => void;
@@ -22,26 +22,30 @@ export function getSortableRegistry(runtime) {
22
22
  };
23
23
  runtime.subscribe({
24
24
  onDragStart: (event) => {
25
- initializeSortablePointerMovement(registry, event.placementPosition ?? event.pointerPosition);
25
+ const placementPosition = event.placementPosition ?? event.pointerPosition;
26
+ initializeSortablePointerMovement(registry, event.movementPosition ?? placementPosition, placementPosition);
26
27
  snapshotSortableElement(registry, event.draggableId);
27
28
  },
28
29
  onDragUpdate: (event) => {
29
30
  if (isSortablePreviewTarget({
30
31
  activeDropTargetId: event.activeDropTargetId,
31
32
  })) {
33
+ const placementPosition = event.placementPosition ?? event.pointerPosition;
32
34
  moveSortablePreview({
33
35
  registry,
34
36
  runtime,
35
37
  draggedDraggableId: event.draggableId,
36
38
  activeDropTargetId: event.activeDropTargetId,
37
- placementPosition: event.placementPosition ?? event.pointerPosition,
39
+ placementPosition,
40
+ movementPosition: event.movementPosition ?? placementPosition,
38
41
  options: getSortableOptions(registry, event.draggableId),
39
42
  });
40
43
  }
41
44
  else {
42
45
  clearSortablePreviewPlacement(registry);
43
46
  }
44
- updateSortablePointerMovement(registry, event.placementPosition ?? event.pointerPosition);
47
+ const placementPosition = event.placementPosition ?? event.pointerPosition;
48
+ updateSortablePointerMovement(registry, event.movementPosition ?? placementPosition, placementPosition);
45
49
  },
46
50
  onDragEnd: (event) => {
47
51
  releaseSortableActiveDragState(registry, event.draggableId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mk-drag-and-drop/dom",
3
- "version": "0.4.4",
3
+ "version": "0.4.5",
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",