@mk-drag-and-drop/dom 0.3.0 → 0.4.1

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
@@ -98,6 +98,9 @@ The returned controller has:
98
98
  array of target ids, or `{ group }`
99
99
  - `remeasureOverlay()`: remeasures the currently mounted drag overlay element;
100
100
  it is a safe no-op when no overlay is mounted or no drag is active
101
+ - `recomputeActiveDrag()`: reruns active targeting and drag update from the
102
+ last pointer position; it is a safe no-op when no drag is active and does not
103
+ remeasure drop targets
101
104
 
102
105
  The controller does not expose runtime teardown, disposal, update, or broad
103
106
  overlay removal methods. Runtime objects are ordinary JavaScript objects; DOM
@@ -362,6 +365,17 @@ registry may narrow measured candidates to relevant sortable item, neighbor, or
362
365
  container entries before the targeting algorithm runs; the algorithm still makes
363
366
  the active-target decision from that narrowed measured list.
364
367
 
368
+ Same-container sortable preview keeps its movement-responsive first-placement
369
+ behavior: forward movement places after a newly active target, backward movement
370
+ places before, and no sortable-axis movement uses the target midpoint. When a
371
+ sortable item first enters a different DOM container, the initial side is based
372
+ on the target midpoint on the sortable axis: above or left of midpoint places
373
+ before, while below or right of midpoint places after.
374
+
375
+ `placementBoundary` is used after a preview placement already exists to control
376
+ same-target reversal and hysteresis thresholds. It does not decide the initial
377
+ side for cross-container sortable entry.
378
+
365
379
  ## Modifiers
366
380
 
367
381
  Modifiers transform pointer movement before drag state updates. Built-ins:
@@ -408,6 +422,36 @@ order, so app-owned layout changes such as expanding tree rows or changing
408
422
  grouped sections during a drag should call the public remeasure API when those
409
423
  changes need to affect targeting.
410
424
 
425
+ ## Active Drag Recompute
426
+
427
+ `controller.recomputeActiveDrag()` reruns the active drag update path from the
428
+ last stored raw pointer position. It reapplies modifiers, derives the overlay
429
+ rect from cached overlay measurement, recomputes the active drop target from
430
+ cached target measurements, updates the overlay host, and sends the normal drag
431
+ update notifications. This is the same update path used by pointer movement.
432
+ Calling it with no active drag is safe and does nothing.
433
+
434
+ Use it when something external changes what is under the pointer during an
435
+ active drag without producing a new pointer event.
436
+
437
+ ```ts
438
+ controller.recomputeActiveDrag();
439
+ ```
440
+
441
+ Recompute does not remeasure drop targets. If target geometry or registrations
442
+ changed and those changes should affect targeting, remeasure first and then
443
+ recompute.
444
+
445
+ ```ts
446
+ controller.remeasureDropTargets({ group: "items" });
447
+ controller.recomputeActiveDrag();
448
+ ```
449
+
450
+ ```ts
451
+ // Example: after external scrolling changes what is under the pointer
452
+ controller.recomputeActiveDrag();
453
+ ```
454
+
411
455
  ## Data Ownership
412
456
 
413
457
  The DOM package never owns application data. Item ids, target ids, group ids,
@@ -35,5 +35,6 @@ export type DragControllerOptions = {
35
35
  export type DragController = {
36
36
  remeasureDropTargets: (input?: RemeasureDropTargetsInput) => void;
37
37
  remeasureOverlay: () => void;
38
+ recomputeActiveDrag: () => void;
38
39
  };
39
40
  export declare function createDragController(options?: DragControllerOptions): DragController;
@@ -23,6 +23,9 @@ export function createDragController(options = {}) {
23
23
  runtime.remeasureDropTargets(input);
24
24
  },
25
25
  remeasureOverlay,
26
+ recomputeActiveDrag: () => {
27
+ runtime.recomputeActiveDrag();
28
+ },
26
29
  };
27
30
  setControllerRuntime(controller, runtime);
28
31
  return controller;
@@ -11,6 +11,7 @@ export type DragRuntimeScope = DomDraggableRuntime & DomDroppableRuntime & DomDr
11
11
  configure: (input: DragRuntimeScopeConfigureInput) => void;
12
12
  cancelDrag: () => void;
13
13
  releaseActiveDragResources: () => void;
14
+ recomputeActiveDrag: () => void;
14
15
  setOverlayRect: (overlayRect: DragRect | null) => void;
15
16
  };
16
17
  export type InternalStaleDomBindingRuntime = {
@@ -5,6 +5,7 @@ export function createDragRuntimeScope(options) {
5
5
  configure: (input) => runtime.configure(input),
6
6
  cancelDrag: () => runtime.cancelDrag(),
7
7
  releaseActiveDragResources: () => runtime.releaseActiveDragResources(),
8
+ recomputeActiveDrag: () => runtime.recomputeActiveDrag(),
8
9
  setOverlayRect: (overlayRect) => runtime.setOverlayRect(overlayRect),
9
10
  requestDragStart: (input) => runtime.requestDragStart(input),
10
11
  isKeyboardDragEnabled: () => runtime.isKeyboardDragEnabled(),
@@ -43,6 +43,7 @@ export declare class DragRuntime {
43
43
  requestKeyboardDragStart(input: RequestKeyboardDragStartInput): void;
44
44
  moveKeyboardDrag(direction: KeyboardMoveDirection): void;
45
45
  updatePointer(rawPointerPosition: Point): void;
46
+ recomputeActiveDrag(): void;
46
47
  private updatePointerNow;
47
48
  private updatePointerNowOrRelease;
48
49
  private finishDragAfterQueuedPointerUpdate;
@@ -180,6 +180,13 @@ export class DragRuntime {
180
180
  }
181
181
  });
182
182
  }
183
+ recomputeActiveDrag() {
184
+ const session = this.getDraggingSession();
185
+ if (!session) {
186
+ return;
187
+ }
188
+ this.updatePointerNowOrRelease(session.rawPointerPosition);
189
+ }
183
190
  updatePointerNow(rawPointerPosition) {
184
191
  const session = this.getDraggingSession();
185
192
  if (!session) {
@@ -56,6 +56,7 @@ export declare function getSortablePreviewPlacement(input: {
56
56
  movement: SortablePointerMovementState | null;
57
57
  previewPlacement: SortablePreviewPlacementState | null;
58
58
  options: NormalizedSortableOptions;
59
+ useMidpointInitialPlacement?: boolean;
59
60
  }): SortablePreviewPlacementDecision;
60
61
  export declare function isSortablePreviewAlreadyPlaced(input: {
61
62
  draggedElement: HTMLElement;
@@ -106,6 +106,7 @@ export function moveSortablePreview(input) {
106
106
  if (!listElement) {
107
107
  return;
108
108
  }
109
+ const useMidpointInitialPlacement = draggedElement.parentElement !== listElement;
109
110
  if (!target.capabilities.sortable) {
110
111
  return;
111
112
  }
@@ -120,6 +121,7 @@ export function moveSortablePreview(input) {
120
121
  movement: input.registry.activeDrag.pointerMovement,
121
122
  previewPlacement: input.registry.activeDrag.previewPlacement,
122
123
  options: input.options,
124
+ useMidpointInitialPlacement,
123
125
  });
124
126
  const placement = placementDecision.placement;
125
127
  setSortablePreviewPlacementState({
@@ -165,12 +167,18 @@ export function getSortablePreviewPlacement(input) {
165
167
  previousPlacement.placement === null ||
166
168
  previousPlacement.movementDirection === "none") {
167
169
  return {
168
- placement: getOptimisticPlacement({
169
- axis,
170
- axisPosition,
171
- currentDirection,
172
- targetRect,
173
- }),
170
+ placement: input.useMidpointInitialPlacement
171
+ ? getPlacementSideFromTargetMidpoint({
172
+ axis,
173
+ axisPosition,
174
+ targetRect,
175
+ })
176
+ : getOptimisticPlacement({
177
+ axis,
178
+ axisPosition,
179
+ currentDirection,
180
+ targetRect,
181
+ }),
174
182
  movementDirection: currentDirection,
175
183
  };
176
184
  }
@@ -222,6 +230,12 @@ function getOptimisticPlacement(input) {
222
230
  endRatio: neutralPlacementBoundaryRatio,
223
231
  }));
224
232
  }
233
+ function getPlacementSideFromTargetMidpoint(input) {
234
+ const targetStart = input.axis === "horizontal" ? input.targetRect.left : input.targetRect.top;
235
+ const targetSize = input.axis === "horizontal" ? input.targetRect.width : input.targetRect.height;
236
+ const midpoint = targetStart + targetSize / 2;
237
+ return input.axisPosition < midpoint ? "before" : "after";
238
+ }
225
239
  function getCurrentAxisMovementDirection(input) {
226
240
  const axisPosition = getAxisPosition(input.placementPosition, input.axis);
227
241
  const previousAxisPosition = input.movement
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mk-drag-and-drop/dom",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
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",