@mk-drag-and-drop/dom 0.2.0 → 0.3.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.
package/README.md CHANGED
@@ -36,6 +36,8 @@ The root export includes:
36
36
  - `createDropContainer`
37
37
  - `createSortable`
38
38
  - `createDragHandle`
39
+ - controller types: `DragController`, `DragControllerOptions`,
40
+ `DragControllerOverlayInput`, `DragControllerAnnouncements`
39
41
  - lifecycle types: `DragStartEvent`, `DragUpdateEvent`, `DragEndEvent`,
40
42
  `DropEvent`, `DragSource`, `DragEndResult`, `DragLifecycleCallbacks`,
41
43
  `DragLifecycleHelpers`
@@ -94,6 +96,8 @@ The returned controller has:
94
96
 
95
97
  - `remeasureDropTargets(input?)`: remeasures all targets, one target id, an
96
98
  array of target ids, or `{ group }`
99
+ - `remeasureOverlay()`: remeasures the currently mounted drag overlay element;
100
+ it is a safe no-op when no overlay is mounted or no drag is active
97
101
 
98
102
  The controller does not expose runtime teardown, disposal, update, or broad
99
103
  overlay removal methods. Runtime objects are ordinary JavaScript objects; DOM
@@ -108,6 +112,14 @@ controller calls `dragOverlay` once more for the `"released"` phase and passes
108
112
  `removeOverlay`; call that function when app-owned release animation is done.
109
113
  Pointer movement does not call `dragOverlay`.
110
114
 
115
+ The overlay input includes:
116
+
117
+ - `dragState`: item id, group, source rect, start pointer, and current pointer.
118
+ - `phase`: `"dragging"` or `"released"`.
119
+ - `remeasureOverlay`: manually refreshes the cached overlay measurement.
120
+ - `removeOverlay`: present only for `"released"` overlays in manual release
121
+ mode; call it when app-owned release animation should remove the overlay.
122
+
111
123
  `removeOverlay` is only present for released overlays in manual release mode.
112
124
  Calling it more than once is safe. It removes the overlay host state only; it is
113
125
  not a controller or runtime teardown API.
@@ -121,7 +133,31 @@ from the current pointer delta.
121
133
  The controller measures the returned overlay element on mount/replacement. When
122
134
  `ResizeObserver` is available, it also remeasures when that element changes
123
135
  size. The runtime derives the current overlay rect from that cached measurement
124
- plus pointer movement.
136
+ plus modified pointer movement, and it does not measure overlay DOM on every
137
+ pointer move.
138
+
139
+ Manual `remeasureOverlay()` is for cases automatic observation cannot cover or
140
+ where the app knows visual geometry changed. CSS transforms can change visual
141
+ bounds without necessarily triggering `ResizeObserver`.
142
+
143
+ ```ts
144
+ controller.remeasureOverlay();
145
+ ```
146
+
147
+ ```ts
148
+ const controller = createDragController({
149
+ dragOverlay({ dragState, remeasureOverlay }) {
150
+ const element = document.createElement("div");
151
+ element.textContent = dragState.draggableId;
152
+
153
+ element.addEventListener("transitionend", () => {
154
+ remeasureOverlay();
155
+ });
156
+
157
+ return element;
158
+ },
159
+ });
160
+ ```
125
161
 
126
162
  For dynamic overlay content, keep a reference to the element and update it from
127
163
  lifecycle callbacks or subscriptions:
@@ -164,6 +200,7 @@ type DragUpdateEvent = {
164
200
  draggableId: string;
165
201
  source: DragSource;
166
202
  pointerPosition: DragPoint;
203
+ overlayRect: DragRect | null;
167
204
  activeDropTargetId: string | null;
168
205
  previousDropTargetId: string | null;
169
206
  };
@@ -181,6 +218,7 @@ type DragEndEvent = {
181
218
  source: DragSource;
182
219
  result: DragEndResult;
183
220
  dropTargetId: string | null;
221
+ overlayRect: DragRect | null;
184
222
  };
185
223
 
186
224
  type DropEvent = {
@@ -200,6 +238,14 @@ type SortableDropPlacement = {
200
238
  };
201
239
  ```
202
240
 
241
+ `DragUpdateEvent.overlayRect` is the current viewport-space rectangle for the
242
+ drag overlay. `DragEndEvent.overlayRect` is the final viewport-space rectangle
243
+ at the moment the drag ends. Both are `null` when no drag overlay is configured.
244
+ When an overlay is configured, the runtime uses cached overlay measurement plus
245
+ modified pointer movement. Before overlay content has been measured, it uses the
246
+ same translated source-rect fallback as the visual overlay. The drag update and
247
+ drag end paths do not read overlay DOM just to produce this field.
248
+
203
249
  Helpers:
204
250
 
205
251
  - `getDropTargetRect(dropTargetId)`
@@ -339,8 +385,14 @@ modifier state.
339
385
  ## Measurement
340
386
 
341
387
  Targets are measured when registered and remeasured at drag start. The explicit
342
- remeasurement entry point is `controller.remeasureDropTargets(input?)`; call it
343
- when app-owned layout changes during a drag should affect targeting.
388
+ target remeasurement entry point is `controller.remeasureDropTargets(input?)`;
389
+ call it when app-owned layout changes during a drag should affect targeting.
390
+
391
+ Overlay measurement is separate from target measurement. Overlay content is
392
+ measured on mount/replacement and by `ResizeObserver` when available. Use
393
+ `controller.remeasureOverlay()` when the current overlay geometry should be
394
+ refreshed manually, such as after an app-owned transition or transform that
395
+ changes visual bounds without a normal size-observer notification.
344
396
 
345
397
  `RemeasureDropTargetsInput` accepts:
346
398
 
@@ -13,10 +13,12 @@ export type DragControllerAnnouncements = {
13
13
  export type DragControllerOverlayInput = {
14
14
  dragState: DragState;
15
15
  phase: "dragging";
16
+ remeasureOverlay: () => void;
16
17
  removeOverlay?: never;
17
18
  } | {
18
19
  dragState: DragState;
19
20
  phase: "released";
21
+ remeasureOverlay: () => void;
20
22
  removeOverlay: () => void;
21
23
  };
22
24
  export type DragControllerOptions = {
@@ -32,5 +34,6 @@ export type DragControllerOptions = {
32
34
  } & DragLifecycleCallbacks;
33
35
  export type DragController = {
34
36
  remeasureDropTargets: (input?: RemeasureDropTargetsInput) => void;
37
+ remeasureOverlay: () => void;
35
38
  };
36
39
  export declare function createDragController(options?: DragControllerOptions): DragController;
@@ -22,6 +22,7 @@ export function createDragController(options = {}) {
22
22
  remeasureDropTargets: (input) => {
23
23
  runtime.remeasureDropTargets(input);
24
24
  },
25
+ remeasureOverlay,
25
26
  };
26
27
  setControllerRuntime(controller, runtime);
27
28
  return controller;
@@ -124,6 +125,9 @@ export function createDragController(options = {}) {
124
125
  }
125
126
  runtime.setOverlayRect(rectToDragRect(overlayElement.getBoundingClientRect()));
126
127
  }
128
+ function remeasureOverlay() {
129
+ measureOverlayElement();
130
+ }
127
131
  function disconnectOverlayResizeObserver() {
128
132
  overlayResizeObserver?.disconnect();
129
133
  overlayResizeObserver = null;
@@ -149,12 +153,14 @@ export function createDragController(options = {}) {
149
153
  return {
150
154
  dragState: overlayState.dragState,
151
155
  phase: "released",
156
+ remeasureOverlay,
152
157
  removeOverlay: clearOverlayHost,
153
158
  };
154
159
  }
155
160
  return {
156
161
  dragState: overlayState.dragState,
157
162
  phase: "dragging",
163
+ remeasureOverlay,
158
164
  };
159
165
  }
160
166
  function syncLiveRegion() {
@@ -221,6 +221,7 @@ export class DragRuntime {
221
221
  draggableId: nextSession.draggableId,
222
222
  source: nextSession.source,
223
223
  pointerPosition,
224
+ overlayRect,
224
225
  activeDropTargetId: nextSession.activeDropTargetId,
225
226
  previousDropTargetId,
226
227
  };
@@ -499,6 +500,9 @@ export class DragRuntime {
499
500
  ? "dropped"
500
501
  : "invalid-target"
501
502
  : null;
503
+ const overlayRect = session && this.hasDragOverlay
504
+ ? this.getCurrentDragRectAt(session.pointerPosition)
505
+ : null;
502
506
  const sortablePlacement = session && dropTargetId
503
507
  ? this.getDropEventSortablePlacement(session, dropTargetId)
504
508
  : undefined;
@@ -525,6 +529,7 @@ export class DragRuntime {
525
529
  draggableId: session.draggableId,
526
530
  source: session.source,
527
531
  result,
532
+ overlayRect,
528
533
  dropTargetId,
529
534
  });
530
535
  if (result === "dropped" && dropTargetId) {
@@ -12,6 +12,7 @@ export type DragUpdateEvent = {
12
12
  draggableId: string;
13
13
  source: DragSource;
14
14
  pointerPosition: DragPoint;
15
+ overlayRect: DragRect | null;
15
16
  activeDropTargetId: string | null;
16
17
  previousDropTargetId: string | null;
17
18
  };
@@ -19,6 +20,7 @@ export type DragEndEvent = {
19
20
  draggableId: string;
20
21
  source: DragSource;
21
22
  result: DragEndResult;
23
+ overlayRect: DragRect | null;
22
24
  dropTargetId: string | null;
23
25
  };
24
26
  export type DropEvent = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mk-drag-and-drop/dom",
3
- "version": "0.2.0",
3
+ "version": "0.3.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",
@@ -41,6 +41,7 @@
41
41
  "files": [
42
42
  "dist"
43
43
  ],
44
+ "sideEffects": false,
44
45
  "devDependencies": {
45
46
  "eslint": "^8.57.0",
46
47
  "jsdom": "^24.1.3",