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 +0 -0
- package/README.md +98 -17
- package/dist/index.d.mts +37 -1
- package/dist/index.mjs +109 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +30 -19
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
|
|
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
|
|
6
|
-
|
|
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. [
|
|
11
|
-
2. [
|
|
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 '
|
|
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 '
|
|
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
|
-
- `
|
|
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
|
-
|
|
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
|
package/dist/index.mjs.map
CHANGED
|
@@ -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.
|
|
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
|
-
"
|
|
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
|
+
}
|