dnd-manager 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
File without changes
package/README.md CHANGED
@@ -1,14 +1,98 @@
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, and compatible with any
4
+ framework.
4
5
 
5
- This document provides practical examples of using `DragDropManager` in both vanilla
6
- JavaScript and React applications.
6
+ This document provides practical examples of using the library in both vanilla JavaScript and
7
+ React applications.
7
8
 
8
9
  ## Table of Contents
9
10
 
10
- 1. [Vanilla JavaScript Example](#vanilla-javascript-example)
11
- 2. [React Example](#react-example)
11
+ 1. [Installation](#installation)
12
+ 2. [Clone Preview Helper](#clone-preview-helper)
13
+ 3. [Vanilla JavaScript Example](#vanilla-javascript-example)
14
+ 4. [React Example](#react-example)
15
+
16
+ ---
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ pnpm add dnd-manager
22
+ ```
23
+
24
+ Or with npm/yarn:
25
+
26
+ ```bash
27
+ npm install dnd-manager
28
+ yarn add dnd-manager
29
+ ```
30
+
31
+ ---
32
+
33
+ ## Clone Preview Helper
34
+
35
+ Use `new DragPreviewController()` to get a clone of the dragged element that follows the cursor with
36
+ `position: fixed`, `pointer-events: none`, and transform-based movement.
37
+
38
+ ```typescript
39
+ import {
40
+ DragPreviewController,
41
+ DragDropManager,
42
+ type DragDropCallbacks,
43
+ type PointerPosition,
44
+ } from 'dnd-manager'
45
+
46
+ type Item = { index: number }
47
+ type Position = { index: number }
48
+
49
+ const preview = new DragPreviewController({
50
+ zIndex: 9999,
51
+ opacity: 0.95,
52
+ className: 'drop-shadow-xl',
53
+ })
54
+
55
+ const callbacks: DragDropCallbacks<Item, Position> = {
56
+ getItemPosition: (element) => {
57
+ const raw = element.dataset.index
58
+ if (!raw) return null
59
+ const index = Number.parseInt(raw, 10)
60
+ return Number.isFinite(index) ? { index } : null
61
+ },
62
+
63
+ getItemData: (_element, pos) => ({ index: pos.index }),
64
+
65
+ onDragStart: (element) => {
66
+ preview.startFromElement(element)
67
+ },
68
+
69
+ onDragMove: (pos: PointerPosition) => {
70
+ preview.moveToPointer(pos)
71
+ },
72
+
73
+ onDragEnd: () => {
74
+ preview.stop()
75
+ },
76
+ }
77
+
78
+ const manager = new DragDropManager<Item, Position>(
79
+ '#container',
80
+ {
81
+ draggableKind: 'ITEM',
82
+ droppableKind: 'ITEM',
83
+ },
84
+ callbacks,
85
+ )
86
+
87
+ // Important cleanup for unmount/page teardown.
88
+ function cleanup() {
89
+ preview.destroy()
90
+ manager.destroy()
91
+ }
92
+ ```
93
+
94
+ This helper uses the clone-in-`document.body` pattern to avoid drift in scrolled containers and
95
+ keeps cursor tracking smooth via transform updates.
12
96
 
13
97
  ---
14
98
 
@@ -199,7 +283,7 @@ data to the console, and dragging shows a preview element following the cursor.
199
283
  **TypeScript (app.ts):**
200
284
 
201
285
  ```typescript
202
- import { DragDropManager, type DragDropCallbacks } from './drag-drop-manager'
286
+ import { DragDropManager, type DragDropCallbacks } from 'dnd-manager'
203
287
 
204
288
  // Types
205
289
  type GridItem = {
@@ -222,11 +306,6 @@ function initDragDropGrid(): void {
222
306
 
223
307
  // Setup DragDropManager callbacks
224
308
  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
309
  getItemPosition: (element, kind) => {
231
310
  const row = element.dataset.row
232
311
  const col = element.dataset.col
@@ -343,6 +422,8 @@ function initDragDropGrid(): void {
343
422
  clickThreshold: 10,
344
423
  scrollThreshold: 100,
345
424
  scrollSpeed: 10,
425
+ cancelOnEscape: true,
426
+ cancelOnPointerLeave: true,
346
427
  },
347
428
  callbacks,
348
429
  )
@@ -365,7 +446,7 @@ This example shows the same draggable grid implemented as a React component with
365
446
 
366
447
  ```typescript
367
448
  import { useEffect, useRef, useState } from 'react'
368
- import { DragDropManager, type DragDropCallbacks } from './drag-drop-manager'
449
+ import { DragDropManager, type DragDropCallbacks } from 'dnd-manager'
369
450
 
370
451
  // Types
371
452
  type GridItem = {
@@ -479,10 +560,6 @@ export function DraggableGrid() {
479
560
 
480
561
  // Setup callbacks
481
562
  const callbacks: DragDropCallbacks<GridItem, GridPosition> = {
482
- canDrag: (element, position) => {
483
- return element.dataset.empty === undefined
484
- },
485
-
486
563
  getItemPosition: (element, kind) => {
487
564
  const row = element.dataset.row
488
565
  const col = element.dataset.col
@@ -557,6 +634,8 @@ export function DraggableGrid() {
557
634
  clickThreshold: 10,
558
635
  scrollThreshold: 100,
559
636
  scrollSpeed: 10,
637
+ cancelOnEscape: true,
638
+ cancelOnPointerLeave: true,
560
639
  },
561
640
  callbacks,
562
641
  )
@@ -741,7 +820,7 @@ function DragPreview({ item, position, width, height }: DragPreviewProps) {
741
820
 
742
821
  ### DragDropManager Features Used:
743
822
 
744
- - `canDrag` - Prevents dragging empty cells
823
+ - `data-empty` convention - Elements with `data-empty` are automatically treated as non-draggable
745
824
  - `getItemPosition` - Extracts grid coordinates from data attributes
746
825
  - `getItemData` - Retrieves item data from grid state
747
826
  - `onDragStart` - Shows preview and updates source appearance
@@ -757,6 +836,8 @@ function DragPreview({ item, position, width, height }: DragPreviewProps) {
757
836
  - `clickThreshold: 10` - Distinguishes clicks from drags
758
837
  - `scrollThreshold: 100` - Auto-scroll near viewport edges
759
838
  - `scrollSpeed: 10` - Smooth scroll speed
839
+ - `cancelOnEscape: true` - Cancels active drag when Escape is pressed
840
+ - `cancelOnPointerLeave: true` - Cancels active drag if pointer leaves the window
760
841
 
761
842
  ---
762
843
 
package/dist/index.d.mts CHANGED
@@ -46,6 +46,18 @@ type DragDropConfig = {
46
46
  * scrollSpeed: 20 // Faster scroll
47
47
  */
48
48
  scrollSpeed?: number;
49
+ /**
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;
49
61
  /**
50
62
  * The `data-kind` attribute value for draggable elements.
51
63
  * Elements with this attribute can be picked up and dragged.
@@ -84,6 +96,7 @@ type DragState<TItem, TPosition> = {
84
96
  sourceElement: HTMLElement | null;
85
97
  sourcePosition: TPosition | null;
86
98
  sourceItem: TItem | null;
99
+ activePointerId: number | null;
87
100
  isDragging: boolean;
88
101
  lastHoveredElement: HTMLElement | null;
89
102
  };
@@ -103,8 +116,12 @@ declare class DragDropManager<TItem = unknown, TPosition = unknown> {
103
116
  private handlePointerMove;
104
117
  private handlePointerUp;
105
118
  private handlePointerCancel;
119
+ private handleKeyDown;
120
+ private handleMouseOut;
106
121
  private handleAutoScroll;
107
122
  private cleanup;
123
+ private getPointerId;
124
+ private isActivePointer;
108
125
  /**
109
126
  * Find closest ancestor element with matching data-kind attribute
110
127
  */
@@ -127,5 +144,24 @@ declare class DragDropManager<TItem = unknown, TPosition = unknown> {
127
144
  destroy(): void;
128
145
  }
129
146
  //#endregion
130
- export { type DragDropCallbacks, type DragDropConfig, DragDropManager, type PointerPosition };
147
+ //#region src/drag-preview-controller.d.ts
148
+ type DragPreviewControllerOptions = {
149
+ zIndex?: number;
150
+ opacity?: number;
151
+ centerOnCursor?: boolean;
152
+ className?: string | string[];
153
+ copyComputedStyles?: string[];
154
+ appendTo?: HTMLElement;
155
+ };
156
+ declare class DragPreviewController {
157
+ private options;
158
+ private previewElement;
159
+ constructor(options?: DragPreviewControllerOptions);
160
+ startFromElement(element: HTMLElement): void;
161
+ moveToPointer(pos: PointerPosition): void;
162
+ stop(): void;
163
+ destroy(): void;
164
+ }
165
+ //#endregion
166
+ export { type DragDropCallbacks, type DragDropConfig, DragDropManager, DragPreviewController, type DragPreviewControllerOptions, type PointerPosition };
131
167
  //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs CHANGED
@@ -8,6 +8,7 @@ var DragDropManager = class {
8
8
  sourceElement: null,
9
9
  sourcePosition: null,
10
10
  sourceItem: null,
11
+ activePointerId: null,
11
12
  isDragging: false,
12
13
  lastHoveredElement: null
13
14
  };
@@ -28,6 +29,8 @@ var DragDropManager = class {
28
29
  clickThreshold: config.clickThreshold ?? 10,
29
30
  scrollThreshold: config.scrollThreshold ?? 100,
30
31
  scrollSpeed: config.scrollSpeed ?? 10,
32
+ cancelOnEscape: config.cancelOnEscape ?? true,
33
+ cancelOnPointerLeave: config.cancelOnPointerLeave ?? true,
31
34
  draggableKind: config.draggableKind,
32
35
  droppableKind: config.droppableKind
33
36
  };
@@ -36,7 +39,9 @@ var DragDropManager = class {
36
39
  pointerDown: this.handlePointerDown.bind(this),
37
40
  pointerMove: this.handlePointerMove.bind(this),
38
41
  pointerUp: this.handlePointerUp.bind(this),
39
- pointerCancel: this.handlePointerCancel.bind(this)
42
+ pointerCancel: this.handlePointerCancel.bind(this),
43
+ keyDown: this.handleKeyDown.bind(this),
44
+ mouseOut: this.handleMouseOut.bind(this)
40
45
  };
41
46
  this.attachListeners();
42
47
  }
@@ -45,8 +50,13 @@ var DragDropManager = class {
45
50
  this.container.addEventListener("pointermove", this.boundHandlers.pointerMove);
46
51
  this.container.addEventListener("pointerup", this.boundHandlers.pointerUp);
47
52
  this.container.addEventListener("pointercancel", this.boundHandlers.pointerCancel);
53
+ window.addEventListener("pointerup", this.boundHandlers.pointerUp);
54
+ window.addEventListener("pointercancel", this.boundHandlers.pointerCancel);
55
+ window.addEventListener("keydown", this.boundHandlers.keyDown);
56
+ window.addEventListener("mouseout", this.boundHandlers.mouseOut);
48
57
  }
49
58
  handlePointerDown(e) {
59
+ if (this.state.startPosition) return;
50
60
  const target = e.target;
51
61
  const draggableElement = this.findElementByKind(target, this.config.draggableKind);
52
62
  if (!draggableElement) return;
@@ -64,8 +74,13 @@ var DragDropManager = class {
64
74
  this.state.sourceElement = draggableElement;
65
75
  this.state.sourcePosition = position;
66
76
  this.state.sourceItem = item;
77
+ this.state.activePointerId = this.getPointerId(e);
78
+ if (this.state.activePointerId !== null && typeof draggableElement.setPointerCapture === "function") try {
79
+ draggableElement.setPointerCapture(this.state.activePointerId);
80
+ } catch {}
67
81
  }
68
82
  handlePointerMove(e) {
83
+ if (!this.isActivePointer(e)) return;
69
84
  const currentPos = {
70
85
  x: e.clientX,
71
86
  y: e.clientY
@@ -100,6 +115,7 @@ var DragDropManager = class {
100
115
  });
101
116
  }
102
117
  handlePointerUp(e) {
118
+ if (!this.isActivePointer(e)) return;
103
119
  if (this.state.startPosition && !this.state.isDragging) {
104
120
  const currentPos = {
105
121
  x: e.clientX,
@@ -124,6 +140,18 @@ var DragDropManager = class {
124
140
  handlePointerCancel() {
125
141
  this.cleanup();
126
142
  }
143
+ handleKeyDown(e) {
144
+ if (!this.config.cancelOnEscape) return;
145
+ if (e.key !== "Escape") return;
146
+ if (!this.state.startPosition) return;
147
+ this.cleanup();
148
+ }
149
+ handleMouseOut(e) {
150
+ if (!this.config.cancelOnPointerLeave) return;
151
+ if (!this.state.startPosition) return;
152
+ if (e.relatedTarget !== null) return;
153
+ this.cleanup();
154
+ }
127
155
  handleAutoScroll(pos) {
128
156
  const { scrollThreshold, scrollSpeed } = this.config;
129
157
  const viewportWidth = window.innerWidth;
@@ -137,6 +165,9 @@ var DragDropManager = class {
137
165
  if (scrollX !== 0 || scrollY !== 0) window.scrollBy(scrollX, scrollY);
138
166
  }
139
167
  cleanup() {
168
+ 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 {
169
+ this.state.sourceElement.releasePointerCapture(this.state.activePointerId);
170
+ } catch {}
140
171
  if (this.state.sourceElement) this.state.sourceElement.removeAttribute("data-dragging");
141
172
  if (this.state.lastHoveredElement) this.state.lastHoveredElement.removeAttribute("data-hovered");
142
173
  if (this.rafId !== null) {
@@ -149,11 +180,20 @@ var DragDropManager = class {
149
180
  sourceElement: null,
150
181
  sourcePosition: null,
151
182
  sourceItem: null,
183
+ activePointerId: null,
152
184
  isDragging: false,
153
185
  lastHoveredElement: null
154
186
  };
155
187
  this.lastPointerPos = null;
156
188
  }
189
+ getPointerId(e) {
190
+ return typeof e.pointerId === "number" ? e.pointerId : null;
191
+ }
192
+ isActivePointer(e) {
193
+ if (this.state.activePointerId === null) return true;
194
+ const pointerId = this.getPointerId(e);
195
+ return pointerId === null || pointerId === this.state.activePointerId;
196
+ }
157
197
  /**
158
198
  * Find closest ancestor element with matching data-kind attribute
159
199
  */
@@ -192,9 +232,76 @@ var DragDropManager = class {
192
232
  this.container.removeEventListener("pointermove", this.boundHandlers.pointerMove);
193
233
  this.container.removeEventListener("pointerup", this.boundHandlers.pointerUp);
194
234
  this.container.removeEventListener("pointercancel", this.boundHandlers.pointerCancel);
235
+ window.removeEventListener("pointerup", this.boundHandlers.pointerUp);
236
+ window.removeEventListener("pointercancel", this.boundHandlers.pointerCancel);
237
+ window.removeEventListener("keydown", this.boundHandlers.keyDown);
238
+ window.removeEventListener("mouseout", this.boundHandlers.mouseOut);
239
+ }
240
+ };
241
+
242
+ //#endregion
243
+ //#region src/drag-preview-controller.ts
244
+ const OFFSCREEN_TRANSFORM = "translate(-9999px, -9999px)";
245
+ function normalizeClassNames(className) {
246
+ if (!className) return [];
247
+ return Array.isArray(className) ? className : [className];
248
+ }
249
+ var DragPreviewController = class {
250
+ options;
251
+ previewElement = null;
252
+ constructor(options = {}) {
253
+ this.options = {
254
+ zIndex: options.zIndex ?? 9999,
255
+ opacity: options.opacity ?? .95,
256
+ centerOnCursor: options.centerOnCursor ?? true,
257
+ className: options.className,
258
+ copyComputedStyles: options.copyComputedStyles,
259
+ appendTo: options.appendTo
260
+ };
261
+ }
262
+ startFromElement(element) {
263
+ this.stop();
264
+ const rect = element.getBoundingClientRect();
265
+ const clone = element.cloneNode(true);
266
+ const computed = window.getComputedStyle(element);
267
+ const appendTo = this.options.appendTo ?? document.body;
268
+ clone.setAttribute("aria-hidden", "true");
269
+ clone.style.position = "fixed";
270
+ clone.style.left = "0";
271
+ clone.style.top = "0";
272
+ clone.style.width = `${rect.width}px`;
273
+ clone.style.height = `${rect.height}px`;
274
+ clone.style.pointerEvents = "none";
275
+ clone.style.zIndex = String(this.options.zIndex);
276
+ clone.style.opacity = String(this.options.opacity);
277
+ clone.style.transform = OFFSCREEN_TRANSFORM;
278
+ clone.style.transition = "none";
279
+ clone.style.cursor = "grabbing";
280
+ clone.style.margin = "0";
281
+ clone.style.boxSizing = "border-box";
282
+ clone.style.background = computed.backgroundColor;
283
+ for (const classToken of normalizeClassNames(this.options.className)) clone.classList.add(classToken);
284
+ if (this.options.copyComputedStyles && this.options.copyComputedStyles.length > 0) for (const propertyName of this.options.copyComputedStyles) clone.style.setProperty(propertyName, computed.getPropertyValue(propertyName));
285
+ appendTo.appendChild(clone);
286
+ this.previewElement = clone;
287
+ }
288
+ moveToPointer(pos) {
289
+ if (!this.previewElement) return;
290
+ if (this.options.centerOnCursor) {
291
+ this.previewElement.style.transform = `translate(${pos.x}px, ${pos.y}px) translate(-50%, -50%)`;
292
+ return;
293
+ }
294
+ this.previewElement.style.transform = `translate(${pos.x}px, ${pos.y}px)`;
295
+ }
296
+ stop() {
297
+ this.previewElement?.remove();
298
+ this.previewElement = null;
299
+ }
300
+ destroy() {
301
+ this.stop();
195
302
  }
196
303
  };
197
304
 
198
305
  //#endregion
199
- export { DragDropManager };
306
+ export { DragDropManager, DragPreviewController };
200
307
  //# 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` 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 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 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\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 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 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 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 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 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 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 /**\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 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,52 @@
1
1
  {
2
2
  "name": "dnd-manager",
3
- "version": "1.0.0",
3
+ "version": "1.1.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": {
26
+ "@vitest/browser-playwright": "^4.0.18",
27
+ "@vitest/coverage-v8": "^4.0.18",
28
+ "jsdom": "^28.1.0",
29
+ "oxfmt": "^0.35.0",
34
30
  "prettier": "^3.8.1",
35
31
  "publint": "^0.3.17",
36
32
  "sort-package-json": "^3.6.1",
37
33
  "tsdown": "^0.20.3",
38
34
  "typescript": "^5.9.3",
39
35
  "vitest": "^4.0.18"
36
+ },
37
+ "scripts": {
38
+ "dev": "pnpm --filter dnd-manager-demo dev",
39
+ "build": "tsdown src/index.ts --dts --sourcemap --format esm --clean",
40
+ "build:watch": "pnpm build --watch",
41
+ "build:demo": "pnpm --filter dnd-manager-demo build",
42
+ "build:all": "pnpm build && pnpm build:demo",
43
+ "format": "oxfmt --write .",
44
+ "format:check": "oxfmt --check .",
45
+ "test": "vitest --run",
46
+ "test:coverage": "pnpm build && vitest --run --coverage",
47
+ "test:e2e": "pnpm build && vitest --project e2e",
48
+ "test:unit": "vitest --project unit",
49
+ "test:watch": "vitest --watch",
50
+ "typecheck": "tsc --noEmit"
40
51
  }
41
- }
52
+ }