dnd-manager 1.0.0 → 1.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.
package/LICENSE CHANGED
File without changes
package/README.md CHANGED
@@ -1,14 +1,99 @@
1
1
  # dnd-manager
2
2
 
3
- Simple, performant, and data-driven Drag-and-Drop library for vanilla JS, and compatible with any framework.
3
+ Simple, performant, and data-driven Drag-and-Drop library for vanilla JS, compatible with any
4
+ framework, and designed for developers who want full control over drag-and-drop UX without being
5
+ locked into specific component patterns or opinionated libraries.
4
6
 
5
- This document provides practical examples of using `DragDropManager` in both vanilla
6
- JavaScript and React applications.
7
+ This document provides practical examples of using the library in both vanilla JavaScript and React
8
+ applications.
7
9
 
8
10
  ## Table of Contents
9
11
 
10
- 1. [Vanilla JavaScript Example](#vanilla-javascript-example)
11
- 2. [React Example](#react-example)
12
+ 1. [Installation](#installation)
13
+ 2. [Clone Preview Helper](#clone-preview-helper)
14
+ 3. [Vanilla JavaScript Example](#vanilla-javascript-example)
15
+ 4. [React Example](#react-example)
16
+
17
+ ---
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ pnpm add dnd-manager
23
+ ```
24
+
25
+ Or with npm/yarn:
26
+
27
+ ```bash
28
+ npm install dnd-manager
29
+ yarn add dnd-manager
30
+ ```
31
+
32
+ ---
33
+
34
+ ## Clone Preview Helper
35
+
36
+ Use `new DragPreviewController()` to get a clone of the dragged element that follows the cursor with
37
+ `position: fixed`, `pointer-events: none`, and transform-based movement.
38
+
39
+ ```typescript
40
+ import {
41
+ DragPreviewController,
42
+ DragDropManager,
43
+ type DragDropCallbacks,
44
+ type PointerPosition,
45
+ } from 'dnd-manager'
46
+
47
+ type Item = { index: number }
48
+ type Position = { index: number }
49
+
50
+ const preview = new DragPreviewController({
51
+ zIndex: 9999,
52
+ opacity: 0.95,
53
+ className: 'drop-shadow-xl',
54
+ })
55
+
56
+ const callbacks: DragDropCallbacks<Item, Position> = {
57
+ getItemPosition: (element) => {
58
+ const raw = element.dataset.index
59
+ if (!raw) return null
60
+ const index = Number.parseInt(raw, 10)
61
+ return Number.isFinite(index) ? { index } : null
62
+ },
63
+
64
+ getItemData: (_element, pos) => ({ index: pos.index }),
65
+
66
+ onDragStart: (element) => {
67
+ preview.startFromElement(element)
68
+ },
69
+
70
+ onDragMove: (pos: PointerPosition) => {
71
+ preview.moveToPointer(pos)
72
+ },
73
+
74
+ onDragEnd: () => {
75
+ preview.stop()
76
+ },
77
+ }
78
+
79
+ const manager = new DragDropManager<Item, Position>(
80
+ '#container',
81
+ {
82
+ draggableKind: 'ITEM',
83
+ droppableKind: 'ITEM',
84
+ },
85
+ callbacks,
86
+ )
87
+
88
+ // Important cleanup for unmount/page teardown.
89
+ function cleanup() {
90
+ preview.destroy()
91
+ manager.destroy()
92
+ }
93
+ ```
94
+
95
+ This helper uses the clone-in-`document.body` pattern to avoid drift in scrolled containers and
96
+ keeps cursor tracking smooth via transform updates.
12
97
 
13
98
  ---
14
99
 
@@ -199,7 +284,7 @@ data to the console, and dragging shows a preview element following the cursor.
199
284
  **TypeScript (app.ts):**
200
285
 
201
286
  ```typescript
202
- import { DragDropManager, type DragDropCallbacks } from './drag-drop-manager'
287
+ import { DragDropManager, type DragDropCallbacks } from 'dnd-manager'
203
288
 
204
289
  // Types
205
290
  type GridItem = {
@@ -222,11 +307,6 @@ function initDragDropGrid(): void {
222
307
 
223
308
  // Setup DragDropManager callbacks
224
309
  const callbacks: DragDropCallbacks<GridItem, GridPosition> = {
225
- canDrag: (element, position) => {
226
- // Can only drag non-empty cells
227
- return element.dataset.empty === undefined
228
- },
229
-
230
310
  getItemPosition: (element, kind) => {
231
311
  const row = element.dataset.row
232
312
  const col = element.dataset.col
@@ -343,6 +423,8 @@ function initDragDropGrid(): void {
343
423
  clickThreshold: 10,
344
424
  scrollThreshold: 100,
345
425
  scrollSpeed: 10,
426
+ cancelOnEscape: true,
427
+ cancelOnPointerLeave: true,
346
428
  },
347
429
  callbacks,
348
430
  )
@@ -365,7 +447,7 @@ This example shows the same draggable grid implemented as a React component with
365
447
 
366
448
  ```typescript
367
449
  import { useEffect, useRef, useState } from 'react'
368
- import { DragDropManager, type DragDropCallbacks } from './drag-drop-manager'
450
+ import { DragDropManager, type DragDropCallbacks } from 'dnd-manager'
369
451
 
370
452
  // Types
371
453
  type GridItem = {
@@ -479,10 +561,6 @@ export function DraggableGrid() {
479
561
 
480
562
  // Setup callbacks
481
563
  const callbacks: DragDropCallbacks<GridItem, GridPosition> = {
482
- canDrag: (element, position) => {
483
- return element.dataset.empty === undefined
484
- },
485
-
486
564
  getItemPosition: (element, kind) => {
487
565
  const row = element.dataset.row
488
566
  const col = element.dataset.col
@@ -557,6 +635,8 @@ export function DraggableGrid() {
557
635
  clickThreshold: 10,
558
636
  scrollThreshold: 100,
559
637
  scrollSpeed: 10,
638
+ cancelOnEscape: true,
639
+ cancelOnPointerLeave: true,
560
640
  },
561
641
  callbacks,
562
642
  )
@@ -741,7 +821,7 @@ function DragPreview({ item, position, width, height }: DragPreviewProps) {
741
821
 
742
822
  ### DragDropManager Features Used:
743
823
 
744
- - `canDrag` - Prevents dragging empty cells
824
+ - `data-empty` convention - Elements with `data-empty` are automatically treated as non-draggable
745
825
  - `getItemPosition` - Extracts grid coordinates from data attributes
746
826
  - `getItemData` - Retrieves item data from grid state
747
827
  - `onDragStart` - Shows preview and updates source appearance
@@ -753,10 +833,14 @@ function DragPreview({ item, position, width, height }: DragPreviewProps) {
753
833
 
754
834
  ### Configuration:
755
835
 
836
+ - `draggableKind` / `droppableKind` - Accept `string` or `string[]` to match one or many kinds. Each
837
+ element should still expose a single `data-kind` value.
756
838
  - `dragThreshold: 10` - Prevents accidental drags
757
839
  - `clickThreshold: 10` - Distinguishes clicks from drags
758
840
  - `scrollThreshold: 100` - Auto-scroll near viewport edges
759
841
  - `scrollSpeed: 10` - Smooth scroll speed
842
+ - `cancelOnEscape: true` - Cancels active drag when Escape is pressed
843
+ - `cancelOnPointerLeave: true` - Cancels active drag if pointer leaves the window
760
844
 
761
845
  ---
762
846
 
package/dist/index.d.mts CHANGED
@@ -47,23 +47,40 @@ type DragDropConfig = {
47
47
  */
48
48
  scrollSpeed?: number;
49
49
  /**
50
- * The `data-kind` attribute value for draggable elements.
51
- * Elements with this attribute can be picked up and dragged.
50
+ * Cancel the current drag operation when Escape is pressed.
51
+ * Useful for explicit keyboard cancellation.
52
+ * @default true
53
+ */
54
+ cancelOnEscape?: boolean;
55
+ /**
56
+ * Cancel the current drag operation when the pointer leaves the browser window.
57
+ * Prevents "stuck dragging" when pointerup happens outside the app/window.
58
+ * @default true
59
+ */
60
+ cancelOnPointerLeave?: boolean;
61
+ /**
62
+ * The `data-kind` value(s) for draggable elements.
63
+ * Can be a single kind or multiple kinds.
64
+ * Each draggable element should still expose a single `data-kind` value.
52
65
  * @example
53
66
  * draggableKind: 'cell' // <div data-kind="cell">
67
+ * draggableKind: ['quest', 'bounty'] // <div data-kind="quest">
54
68
  * draggableKind: 'card' // <div data-kind="card">
55
69
  * draggableKind: 'list-item' // <div data-kind="list-item">
56
70
  */
57
- draggableKind: string;
71
+ draggableKind: string | string[];
58
72
  /**
59
- * The `data-kind` attribute value for droppable target elements.
60
- * Elements with this attribute can receive dropped items.
73
+ * The `data-kind` value(s) for droppable target elements.
74
+ * Can be a single kind or multiple kinds.
75
+ * Elements with matching kinds can receive dropped items.
76
+ * Each droppable element should still expose a single `data-kind` value.
61
77
  * Often the same as `draggableKind` for reorderable lists.
62
78
  * @example
63
79
  * droppableKind: 'cell' // Can drop on cells
80
+ * droppableKind: ['quest', 'bounty'] // Can drop on either kind
64
81
  * droppableKind: 'zone' // Can drop on zones
65
82
  */
66
- droppableKind: string;
83
+ droppableKind: string | string[];
67
84
  };
68
85
  type PointerPosition = {
69
86
  x: number;
@@ -84,12 +101,15 @@ type DragState<TItem, TPosition> = {
84
101
  sourceElement: HTMLElement | null;
85
102
  sourcePosition: TPosition | null;
86
103
  sourceItem: TItem | null;
104
+ activePointerId: number | null;
87
105
  isDragging: boolean;
88
106
  lastHoveredElement: HTMLElement | null;
89
107
  };
90
108
  declare class DragDropManager<TItem = unknown, TPosition = unknown> {
91
109
  private container;
92
110
  private config;
111
+ private draggableKinds;
112
+ private droppableKinds;
93
113
  private callbacks;
94
114
  private state;
95
115
  private rafId;
@@ -103,12 +123,17 @@ declare class DragDropManager<TItem = unknown, TPosition = unknown> {
103
123
  private handlePointerMove;
104
124
  private handlePointerUp;
105
125
  private handlePointerCancel;
126
+ private handleKeyDown;
127
+ private handleMouseOut;
106
128
  private handleAutoScroll;
107
129
  private cleanup;
130
+ private getPointerId;
131
+ private isActivePointer;
132
+ private normalizeKindList;
108
133
  /**
109
- * Find closest ancestor element with matching data-kind attribute
134
+ * Find closest ancestor element with a `data-kind` matching any expected kind.
110
135
  */
111
- private findElementByKind;
136
+ private findElementByKinds;
112
137
  /**
113
138
  * Calculate distance between two points
114
139
  */
@@ -127,5 +152,24 @@ declare class DragDropManager<TItem = unknown, TPosition = unknown> {
127
152
  destroy(): void;
128
153
  }
129
154
  //#endregion
130
- export { type DragDropCallbacks, type DragDropConfig, DragDropManager, type PointerPosition };
155
+ //#region src/drag-preview-controller.d.ts
156
+ type DragPreviewControllerOptions = {
157
+ zIndex?: number;
158
+ opacity?: number;
159
+ centerOnCursor?: boolean;
160
+ className?: string | string[];
161
+ copyComputedStyles?: string[];
162
+ appendTo?: HTMLElement;
163
+ };
164
+ declare class DragPreviewController {
165
+ private options;
166
+ private previewElement;
167
+ constructor(options?: DragPreviewControllerOptions);
168
+ startFromElement(element: HTMLElement): void;
169
+ moveToPointer(pos: PointerPosition): void;
170
+ stop(): void;
171
+ destroy(): void;
172
+ }
173
+ //#endregion
174
+ export { type DragDropCallbacks, type DragDropConfig, DragDropManager, DragPreviewController, type DragPreviewControllerOptions, type PointerPosition };
131
175
  //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs CHANGED
@@ -2,12 +2,15 @@
2
2
  var DragDropManager = class {
3
3
  container;
4
4
  config;
5
+ draggableKinds;
6
+ droppableKinds;
5
7
  callbacks;
6
8
  state = {
7
9
  startPosition: null,
8
10
  sourceElement: null,
9
11
  sourcePosition: null,
10
12
  sourceItem: null,
13
+ activePointerId: null,
11
14
  isDragging: false,
12
15
  lastHoveredElement: null
13
16
  };
@@ -28,15 +31,21 @@ var DragDropManager = class {
28
31
  clickThreshold: config.clickThreshold ?? 10,
29
32
  scrollThreshold: config.scrollThreshold ?? 100,
30
33
  scrollSpeed: config.scrollSpeed ?? 10,
34
+ cancelOnEscape: config.cancelOnEscape ?? true,
35
+ cancelOnPointerLeave: config.cancelOnPointerLeave ?? true,
31
36
  draggableKind: config.draggableKind,
32
37
  droppableKind: config.droppableKind
33
38
  };
39
+ this.draggableKinds = this.normalizeKindList(this.config.draggableKind);
40
+ this.droppableKinds = this.normalizeKindList(this.config.droppableKind);
34
41
  this.callbacks = callbacks;
35
42
  this.boundHandlers = {
36
43
  pointerDown: this.handlePointerDown.bind(this),
37
44
  pointerMove: this.handlePointerMove.bind(this),
38
45
  pointerUp: this.handlePointerUp.bind(this),
39
- pointerCancel: this.handlePointerCancel.bind(this)
46
+ pointerCancel: this.handlePointerCancel.bind(this),
47
+ keyDown: this.handleKeyDown.bind(this),
48
+ mouseOut: this.handleMouseOut.bind(this)
40
49
  };
41
50
  this.attachListeners();
42
51
  }
@@ -45,12 +54,18 @@ var DragDropManager = class {
45
54
  this.container.addEventListener("pointermove", this.boundHandlers.pointerMove);
46
55
  this.container.addEventListener("pointerup", this.boundHandlers.pointerUp);
47
56
  this.container.addEventListener("pointercancel", this.boundHandlers.pointerCancel);
57
+ window.addEventListener("pointerup", this.boundHandlers.pointerUp);
58
+ window.addEventListener("pointercancel", this.boundHandlers.pointerCancel);
59
+ window.addEventListener("keydown", this.boundHandlers.keyDown);
60
+ window.addEventListener("mouseout", this.boundHandlers.mouseOut);
48
61
  }
49
62
  handlePointerDown(e) {
63
+ if (this.state.startPosition) return;
50
64
  const target = e.target;
51
- const draggableElement = this.findElementByKind(target, this.config.draggableKind);
52
- if (!draggableElement) return;
53
- const position = this.callbacks.getItemPosition(draggableElement, this.config.draggableKind);
65
+ const draggableMatch = this.findElementByKinds(target, this.draggableKinds);
66
+ if (!draggableMatch) return;
67
+ const { element: draggableElement, kind: sourceKind } = draggableMatch;
68
+ const position = this.callbacks.getItemPosition(draggableElement, sourceKind);
54
69
  if (!position) return;
55
70
  if (draggableElement.dataset.empty !== void 0) return;
56
71
  if (this.callbacks.canDrag && !this.callbacks.canDrag(draggableElement, position)) return;
@@ -64,8 +79,13 @@ var DragDropManager = class {
64
79
  this.state.sourceElement = draggableElement;
65
80
  this.state.sourcePosition = position;
66
81
  this.state.sourceItem = item;
82
+ this.state.activePointerId = this.getPointerId(e);
83
+ if (this.state.activePointerId !== null && typeof draggableElement.setPointerCapture === "function") try {
84
+ draggableElement.setPointerCapture(this.state.activePointerId);
85
+ } catch {}
67
86
  }
68
87
  handlePointerMove(e) {
88
+ if (!this.isActivePointer(e)) return;
69
89
  const currentPos = {
70
90
  x: e.clientX,
71
91
  y: e.clientY
@@ -87,7 +107,7 @@ var DragDropManager = class {
87
107
  if (!this.lastPointerPos) return;
88
108
  const pos = this.lastPointerPos;
89
109
  const elementUnderCursor = document.elementFromPoint(pos.x, pos.y);
90
- const droppableElement = this.findElementByKind(elementUnderCursor, this.config.droppableKind);
110
+ const droppableElement = this.findElementByKinds(elementUnderCursor, this.droppableKinds)?.element ?? null;
91
111
  if (droppableElement !== this.state.lastHoveredElement) {
92
112
  if (this.state.lastHoveredElement) this.state.lastHoveredElement.removeAttribute("data-hovered");
93
113
  if (droppableElement) {
@@ -100,6 +120,7 @@ var DragDropManager = class {
100
120
  });
101
121
  }
102
122
  handlePointerUp(e) {
123
+ if (!this.isActivePointer(e)) return;
103
124
  if (this.state.startPosition && !this.state.isDragging) {
104
125
  const currentPos = {
105
126
  x: e.clientX,
@@ -114,9 +135,10 @@ var DragDropManager = class {
114
135
  return;
115
136
  }
116
137
  const target = document.elementFromPoint(e.clientX, e.clientY);
117
- const droppableElement = this.findElementByKind(target, this.config.droppableKind);
118
- if (droppableElement) {
119
- const targetPosition = this.callbacks.getItemPosition(droppableElement, this.config.droppableKind);
138
+ const droppableMatch = this.findElementByKinds(target, this.droppableKinds);
139
+ const droppableElement = droppableMatch?.element ?? null;
140
+ if (droppableElement && droppableMatch) {
141
+ const targetPosition = this.callbacks.getItemPosition(droppableElement, droppableMatch.kind);
120
142
  if (targetPosition) this.callbacks.onDrop?.(this.state.sourcePosition, targetPosition, this.state.sourceItem);
121
143
  }
122
144
  this.cleanup();
@@ -124,6 +146,18 @@ var DragDropManager = class {
124
146
  handlePointerCancel() {
125
147
  this.cleanup();
126
148
  }
149
+ handleKeyDown(e) {
150
+ if (!this.config.cancelOnEscape) return;
151
+ if (e.key !== "Escape") return;
152
+ if (!this.state.startPosition) return;
153
+ this.cleanup();
154
+ }
155
+ handleMouseOut(e) {
156
+ if (!this.config.cancelOnPointerLeave) return;
157
+ if (!this.state.startPosition) return;
158
+ if (e.relatedTarget !== null) return;
159
+ this.cleanup();
160
+ }
127
161
  handleAutoScroll(pos) {
128
162
  const { scrollThreshold, scrollSpeed } = this.config;
129
163
  const viewportWidth = window.innerWidth;
@@ -137,6 +171,9 @@ var DragDropManager = class {
137
171
  if (scrollX !== 0 || scrollY !== 0) window.scrollBy(scrollX, scrollY);
138
172
  }
139
173
  cleanup() {
174
+ if (this.state.sourceElement && this.state.activePointerId !== null && typeof this.state.sourceElement.hasPointerCapture === "function" && typeof this.state.sourceElement.releasePointerCapture === "function" && this.state.sourceElement.hasPointerCapture(this.state.activePointerId)) try {
175
+ this.state.sourceElement.releasePointerCapture(this.state.activePointerId);
176
+ } catch {}
140
177
  if (this.state.sourceElement) this.state.sourceElement.removeAttribute("data-dragging");
141
178
  if (this.state.lastHoveredElement) this.state.lastHoveredElement.removeAttribute("data-hovered");
142
179
  if (this.rafId !== null) {
@@ -149,18 +186,35 @@ var DragDropManager = class {
149
186
  sourceElement: null,
150
187
  sourcePosition: null,
151
188
  sourceItem: null,
189
+ activePointerId: null,
152
190
  isDragging: false,
153
191
  lastHoveredElement: null
154
192
  };
155
193
  this.lastPointerPos = null;
156
194
  }
195
+ getPointerId(e) {
196
+ return typeof e.pointerId === "number" ? e.pointerId : null;
197
+ }
198
+ isActivePointer(e) {
199
+ if (this.state.activePointerId === null) return true;
200
+ const pointerId = this.getPointerId(e);
201
+ return pointerId === null || pointerId === this.state.activePointerId;
202
+ }
203
+ normalizeKindList(kinds) {
204
+ const values = Array.isArray(kinds) ? kinds : [kinds];
205
+ return [...new Set(values.map((value) => value.trim()).filter(Boolean))];
206
+ }
157
207
  /**
158
- * Find closest ancestor element with matching data-kind attribute
208
+ * Find closest ancestor element with a `data-kind` matching any expected kind.
159
209
  */
160
- findElementByKind(element, kind) {
210
+ findElementByKinds(element, kinds) {
161
211
  let current = element;
162
212
  while (current && current !== this.container) {
163
- if (current.dataset.kind === kind) return current;
213
+ const elementKind = current.dataset.kind;
214
+ if (elementKind && kinds.includes(elementKind)) return {
215
+ element: current,
216
+ kind: elementKind
217
+ };
164
218
  current = current.parentElement;
165
219
  }
166
220
  return null;
@@ -192,9 +246,76 @@ var DragDropManager = class {
192
246
  this.container.removeEventListener("pointermove", this.boundHandlers.pointerMove);
193
247
  this.container.removeEventListener("pointerup", this.boundHandlers.pointerUp);
194
248
  this.container.removeEventListener("pointercancel", this.boundHandlers.pointerCancel);
249
+ window.removeEventListener("pointerup", this.boundHandlers.pointerUp);
250
+ window.removeEventListener("pointercancel", this.boundHandlers.pointerCancel);
251
+ window.removeEventListener("keydown", this.boundHandlers.keyDown);
252
+ window.removeEventListener("mouseout", this.boundHandlers.mouseOut);
253
+ }
254
+ };
255
+
256
+ //#endregion
257
+ //#region src/drag-preview-controller.ts
258
+ const OFFSCREEN_TRANSFORM = "translate(-9999px, -9999px)";
259
+ function normalizeClassNames(className) {
260
+ if (!className) return [];
261
+ return Array.isArray(className) ? className : [className];
262
+ }
263
+ var DragPreviewController = class {
264
+ options;
265
+ previewElement = null;
266
+ constructor(options = {}) {
267
+ this.options = {
268
+ zIndex: options.zIndex ?? 9999,
269
+ opacity: options.opacity ?? .95,
270
+ centerOnCursor: options.centerOnCursor ?? true,
271
+ className: options.className,
272
+ copyComputedStyles: options.copyComputedStyles,
273
+ appendTo: options.appendTo
274
+ };
275
+ }
276
+ startFromElement(element) {
277
+ this.stop();
278
+ const rect = element.getBoundingClientRect();
279
+ const clone = element.cloneNode(true);
280
+ const computed = window.getComputedStyle(element);
281
+ const appendTo = this.options.appendTo ?? document.body;
282
+ clone.setAttribute("aria-hidden", "true");
283
+ clone.style.position = "fixed";
284
+ clone.style.left = "0";
285
+ clone.style.top = "0";
286
+ clone.style.width = `${rect.width}px`;
287
+ clone.style.height = `${rect.height}px`;
288
+ clone.style.pointerEvents = "none";
289
+ clone.style.zIndex = String(this.options.zIndex);
290
+ clone.style.opacity = String(this.options.opacity);
291
+ clone.style.transform = OFFSCREEN_TRANSFORM;
292
+ clone.style.transition = "none";
293
+ clone.style.cursor = "grabbing";
294
+ clone.style.margin = "0";
295
+ clone.style.boxSizing = "border-box";
296
+ clone.style.background = computed.backgroundColor;
297
+ for (const classToken of normalizeClassNames(this.options.className)) clone.classList.add(classToken);
298
+ if (this.options.copyComputedStyles && this.options.copyComputedStyles.length > 0) for (const propertyName of this.options.copyComputedStyles) clone.style.setProperty(propertyName, computed.getPropertyValue(propertyName));
299
+ appendTo.appendChild(clone);
300
+ this.previewElement = clone;
301
+ }
302
+ moveToPointer(pos) {
303
+ if (!this.previewElement) return;
304
+ if (this.options.centerOnCursor) {
305
+ this.previewElement.style.transform = `translate(${pos.x}px, ${pos.y}px) translate(-50%, -50%)`;
306
+ return;
307
+ }
308
+ this.previewElement.style.transform = `translate(${pos.x}px, ${pos.y}px)`;
309
+ }
310
+ stop() {
311
+ this.previewElement?.remove();
312
+ this.previewElement = null;
313
+ }
314
+ destroy() {
315
+ this.stop();
195
316
  }
196
317
  };
197
318
 
198
319
  //#endregion
199
- export { DragDropManager };
320
+ export { DragDropManager, DragPreviewController };
200
321
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/drag-drop-manager.ts"],"sourcesContent":["/**\n * Framework-agnostic Drag and Drop Manager\n *\n * A data-driven drag-and-drop utility that works with React and vanilla JS.\n * Uses data attributes and direct DOM manipulation for optimal performance.\n *\n * @template TItem - The type of data associated with draggable items\n * @template TPosition - The type representing position/coordinates (e.g., {x, y}, {box, cell}, etc.)\n */\n\nexport type DragDropConfig = {\n /**\n * Minimum distance in pixels the pointer must move before a drag operation starts.\n * Prevents accidental drags from small movements and allows normal scrolling on mobile.\n * Checked during `pointermove` before drag has started.\n * @default 10\n * @example\n * dragThreshold: 15 // Harder to start drag (better for mobile scroll)\n */\n dragThreshold?: number\n\n /**\n * Maximum distance in pixels between pointerdown and pointerup to treat as a click/tap.\n * Used to distinguish intentional clicks from cancelled drag attempts.\n * Only checked on `pointerup` if drag never started.\n * @default 10\n * @example\n * clickThreshold: 5 // Stricter click detection\n * clickThreshold: 15 // More forgiving for mobile taps\n */\n clickThreshold?: number\n\n /**\n * Distance in pixels from viewport edge that triggers auto-scrolling during drag.\n * When dragging near screen edges, the window automatically scrolls.\n * @default 100\n * @example\n * scrollThreshold: 50 // Start scrolling closer to edges\n * scrollThreshold: 150 // Larger safe zone before scrolling\n */\n scrollThreshold?: number\n\n /**\n * Scroll speed in pixels per animation frame during auto-scroll.\n * Higher values = faster scrolling when dragging near viewport edges.\n * @default 10\n * @example\n * scrollSpeed: 5 // Slower, smoother scroll\n * scrollSpeed: 20 // Faster scroll\n */\n scrollSpeed?: number\n\n /**\n * The `data-kind` attribute value for draggable elements.\n * Elements with this attribute can be picked up and dragged.\n * @example\n * draggableKind: 'cell' // <div data-kind=\"cell\">\n * draggableKind: 'card' // <div data-kind=\"card\">\n * draggableKind: 'list-item' // <div data-kind=\"list-item\">\n */\n draggableKind: string\n\n /**\n * The `data-kind` attribute value for droppable target elements.\n * Elements with this attribute can receive dropped items.\n * Often the same as `draggableKind` for reorderable lists.\n * @example\n * droppableKind: 'cell' // Can drop on cells\n * droppableKind: 'zone' // Can drop on zones\n */\n droppableKind: string\n}\n\nexport type PointerPosition = {\n x: number\n y: number\n}\n\nexport type DragDropCallbacks<TItem, TPosition> = {\n /** Called when determining if an element can be dragged */\n canDrag?: (element: HTMLElement, position: TPosition) => boolean\n /** Called when drag starts (after threshold is met) */\n onDragStart?: (element: HTMLElement, position: TPosition, item: TItem) => void\n /** Called continuously during drag */\n onDragMove?: (pos: PointerPosition, hoveredElement: HTMLElement | null) => void\n /** Called when item is dropped on a valid target */\n onDrop?: (sourcePosition: TPosition, targetPosition: TPosition, sourceItem: TItem) => void\n /** Called when drag ends (regardless of success) */\n onDragEnd?: () => void\n /** Called on simple click/tap (no drag) */\n onClick?: (element: HTMLElement, position: TPosition) => void\n /** Called to extract item data from an element */\n getItemData?: (element: HTMLElement, position: TPosition) => TItem | null\n /** Called to get the custom position/coordinates of an element */\n getItemPosition: (element: HTMLElement, kind: string) => TPosition | null\n}\n\ntype DragState<TItem, TPosition> = {\n startPosition: PointerPosition | null\n sourceElement: HTMLElement | null\n sourcePosition: TPosition | null\n sourceItem: TItem | null\n isDragging: boolean\n lastHoveredElement: HTMLElement | null\n}\n\nexport class DragDropManager<TItem = unknown, TPosition = unknown> {\n private container: HTMLElement\n private config: Required<DragDropConfig>\n private callbacks: DragDropCallbacks<TItem, TPosition>\n private state: DragState<TItem, TPosition> = {\n startPosition: null,\n sourceElement: null,\n sourcePosition: null,\n sourceItem: null,\n isDragging: false,\n lastHoveredElement: null,\n }\n private rafId: number | null = null\n private lastPointerPos: PointerPosition | null = null\n private boundHandlers: {\n pointerDown: (e: PointerEvent) => void\n pointerMove: (e: PointerEvent) => void\n pointerUp: (e: PointerEvent) => void\n pointerCancel: (e: PointerEvent) => void\n }\n\n constructor(\n containerRef: { current: HTMLElement | null } | string | HTMLElement,\n config: DragDropConfig,\n callbacks: DragDropCallbacks<TItem, TPosition>,\n ) {\n // Resolve container element\n if (typeof containerRef === 'string') {\n const element = document.querySelector<HTMLElement>(containerRef)\n if (!element) {\n throw new Error(`DragDropManager: Could not find element with selector \"${containerRef}\"`)\n }\n this.container = element\n } else if (typeof containerRef === 'object' && 'current' in containerRef) {\n if (!containerRef.current) {\n throw new Error('DragDropManager: Ref.current is null')\n }\n this.container = containerRef.current\n } else {\n this.container = containerRef as HTMLElement\n }\n\n this.config = {\n dragThreshold: config.dragThreshold ?? 10,\n clickThreshold: config.clickThreshold ?? 10,\n scrollThreshold: config.scrollThreshold ?? 100,\n scrollSpeed: config.scrollSpeed ?? 10,\n draggableKind: config.draggableKind,\n droppableKind: config.droppableKind,\n }\n\n this.callbacks = callbacks\n\n // Bind handlers to preserve context\n this.boundHandlers = {\n pointerDown: this.handlePointerDown.bind(this),\n pointerMove: this.handlePointerMove.bind(this),\n pointerUp: this.handlePointerUp.bind(this),\n pointerCancel: this.handlePointerCancel.bind(this),\n }\n\n this.attachListeners()\n }\n\n private attachListeners(): void {\n this.container.addEventListener('pointerdown', this.boundHandlers.pointerDown)\n this.container.addEventListener('pointermove', this.boundHandlers.pointerMove)\n this.container.addEventListener('pointerup', this.boundHandlers.pointerUp)\n this.container.addEventListener('pointercancel', this.boundHandlers.pointerCancel)\n }\n\n private handlePointerDown(e: PointerEvent): void {\n const target = e.target as HTMLElement\n const draggableElement = this.findElementByKind(target, this.config.draggableKind)\n\n if (!draggableElement) return\n\n const position = this.callbacks.getItemPosition(draggableElement, this.config.draggableKind)\n if (!position) return\n\n // Check if element is empty (convention: data-empty=\"any-value\")\n const isEmpty = draggableElement.dataset.empty !== undefined\n if (isEmpty) return\n\n // Check canDrag callback\n if (this.callbacks.canDrag && !this.callbacks.canDrag(draggableElement, position)) {\n return\n }\n\n // Get item data\n const item = this.callbacks.getItemData?.(draggableElement, position) ?? null\n if (!item) return\n\n // Prevent text selection during potential drag\n e.preventDefault()\n\n // Store start position and element info\n this.state.startPosition = { x: e.clientX, y: e.clientY }\n this.state.sourceElement = draggableElement\n this.state.sourcePosition = position\n this.state.sourceItem = item\n }\n\n private handlePointerMove(e: PointerEvent): void {\n const currentPos: PointerPosition = { x: e.clientX, y: e.clientY }\n\n // Check if we should start dragging (threshold not yet met)\n if (this.state.startPosition && !this.state.isDragging) {\n const distance = this.getDistance(this.state.startPosition, currentPos)\n\n if (distance >= this.config.dragThreshold) {\n // Start drag\n this.state.isDragging = true\n\n if (this.state.sourceElement && this.state.sourcePosition && this.state.sourceItem) {\n // Set data-dragging attribute on source\n this.state.sourceElement.setAttribute('data-dragging', 'true')\n\n // Notify drag start\n this.callbacks.onDragStart?.(\n this.state.sourceElement,\n this.state.sourcePosition,\n this.state.sourceItem,\n )\n }\n }\n return\n }\n\n if (!this.state.isDragging) return\n\n // Store latest position for RAF\n this.lastPointerPos = currentPos\n\n // Throttle updates to 60fps using RAF\n if (this.rafId === null) {\n this.rafId = requestAnimationFrame(() => {\n this.rafId = null\n if (!this.lastPointerPos) return\n\n const pos = this.lastPointerPos\n\n // Find hovered droppable element\n const elementUnderCursor = document.elementFromPoint(pos.x, pos.y) as HTMLElement\n const droppableElement = this.findElementByKind(\n elementUnderCursor,\n this.config.droppableKind,\n )\n\n // Update hover feedback\n if (droppableElement !== this.state.lastHoveredElement) {\n // Remove hover from previous element\n if (this.state.lastHoveredElement) {\n this.state.lastHoveredElement.removeAttribute('data-hovered')\n }\n\n // Add hover to current element (including the source element).\n // This enables \"drag over itself\" states (e.g. combined data-dragging + data-hovered styling).\n if (droppableElement) {\n droppableElement.setAttribute('data-hovered', 'true')\n this.state.lastHoveredElement = droppableElement\n } else {\n this.state.lastHoveredElement = null\n }\n }\n\n // Auto-scroll\n this.handleAutoScroll(pos)\n\n // Notify drag move\n this.callbacks.onDragMove?.(pos, this.state.lastHoveredElement)\n })\n }\n }\n\n private handlePointerUp(e: PointerEvent): void {\n // Handle click/tap if we never started dragging\n if (this.state.startPosition && !this.state.isDragging) {\n const currentPos = { x: e.clientX, y: e.clientY }\n const distance = this.getDistance(this.state.startPosition, currentPos)\n\n // If distance is very small, treat as click\n if (\n distance < this.config.clickThreshold &&\n this.state.sourceElement &&\n this.state.sourcePosition\n ) {\n this.callbacks.onClick?.(this.state.sourceElement, this.state.sourcePosition)\n }\n\n this.cleanup()\n return\n }\n\n if (!this.state.isDragging || !this.state.sourcePosition || !this.state.sourceItem) {\n this.cleanup()\n return\n }\n\n // Find drop target\n const target = document.elementFromPoint(e.clientX, e.clientY) as HTMLElement\n const droppableElement = this.findElementByKind(target, this.config.droppableKind)\n\n if (droppableElement) {\n const targetPosition = this.callbacks.getItemPosition(\n droppableElement,\n this.config.droppableKind,\n )\n\n if (targetPosition) {\n // Notify drop\n this.callbacks.onDrop?.(this.state.sourcePosition, targetPosition, this.state.sourceItem)\n }\n }\n\n this.cleanup()\n }\n\n private handlePointerCancel(): void {\n this.cleanup()\n }\n\n private handleAutoScroll(pos: PointerPosition): void {\n const { scrollThreshold, scrollSpeed } = this.config\n const viewportWidth = window.innerWidth\n const viewportHeight = window.innerHeight\n\n let scrollX = 0\n let scrollY = 0\n\n // Check horizontal scroll\n if (pos.x < scrollThreshold) {\n scrollX = -scrollSpeed\n } else if (pos.x > viewportWidth - scrollThreshold) {\n scrollX = scrollSpeed\n }\n\n // Check vertical scroll\n if (pos.y < scrollThreshold) {\n scrollY = -scrollSpeed\n } else if (pos.y > viewportHeight - scrollThreshold) {\n scrollY = scrollSpeed\n }\n\n if (scrollX !== 0 || scrollY !== 0) {\n window.scrollBy(scrollX, scrollY)\n }\n }\n\n private cleanup(): void {\n // Remove data-dragging attribute\n if (this.state.sourceElement) {\n this.state.sourceElement.removeAttribute('data-dragging')\n }\n\n // Remove hover attribute\n if (this.state.lastHoveredElement) {\n this.state.lastHoveredElement.removeAttribute('data-hovered')\n }\n\n // Cancel RAF\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId)\n this.rafId = null\n }\n\n // Notify drag end\n if (this.state.isDragging) {\n this.callbacks.onDragEnd?.()\n }\n\n // Reset state\n this.state = {\n startPosition: null,\n sourceElement: null,\n sourcePosition: null,\n sourceItem: null,\n isDragging: false,\n lastHoveredElement: null,\n }\n this.lastPointerPos = null\n }\n\n /**\n * Find closest ancestor element with matching data-kind attribute\n */\n private findElementByKind(element: HTMLElement | null, kind: string): HTMLElement | null {\n let current = element\n while (current && current !== this.container) {\n if (current.dataset.kind === kind) {\n return current\n }\n current = current.parentElement\n }\n return null\n }\n\n /**\n * Calculate distance between two points\n */\n private getDistance(p1: PointerPosition, p2: PointerPosition): number {\n return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2))\n }\n\n /**\n * Get current drag state (read-only)\n */\n public getState(): Readonly<DragState<TItem, TPosition>> {\n return { ...this.state }\n }\n\n /**\n * Check if currently dragging\n */\n public isDragging(): boolean {\n return this.state.isDragging\n }\n\n /**\n * Cleanup and remove event listeners\n */\n public destroy(): void {\n this.cleanup()\n this.container.removeEventListener('pointerdown', this.boundHandlers.pointerDown)\n this.container.removeEventListener('pointermove', this.boundHandlers.pointerMove)\n this.container.removeEventListener('pointerup', this.boundHandlers.pointerUp)\n this.container.removeEventListener('pointercancel', this.boundHandlers.pointerCancel)\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/drag-drop-manager.ts","../src/drag-preview-controller.ts"],"sourcesContent":["/**\n * Framework-agnostic Drag and Drop Manager\n *\n * A data-driven drag-and-drop utility that works with React and vanilla JS.\n * Uses data attributes and direct DOM manipulation for optimal performance.\n *\n * @template TItem - The type of data associated with draggable items\n * @template TPosition - The type representing position/coordinates (e.g., {x, y}, {box, cell}, etc.)\n */\n\nexport type DragDropConfig = {\n /**\n * Minimum distance in pixels the pointer must move before a drag operation starts.\n * Prevents accidental drags from small movements and allows normal scrolling on mobile.\n * Checked during `pointermove` before drag has started.\n * @default 10\n * @example\n * dragThreshold: 15 // Harder to start drag (better for mobile scroll)\n */\n dragThreshold?: number\n\n /**\n * Maximum distance in pixels between pointerdown and pointerup to treat as a click/tap.\n * Used to distinguish intentional clicks from cancelled drag attempts.\n * Only checked on `pointerup` if drag never started.\n * @default 10\n * @example\n * clickThreshold: 5 // Stricter click detection\n * clickThreshold: 15 // More forgiving for mobile taps\n */\n clickThreshold?: number\n\n /**\n * Distance in pixels from viewport edge that triggers auto-scrolling during drag.\n * When dragging near screen edges, the window automatically scrolls.\n * @default 100\n * @example\n * scrollThreshold: 50 // Start scrolling closer to edges\n * scrollThreshold: 150 // Larger safe zone before scrolling\n */\n scrollThreshold?: number\n\n /**\n * Scroll speed in pixels per animation frame during auto-scroll.\n * Higher values = faster scrolling when dragging near viewport edges.\n * @default 10\n * @example\n * scrollSpeed: 5 // Slower, smoother scroll\n * scrollSpeed: 20 // Faster scroll\n */\n scrollSpeed?: number\n\n /**\n * Cancel the current drag operation when Escape is pressed.\n * Useful for explicit keyboard cancellation.\n * @default true\n */\n cancelOnEscape?: boolean\n\n /**\n * Cancel the current drag operation when the pointer leaves the browser window.\n * Prevents \"stuck dragging\" when pointerup happens outside the app/window.\n * @default true\n */\n cancelOnPointerLeave?: boolean\n\n /**\n * The `data-kind` value(s) for draggable elements.\n * Can be a single kind or multiple kinds.\n * Each draggable element should still expose a single `data-kind` value.\n * @example\n * draggableKind: 'cell' // <div data-kind=\"cell\">\n * draggableKind: ['quest', 'bounty'] // <div data-kind=\"quest\">\n * draggableKind: 'card' // <div data-kind=\"card\">\n * draggableKind: 'list-item' // <div data-kind=\"list-item\">\n */\n draggableKind: string | string[]\n\n /**\n * The `data-kind` value(s) for droppable target elements.\n * Can be a single kind or multiple kinds.\n * Elements with matching kinds can receive dropped items.\n * Each droppable element should still expose a single `data-kind` value.\n * Often the same as `draggableKind` for reorderable lists.\n * @example\n * droppableKind: 'cell' // Can drop on cells\n * droppableKind: ['quest', 'bounty'] // Can drop on either kind\n * droppableKind: 'zone' // Can drop on zones\n */\n droppableKind: string | string[]\n}\n\nexport type PointerPosition = {\n x: number\n y: number\n}\n\nexport type DragDropCallbacks<TItem, TPosition> = {\n /** Called when determining if an element can be dragged */\n canDrag?: (element: HTMLElement, position: TPosition) => boolean\n /** Called when drag starts (after threshold is met) */\n onDragStart?: (element: HTMLElement, position: TPosition, item: TItem) => void\n /** Called continuously during drag */\n onDragMove?: (pos: PointerPosition, hoveredElement: HTMLElement | null) => void\n /** Called when item is dropped on a valid target */\n onDrop?: (sourcePosition: TPosition, targetPosition: TPosition, sourceItem: TItem) => void\n /** Called when drag ends (regardless of success) */\n onDragEnd?: () => void\n /** Called on simple click/tap (no drag) */\n onClick?: (element: HTMLElement, position: TPosition) => void\n /** Called to extract item data from an element */\n getItemData?: (element: HTMLElement, position: TPosition) => TItem | null\n /** Called to get the custom position/coordinates of an element */\n getItemPosition: (element: HTMLElement, kind: string) => TPosition | null\n}\n\ntype DragState<TItem, TPosition> = {\n startPosition: PointerPosition | null\n sourceElement: HTMLElement | null\n sourcePosition: TPosition | null\n sourceItem: TItem | null\n activePointerId: number | null\n isDragging: boolean\n lastHoveredElement: HTMLElement | null\n}\n\nexport class DragDropManager<TItem = unknown, TPosition = unknown> {\n private container: HTMLElement\n private config: Required<DragDropConfig>\n private draggableKinds: string[]\n private droppableKinds: string[]\n private callbacks: DragDropCallbacks<TItem, TPosition>\n private state: DragState<TItem, TPosition> = {\n startPosition: null,\n sourceElement: null,\n sourcePosition: null,\n sourceItem: null,\n activePointerId: null,\n isDragging: false,\n lastHoveredElement: null,\n }\n private rafId: number | null = null\n private lastPointerPos: PointerPosition | null = null\n private boundHandlers: {\n pointerDown: (e: PointerEvent) => void\n pointerMove: (e: PointerEvent) => void\n pointerUp: (e: PointerEvent) => void\n pointerCancel: (e: PointerEvent) => void\n keyDown: (e: KeyboardEvent) => void\n mouseOut: (e: MouseEvent) => void\n }\n\n constructor(\n containerRef: { current: HTMLElement | null } | string | HTMLElement,\n config: DragDropConfig,\n callbacks: DragDropCallbacks<TItem, TPosition>,\n ) {\n // Resolve container element\n if (typeof containerRef === 'string') {\n const element = document.querySelector<HTMLElement>(containerRef)\n if (!element) {\n throw new Error(`DragDropManager: Could not find element with selector \"${containerRef}\"`)\n }\n this.container = element\n } else if (typeof containerRef === 'object' && 'current' in containerRef) {\n if (!containerRef.current) {\n throw new Error('DragDropManager: Ref.current is null')\n }\n this.container = containerRef.current\n } else {\n this.container = containerRef as HTMLElement\n }\n\n this.config = {\n dragThreshold: config.dragThreshold ?? 10,\n clickThreshold: config.clickThreshold ?? 10,\n scrollThreshold: config.scrollThreshold ?? 100,\n scrollSpeed: config.scrollSpeed ?? 10,\n cancelOnEscape: config.cancelOnEscape ?? true,\n cancelOnPointerLeave: config.cancelOnPointerLeave ?? true,\n draggableKind: config.draggableKind,\n droppableKind: config.droppableKind,\n }\n this.draggableKinds = this.normalizeKindList(this.config.draggableKind)\n this.droppableKinds = this.normalizeKindList(this.config.droppableKind)\n\n this.callbacks = callbacks\n\n // Bind handlers to preserve context\n this.boundHandlers = {\n pointerDown: this.handlePointerDown.bind(this),\n pointerMove: this.handlePointerMove.bind(this),\n pointerUp: this.handlePointerUp.bind(this),\n pointerCancel: this.handlePointerCancel.bind(this),\n keyDown: this.handleKeyDown.bind(this),\n mouseOut: this.handleMouseOut.bind(this),\n }\n\n this.attachListeners()\n }\n\n private attachListeners(): void {\n this.container.addEventListener('pointerdown', this.boundHandlers.pointerDown)\n this.container.addEventListener('pointermove', this.boundHandlers.pointerMove)\n this.container.addEventListener('pointerup', this.boundHandlers.pointerUp)\n this.container.addEventListener('pointercancel', this.boundHandlers.pointerCancel)\n window.addEventListener('pointerup', this.boundHandlers.pointerUp)\n window.addEventListener('pointercancel', this.boundHandlers.pointerCancel)\n window.addEventListener('keydown', this.boundHandlers.keyDown)\n window.addEventListener('mouseout', this.boundHandlers.mouseOut)\n }\n\n private handlePointerDown(e: PointerEvent): void {\n if (this.state.startPosition) return\n\n const target = e.target as HTMLElement\n const draggableMatch = this.findElementByKinds(target, this.draggableKinds)\n\n if (!draggableMatch) return\n\n const { element: draggableElement, kind: sourceKind } = draggableMatch\n\n const position = this.callbacks.getItemPosition(draggableElement, sourceKind)\n if (!position) return\n\n // Check if element is empty (convention: data-empty=\"any-value\")\n const isEmpty = draggableElement.dataset.empty !== undefined\n if (isEmpty) return\n\n // Check canDrag callback\n if (this.callbacks.canDrag && !this.callbacks.canDrag(draggableElement, position)) {\n return\n }\n\n // Get item data\n const item = this.callbacks.getItemData?.(draggableElement, position) ?? null\n if (!item) return\n\n // Prevent text selection during potential drag\n e.preventDefault()\n\n // Store start position and element info\n this.state.startPosition = { x: e.clientX, y: e.clientY }\n this.state.sourceElement = draggableElement\n this.state.sourcePosition = position\n this.state.sourceItem = item\n this.state.activePointerId = this.getPointerId(e)\n\n // Capture pointer events outside container boundaries when supported.\n if (\n this.state.activePointerId !== null &&\n typeof draggableElement.setPointerCapture === 'function'\n ) {\n try {\n draggableElement.setPointerCapture(this.state.activePointerId)\n } catch {\n // Ignore unsupported/inactive-pointer capture errors.\n }\n }\n }\n\n private handlePointerMove(e: PointerEvent): void {\n if (!this.isActivePointer(e)) return\n\n const currentPos: PointerPosition = { x: e.clientX, y: e.clientY }\n\n // Check if we should start dragging (threshold not yet met)\n if (this.state.startPosition && !this.state.isDragging) {\n const distance = this.getDistance(this.state.startPosition, currentPos)\n\n if (distance >= this.config.dragThreshold) {\n // Start drag\n this.state.isDragging = true\n\n if (this.state.sourceElement && this.state.sourcePosition && this.state.sourceItem) {\n // Set data-dragging attribute on source\n this.state.sourceElement.setAttribute('data-dragging', 'true')\n\n // Notify drag start\n this.callbacks.onDragStart?.(\n this.state.sourceElement,\n this.state.sourcePosition,\n this.state.sourceItem,\n )\n }\n }\n return\n }\n\n if (!this.state.isDragging) return\n\n // Store latest position for RAF\n this.lastPointerPos = currentPos\n\n // Throttle updates to 60fps using RAF\n if (this.rafId === null) {\n this.rafId = requestAnimationFrame(() => {\n this.rafId = null\n if (!this.lastPointerPos) return\n\n const pos = this.lastPointerPos\n\n // Find hovered droppable element\n const elementUnderCursor = document.elementFromPoint(pos.x, pos.y) as HTMLElement\n const droppableMatch = this.findElementByKinds(elementUnderCursor, this.droppableKinds)\n const droppableElement = droppableMatch?.element ?? null\n\n // Update hover feedback\n if (droppableElement !== this.state.lastHoveredElement) {\n // Remove hover from previous element\n if (this.state.lastHoveredElement) {\n this.state.lastHoveredElement.removeAttribute('data-hovered')\n }\n\n // Add hover to current element (including the source element).\n // This enables \"drag over itself\" states (e.g. combined data-dragging + data-hovered styling).\n if (droppableElement) {\n droppableElement.setAttribute('data-hovered', 'true')\n this.state.lastHoveredElement = droppableElement\n } else {\n this.state.lastHoveredElement = null\n }\n }\n\n // Auto-scroll\n this.handleAutoScroll(pos)\n\n // Notify drag move\n this.callbacks.onDragMove?.(pos, this.state.lastHoveredElement)\n })\n }\n }\n\n private handlePointerUp(e: PointerEvent): void {\n if (!this.isActivePointer(e)) return\n\n // Handle click/tap if we never started dragging\n if (this.state.startPosition && !this.state.isDragging) {\n const currentPos = { x: e.clientX, y: e.clientY }\n const distance = this.getDistance(this.state.startPosition, currentPos)\n\n // If distance is very small, treat as click\n if (\n distance < this.config.clickThreshold &&\n this.state.sourceElement &&\n this.state.sourcePosition\n ) {\n this.callbacks.onClick?.(this.state.sourceElement, this.state.sourcePosition)\n }\n\n this.cleanup()\n return\n }\n\n if (!this.state.isDragging || !this.state.sourcePosition || !this.state.sourceItem) {\n this.cleanup()\n return\n }\n\n // Find drop target\n const target = document.elementFromPoint(e.clientX, e.clientY) as HTMLElement\n const droppableMatch = this.findElementByKinds(target, this.droppableKinds)\n const droppableElement = droppableMatch?.element ?? null\n\n if (droppableElement && droppableMatch) {\n const targetPosition = this.callbacks.getItemPosition(droppableElement, droppableMatch.kind)\n\n if (targetPosition) {\n // Notify drop\n this.callbacks.onDrop?.(this.state.sourcePosition, targetPosition, this.state.sourceItem)\n }\n }\n\n this.cleanup()\n }\n\n private handlePointerCancel(): void {\n this.cleanup()\n }\n\n private handleKeyDown(e: KeyboardEvent): void {\n if (!this.config.cancelOnEscape) return\n if (e.key !== 'Escape') return\n if (!this.state.startPosition) return\n this.cleanup()\n }\n\n private handleMouseOut(e: MouseEvent): void {\n if (!this.config.cancelOnPointerLeave) return\n if (!this.state.startPosition) return\n if (e.relatedTarget !== null) return\n this.cleanup()\n }\n\n private handleAutoScroll(pos: PointerPosition): void {\n const { scrollThreshold, scrollSpeed } = this.config\n const viewportWidth = window.innerWidth\n const viewportHeight = window.innerHeight\n\n let scrollX = 0\n let scrollY = 0\n\n // Check horizontal scroll\n if (pos.x < scrollThreshold) {\n scrollX = -scrollSpeed\n } else if (pos.x > viewportWidth - scrollThreshold) {\n scrollX = scrollSpeed\n }\n\n // Check vertical scroll\n if (pos.y < scrollThreshold) {\n scrollY = -scrollSpeed\n } else if (pos.y > viewportHeight - scrollThreshold) {\n scrollY = scrollSpeed\n }\n\n if (scrollX !== 0 || scrollY !== 0) {\n window.scrollBy(scrollX, scrollY)\n }\n }\n\n private cleanup(): void {\n // Release pointer capture if we own it.\n if (\n this.state.sourceElement &&\n this.state.activePointerId !== null &&\n typeof this.state.sourceElement.hasPointerCapture === 'function' &&\n typeof this.state.sourceElement.releasePointerCapture === 'function' &&\n this.state.sourceElement.hasPointerCapture(this.state.activePointerId)\n ) {\n try {\n this.state.sourceElement.releasePointerCapture(this.state.activePointerId)\n } catch {\n // Ignore release errors for inactive pointer.\n }\n }\n\n // Remove data-dragging attribute\n if (this.state.sourceElement) {\n this.state.sourceElement.removeAttribute('data-dragging')\n }\n\n // Remove hover attribute\n if (this.state.lastHoveredElement) {\n this.state.lastHoveredElement.removeAttribute('data-hovered')\n }\n\n // Cancel RAF\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId)\n this.rafId = null\n }\n\n // Notify drag end\n if (this.state.isDragging) {\n this.callbacks.onDragEnd?.()\n }\n\n // Reset state\n this.state = {\n startPosition: null,\n sourceElement: null,\n sourcePosition: null,\n sourceItem: null,\n activePointerId: null,\n isDragging: false,\n lastHoveredElement: null,\n }\n this.lastPointerPos = null\n }\n\n private getPointerId(e: PointerEvent): number | null {\n return typeof e.pointerId === 'number' ? e.pointerId : null\n }\n\n private isActivePointer(e: PointerEvent): boolean {\n if (this.state.activePointerId === null) {\n return true\n }\n const pointerId = this.getPointerId(e)\n return pointerId === null || pointerId === this.state.activePointerId\n }\n\n private normalizeKindList(kinds: string | string[]): string[] {\n const values = Array.isArray(kinds) ? kinds : [kinds]\n return [...new Set(values.map((value) => value.trim()).filter(Boolean))]\n }\n\n /**\n * Find closest ancestor element with a `data-kind` matching any expected kind.\n */\n private findElementByKinds(\n element: HTMLElement | null,\n kinds: string[],\n ): { element: HTMLElement; kind: string } | null {\n let current = element\n while (current && current !== this.container) {\n const elementKind = current.dataset.kind\n if (elementKind && kinds.includes(elementKind)) {\n return { element: current, kind: elementKind }\n }\n current = current.parentElement\n }\n return null\n }\n\n /**\n * Calculate distance between two points\n */\n private getDistance(p1: PointerPosition, p2: PointerPosition): number {\n return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2))\n }\n\n /**\n * Get current drag state (read-only)\n */\n public getState(): Readonly<DragState<TItem, TPosition>> {\n return { ...this.state }\n }\n\n /**\n * Check if currently dragging\n */\n public isDragging(): boolean {\n return this.state.isDragging\n }\n\n /**\n * Cleanup and remove event listeners\n */\n public destroy(): void {\n this.cleanup()\n this.container.removeEventListener('pointerdown', this.boundHandlers.pointerDown)\n this.container.removeEventListener('pointermove', this.boundHandlers.pointerMove)\n this.container.removeEventListener('pointerup', this.boundHandlers.pointerUp)\n this.container.removeEventListener('pointercancel', this.boundHandlers.pointerCancel)\n window.removeEventListener('pointerup', this.boundHandlers.pointerUp)\n window.removeEventListener('pointercancel', this.boundHandlers.pointerCancel)\n window.removeEventListener('keydown', this.boundHandlers.keyDown)\n window.removeEventListener('mouseout', this.boundHandlers.mouseOut)\n }\n}\n","import type { PointerPosition } from './drag-drop-manager'\n\nexport type DragPreviewControllerOptions = {\n zIndex?: number\n opacity?: number\n centerOnCursor?: boolean\n className?: string | string[]\n copyComputedStyles?: string[]\n appendTo?: HTMLElement\n}\n\nconst OFFSCREEN_TRANSFORM = 'translate(-9999px, -9999px)'\n\nfunction normalizeClassNames(className: string | string[] | undefined): string[] {\n if (!className) return []\n return Array.isArray(className) ? className : [className]\n}\n\nexport class DragPreviewController {\n private options: Required<\n Pick<DragPreviewControllerOptions, 'zIndex' | 'opacity' | 'centerOnCursor'>\n > &\n Omit<DragPreviewControllerOptions, 'zIndex' | 'opacity' | 'centerOnCursor'>\n private previewElement: HTMLElement | null = null\n\n constructor(options: DragPreviewControllerOptions = {}) {\n this.options = {\n zIndex: options.zIndex ?? 9999,\n opacity: options.opacity ?? 0.95,\n centerOnCursor: options.centerOnCursor ?? true,\n className: options.className,\n copyComputedStyles: options.copyComputedStyles,\n appendTo: options.appendTo,\n }\n }\n\n public startFromElement(element: HTMLElement): void {\n this.stop()\n const rect = element.getBoundingClientRect()\n const clone = element.cloneNode(true) as HTMLElement\n const computed = window.getComputedStyle(element)\n const appendTo = this.options.appendTo ?? document.body\n\n clone.setAttribute('aria-hidden', 'true')\n clone.style.position = 'fixed'\n clone.style.left = '0'\n clone.style.top = '0'\n clone.style.width = `${rect.width}px`\n clone.style.height = `${rect.height}px`\n clone.style.pointerEvents = 'none'\n clone.style.zIndex = String(this.options.zIndex)\n clone.style.opacity = String(this.options.opacity)\n clone.style.transform = OFFSCREEN_TRANSFORM\n clone.style.transition = 'none'\n clone.style.cursor = 'grabbing'\n clone.style.margin = '0'\n clone.style.boxSizing = 'border-box'\n clone.style.background = computed.backgroundColor\n\n for (const classToken of normalizeClassNames(this.options.className)) {\n clone.classList.add(classToken)\n }\n\n if (this.options.copyComputedStyles && this.options.copyComputedStyles.length > 0) {\n for (const propertyName of this.options.copyComputedStyles) {\n clone.style.setProperty(propertyName, computed.getPropertyValue(propertyName))\n }\n }\n\n appendTo.appendChild(clone)\n this.previewElement = clone\n }\n\n public moveToPointer(pos: PointerPosition): void {\n if (!this.previewElement) return\n if (this.options.centerOnCursor) {\n this.previewElement.style.transform = `translate(${pos.x}px, ${pos.y}px) translate(-50%, -50%)`\n return\n }\n this.previewElement.style.transform = `translate(${pos.x}px, ${pos.y}px)`\n }\n\n public stop(): void {\n this.previewElement?.remove()\n this.previewElement = null\n }\n\n public destroy(): void {\n this.stop()\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,41 +1,50 @@
1
1
  {
2
2
  "name": "dnd-manager",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "homepage": "https://itsjavi.com/dnd-manager",
5
+ "license": "MIT",
5
6
  "repository": {
6
7
  "type": "git",
7
8
  "url": "git+https://github.com/itsjavi/dnd-manager.git"
8
9
  },
9
- "license": "MIT",
10
+ "files": [
11
+ "dist",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
10
15
  "type": "module",
16
+ "main": "./dist/index.mjs",
17
+ "module": "./dist/index.mjs",
18
+ "types": "./dist/index.d.mts",
11
19
  "exports": {
12
20
  ".": {
13
21
  "import": "./dist/index.mjs",
14
22
  "require": "./dist/index.mjs"
15
23
  }
16
24
  },
17
- "main": "./dist/index.mjs",
18
- "module": "./dist/index.mjs",
19
- "types": "./dist/index.d.mts",
20
- "files": [
21
- "dist",
22
- "README.md",
23
- "LICENSE"
24
- ],
25
- "scripts": {
26
- "build": "tsdown src/index.ts --dts --sourcemap --format esm --clean",
27
- "format": "pnpx sort-package-json && pnpm exec prettier --cache --write .",
28
- "prepublishOnly": "pnpm typecheck && pnpm build && publint",
29
- "test": "vitest --run",
30
- "test:watch": "vitest --watch",
31
- "typecheck": "tsc --noEmit"
32
- },
33
25
  "devDependencies": {
34
- "prettier": "^3.8.1",
26
+ "@vitest/browser-playwright": "^4.0.18",
27
+ "@vitest/coverage-v8": "^4.0.18",
28
+ "jsdom": "^28.1.0",
29
+ "oxfmt": "^0.35.0",
35
30
  "publint": "^0.3.17",
36
- "sort-package-json": "^3.6.1",
37
31
  "tsdown": "^0.20.3",
38
32
  "typescript": "^5.9.3",
39
33
  "vitest": "^4.0.18"
34
+ },
35
+ "scripts": {
36
+ "dev": "pnpm --filter dnd-manager-demo dev",
37
+ "build": "tsdown src/index.ts --dts --sourcemap --format esm --clean",
38
+ "build:watch": "pnpm build --watch",
39
+ "build:demo": "pnpm --filter dnd-manager-demo build",
40
+ "build:all": "pnpm build && pnpm build:demo",
41
+ "format": "oxfmt --write .",
42
+ "format:check": "oxfmt --check .",
43
+ "test": "vitest --run",
44
+ "test:coverage": "pnpm build && vitest --run --coverage",
45
+ "test:e2e": "pnpm build && vitest --project e2e",
46
+ "test:unit": "vitest --project unit",
47
+ "test:watch": "vitest --watch",
48
+ "typecheck": "tsc --noEmit"
40
49
  }
41
- }
50
+ }