snap-dnd 0.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/README.md +318 -0
- package/dist/behaviors/AutoScroll.d.ts +20 -0
- package/dist/behaviors/ConstraintAxis.d.ts +37 -0
- package/dist/behaviors/SnapGrid.d.ts +38 -0
- package/dist/behaviors/index.d.ts +3 -0
- package/dist/core/DragEngine.d.ts +60 -0
- package/dist/core/DragState.d.ts +67 -0
- package/dist/core/DropZone.d.ts +97 -0
- package/dist/core/Snap.d.ts +85 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/index.d.ts +62 -0
- package/dist/plugins/FileDrop.d.ts +33 -0
- package/dist/plugins/Kanban.d.ts +40 -0
- package/dist/plugins/Sortable.d.ts +33 -0
- package/dist/plugins/index.d.ts +3 -0
- package/dist/sensors/FileSensor.d.ts +51 -0
- package/dist/sensors/PointerSensor.d.ts +79 -0
- package/dist/sensors/index.d.ts +2 -0
- package/dist/snap.core.js +13 -0
- package/dist/snap.core.js.map +7 -0
- package/dist/snap.esm.js +49 -0
- package/dist/snap.esm.js.map +7 -0
- package/dist/snap.umd.js +49 -0
- package/dist/snap.umd.js.map +7 -0
- package/dist/types/index.d.ts +161 -0
- package/dist/utils/BoundsCache.d.ts +54 -0
- package/dist/utils/DataTransfer.d.ts +32 -0
- package/dist/utils/EventEmitter.d.ts +37 -0
- package/dist/utils/ObjectPool.d.ts +38 -0
- package/dist/utils/RAFThrottle.d.ts +39 -0
- package/dist/utils/index.d.ts +5 -0
- package/package.json +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
# Snap
|
|
2
|
+
|
|
3
|
+
A zero-dependency, memory-optimized drag and drop library for vanilla JavaScript and Web Components.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Zero dependencies** - Pure vanilla JavaScript
|
|
8
|
+
- **Tiny footprint** - Core ~5KB gzipped, Full ~9KB gzipped
|
|
9
|
+
- **Memory efficient** - Object pooling, event delegation, WeakMap caches
|
|
10
|
+
- **Web Component ready** - Works with Shadow DOM and Lit Elements
|
|
11
|
+
- **Touch support** - Mouse, touch, and pointer events
|
|
12
|
+
- **Beginner friendly** - Works with just data attributes
|
|
13
|
+
- **Highly customizable** - Plugin system, behaviors, comprehensive options
|
|
14
|
+
|
|
15
|
+
## Bundle Sizes
|
|
16
|
+
|
|
17
|
+
| Import | Minified | Gzipped |
|
|
18
|
+
|--------|----------|---------|
|
|
19
|
+
| `snap-dnd/core` | 17.6 KB | **~5 KB** |
|
|
20
|
+
| `snap-dnd` (full) | 35.3 KB | ~9 KB |
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install snap-dnd
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Core Only (Minimal ~5KB)
|
|
29
|
+
|
|
30
|
+
If you don't need plugins (Sortable, Kanban, FileDrop), import the core:
|
|
31
|
+
|
|
32
|
+
```javascript
|
|
33
|
+
import { Snap } from 'snap-dnd/core';
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Quick Start
|
|
37
|
+
|
|
38
|
+
### Declarative (Data Attributes)
|
|
39
|
+
|
|
40
|
+
The simplest way to use Snap - just add data attributes:
|
|
41
|
+
|
|
42
|
+
```html
|
|
43
|
+
<div id="container">
|
|
44
|
+
<div data-draggable>Drag me!</div>
|
|
45
|
+
<div data-draggable>Drag me too!</div>
|
|
46
|
+
<div data-droppable>Drop here</div>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<script type="module">
|
|
50
|
+
import { Snap } from 'snap-dnd';
|
|
51
|
+
|
|
52
|
+
const snap = new Snap(document.getElementById('container'), {
|
|
53
|
+
onDrop: (e) => console.log('Dropped!', e.element, 'into', e.dropZone)
|
|
54
|
+
});
|
|
55
|
+
</script>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Imperative (JavaScript)
|
|
59
|
+
|
|
60
|
+
For more control, use the imperative API:
|
|
61
|
+
|
|
62
|
+
```javascript
|
|
63
|
+
import { Snap } from 'snap-dnd';
|
|
64
|
+
|
|
65
|
+
const snap = new Snap(container, {
|
|
66
|
+
onDragStart: (e) => console.log('Started dragging', e.element),
|
|
67
|
+
onDragMove: (e) => console.log('Moving', e.position),
|
|
68
|
+
onDrop: (e) => console.log('Dropped', e.element, 'at index', e.insertionIndex),
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Add elements programmatically
|
|
72
|
+
snap.addDraggable(myElement, {
|
|
73
|
+
data: { id: 1, type: 'task' },
|
|
74
|
+
axis: 'y' // Only vertical movement
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
snap.addDropZone(myZone, {
|
|
78
|
+
accepts: ['task'],
|
|
79
|
+
onEnter: () => myZone.classList.add('highlight')
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Cleanup when done
|
|
83
|
+
snap.destroy();
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Data Attributes
|
|
87
|
+
|
|
88
|
+
| Attribute | Description |
|
|
89
|
+
|-----------|-------------|
|
|
90
|
+
| `data-draggable` | Makes element draggable |
|
|
91
|
+
| `data-droppable` | Makes element a drop zone |
|
|
92
|
+
| `data-drag-handle` | Only this element can initiate drag |
|
|
93
|
+
| `data-drag-axis="x\|y"` | Constrain to horizontal or vertical |
|
|
94
|
+
| `data-drag-id="..."` | Custom ID passed to callbacks |
|
|
95
|
+
| `data-drag-type="..."` | Type for drop zone filtering |
|
|
96
|
+
| `data-accepts="a,b,c"` | Types this drop zone accepts |
|
|
97
|
+
| `data-file-drop` | Enable file drop zone |
|
|
98
|
+
|
|
99
|
+
## Options
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
const snap = new Snap(container, {
|
|
103
|
+
// Selectors
|
|
104
|
+
draggableSelector: '[data-draggable]',
|
|
105
|
+
dropZoneSelector: '[data-droppable]',
|
|
106
|
+
handleSelector: '[data-drag-handle]',
|
|
107
|
+
|
|
108
|
+
// Behavior
|
|
109
|
+
axis: 'both', // 'x', 'y', or 'both'
|
|
110
|
+
grid: { x: 20, y: 20 }, // Snap to grid
|
|
111
|
+
delay: 0, // ms before drag starts
|
|
112
|
+
distance: 0, // px before drag starts
|
|
113
|
+
|
|
114
|
+
// Auto-scroll when near edges
|
|
115
|
+
autoScroll: true, // or { threshold: 40, maxSpeed: 15 }
|
|
116
|
+
|
|
117
|
+
// Callbacks
|
|
118
|
+
onDragStart: (e) => {},
|
|
119
|
+
onDragMove: (e) => {},
|
|
120
|
+
onDragEnd: (e) => {},
|
|
121
|
+
onDrop: (e) => {},
|
|
122
|
+
onDropZoneEnter: (e) => {},
|
|
123
|
+
onDropZoneLeave: (e) => {},
|
|
124
|
+
|
|
125
|
+
// Advanced
|
|
126
|
+
autoRefresh: false, // Auto-detect DOM changes
|
|
127
|
+
ghostClass: 'my-ghost',
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## With Lit Elements
|
|
132
|
+
|
|
133
|
+
Snap works seamlessly with Web Components:
|
|
134
|
+
|
|
135
|
+
```javascript
|
|
136
|
+
import { LitElement, html } from 'lit';
|
|
137
|
+
import { Snap } from 'snap-dnd';
|
|
138
|
+
|
|
139
|
+
class TaskBoard extends LitElement {
|
|
140
|
+
snap;
|
|
141
|
+
|
|
142
|
+
firstUpdated() {
|
|
143
|
+
this.snap = new Snap(this.shadowRoot, {
|
|
144
|
+
autoRefresh: true, // Handle Lit re-renders
|
|
145
|
+
onDrop: this.handleDrop.bind(this)
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
disconnectedCallback() {
|
|
150
|
+
super.disconnectedCallback();
|
|
151
|
+
this.snap?.destroy();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
handleDrop(e) {
|
|
155
|
+
// Update your state, Lit will re-render
|
|
156
|
+
this.tasks = reorder(this.tasks, e.insertionIndex);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
render() {
|
|
160
|
+
return html`
|
|
161
|
+
<ul>
|
|
162
|
+
${this.tasks.map(task => html`
|
|
163
|
+
<li data-draggable data-drag-id=${task.id}>${task.title}</li>
|
|
164
|
+
`)}
|
|
165
|
+
</ul>
|
|
166
|
+
`;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Plugins
|
|
172
|
+
|
|
173
|
+
### Sortable
|
|
174
|
+
|
|
175
|
+
Reorder items within a container:
|
|
176
|
+
|
|
177
|
+
```javascript
|
|
178
|
+
import { Snap, Sortable } from 'snap-dnd';
|
|
179
|
+
|
|
180
|
+
const snap = new Snap(container).use(new Sortable({
|
|
181
|
+
animation: 150,
|
|
182
|
+
ghostClass: 'sortable-ghost',
|
|
183
|
+
placeholderClass: 'sortable-placeholder'
|
|
184
|
+
}));
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Kanban
|
|
188
|
+
|
|
189
|
+
Move items between multiple containers:
|
|
190
|
+
|
|
191
|
+
```javascript
|
|
192
|
+
import { Snap, Kanban } from 'snap-dnd';
|
|
193
|
+
|
|
194
|
+
const snap = new Snap(board).use(new Kanban({
|
|
195
|
+
containers: '.column',
|
|
196
|
+
items: '.card',
|
|
197
|
+
animation: 150
|
|
198
|
+
}));
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### FileDrop
|
|
202
|
+
|
|
203
|
+
Handle file drops from desktop:
|
|
204
|
+
|
|
205
|
+
```javascript
|
|
206
|
+
import { Snap, FileDrop } from 'snap-dnd';
|
|
207
|
+
|
|
208
|
+
const snap = new Snap(container).use(
|
|
209
|
+
new FileDrop({
|
|
210
|
+
accept: ['image/*', '.pdf'],
|
|
211
|
+
multiple: true,
|
|
212
|
+
maxSize: 10 * 1024 * 1024 // 10MB
|
|
213
|
+
}).onFileDrop((e) => {
|
|
214
|
+
console.log('Files dropped:', e.files);
|
|
215
|
+
})
|
|
216
|
+
);
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Or use the standalone helper:
|
|
220
|
+
|
|
221
|
+
```javascript
|
|
222
|
+
import { createFileDropZone } from 'snap-dnd';
|
|
223
|
+
|
|
224
|
+
const cleanup = createFileDropZone(element, {
|
|
225
|
+
accept: ['image/*'],
|
|
226
|
+
onDrop: (files) => uploadFiles(files)
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Later: cleanup();
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Behaviors
|
|
233
|
+
|
|
234
|
+
Add optional behaviors for extra functionality:
|
|
235
|
+
|
|
236
|
+
```javascript
|
|
237
|
+
import { Snap, AutoScroll, SnapGrid } from 'snap-dnd';
|
|
238
|
+
|
|
239
|
+
const snap = new Snap(container)
|
|
240
|
+
.addBehavior(new AutoScroll({ threshold: 50, maxSpeed: 20 }))
|
|
241
|
+
.addBehavior(new SnapGrid({ x: 10, y: 10 }));
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## CSS
|
|
245
|
+
|
|
246
|
+
Snap doesn't inject any CSS. Add your own styles:
|
|
247
|
+
|
|
248
|
+
```css
|
|
249
|
+
/* Required for touch devices */
|
|
250
|
+
[data-draggable] {
|
|
251
|
+
touch-action: none;
|
|
252
|
+
user-select: none;
|
|
253
|
+
cursor: grab;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/* Optional visual feedback */
|
|
257
|
+
.snap-dragging { opacity: 0.5; }
|
|
258
|
+
.snap-drop-active { background: rgba(0,120,255,0.1); }
|
|
259
|
+
.snap-ghost { box-shadow: 0 4px 12px rgba(0,0,0,0.15); }
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
See `examples/snap.css` for a complete reference stylesheet.
|
|
263
|
+
|
|
264
|
+
## API Reference
|
|
265
|
+
|
|
266
|
+
### Snap Instance
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
interface Snap {
|
|
270
|
+
enable(): void;
|
|
271
|
+
disable(): void;
|
|
272
|
+
destroy(): void;
|
|
273
|
+
refresh(): void;
|
|
274
|
+
|
|
275
|
+
addDraggable(element: HTMLElement, options?: ItemOptions): void;
|
|
276
|
+
removeDraggable(element: HTMLElement): void;
|
|
277
|
+
addDropZone(element: HTMLElement, options?: DropZoneOptions): void;
|
|
278
|
+
removeDropZone(element: HTMLElement): void;
|
|
279
|
+
|
|
280
|
+
isDragging(): boolean;
|
|
281
|
+
getActiveElement(): HTMLElement | null;
|
|
282
|
+
|
|
283
|
+
use(plugin: Plugin): this;
|
|
284
|
+
addBehavior(behavior: Behavior): this;
|
|
285
|
+
setOptions(options: Partial<SnapOptions>): void;
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Event Objects
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
interface DragStartEvent {
|
|
293
|
+
element: HTMLElement;
|
|
294
|
+
position: { x: number; y: number };
|
|
295
|
+
data: DataTransfer;
|
|
296
|
+
cancel(): void;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
interface DropEvent {
|
|
300
|
+
element: HTMLElement;
|
|
301
|
+
dropZone: HTMLElement;
|
|
302
|
+
position: { x: number; y: number };
|
|
303
|
+
data: DataTransfer;
|
|
304
|
+
insertionIndex?: number;
|
|
305
|
+
sourceContainer?: HTMLElement;
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## Browser Support
|
|
310
|
+
|
|
311
|
+
- Chrome 88+
|
|
312
|
+
- Firefox 85+
|
|
313
|
+
- Safari 14+
|
|
314
|
+
- Edge 88+
|
|
315
|
+
|
|
316
|
+
## License
|
|
317
|
+
|
|
318
|
+
MIT
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AutoScroll behavior - scrolls containers when dragging near edges
|
|
3
|
+
*/
|
|
4
|
+
import type { Behavior, DragSession, AutoScrollOptions } from '../types/index.js';
|
|
5
|
+
export declare class AutoScroll implements Behavior {
|
|
6
|
+
name: string;
|
|
7
|
+
private _options;
|
|
8
|
+
private _scrollableAncestors;
|
|
9
|
+
private _rafId;
|
|
10
|
+
private _active;
|
|
11
|
+
constructor(options?: AutoScrollOptions);
|
|
12
|
+
onDragStart(session: DragSession): void;
|
|
13
|
+
onDragMove(session: DragSession): void;
|
|
14
|
+
onDragEnd(): void;
|
|
15
|
+
destroy(): void;
|
|
16
|
+
private _performScroll;
|
|
17
|
+
private _calculateScrollSpeed;
|
|
18
|
+
private _getSpeed;
|
|
19
|
+
private _findScrollableAncestors;
|
|
20
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ConstraintAxis behavior - constrain movement to specific axis
|
|
3
|
+
* Note: Basic axis constraint is handled in DragEngine via options.axis
|
|
4
|
+
* This behavior adds additional constraint features like bounds
|
|
5
|
+
*/
|
|
6
|
+
import type { Behavior, DragSession, Axis, Point } from '../types/index.js';
|
|
7
|
+
export interface ConstraintOptions {
|
|
8
|
+
axis?: Axis;
|
|
9
|
+
/** Bounding rectangle to constrain within */
|
|
10
|
+
bounds?: {
|
|
11
|
+
minX?: number;
|
|
12
|
+
maxX?: number;
|
|
13
|
+
minY?: number;
|
|
14
|
+
maxY?: number;
|
|
15
|
+
};
|
|
16
|
+
/** Constrain to parent element bounds */
|
|
17
|
+
containWithinParent?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare class ConstraintAxis implements Behavior {
|
|
20
|
+
name: string;
|
|
21
|
+
private _options;
|
|
22
|
+
private _bounds;
|
|
23
|
+
constructor(options?: ConstraintOptions);
|
|
24
|
+
onDragStart(session: DragSession): void;
|
|
25
|
+
onDragMove(_session: DragSession): void;
|
|
26
|
+
onDragEnd(): void;
|
|
27
|
+
destroy(): void;
|
|
28
|
+
/**
|
|
29
|
+
* Apply constraints to a point
|
|
30
|
+
*/
|
|
31
|
+
constrain(point: Point, origin: Point): Point;
|
|
32
|
+
/**
|
|
33
|
+
* Get current bounds
|
|
34
|
+
*/
|
|
35
|
+
getBounds(): typeof this._bounds;
|
|
36
|
+
private _calculateParentBounds;
|
|
37
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SnapGrid behavior - snaps drag position to a grid
|
|
3
|
+
* Note: This is handled in DragEngine via options.grid
|
|
4
|
+
* This behavior provides additional grid-related features
|
|
5
|
+
*/
|
|
6
|
+
import type { Behavior, DragSession, GridOptions } from '../types/index.js';
|
|
7
|
+
export interface SnapGridOptions extends GridOptions {
|
|
8
|
+
/** Only snap when within threshold of grid line */
|
|
9
|
+
threshold?: number;
|
|
10
|
+
/** Show visual grid guides */
|
|
11
|
+
showGuides?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare class SnapGrid implements Behavior {
|
|
14
|
+
name: string;
|
|
15
|
+
private _options;
|
|
16
|
+
private _guideContainer;
|
|
17
|
+
constructor(options: SnapGridOptions);
|
|
18
|
+
onDragStart(session: DragSession): void;
|
|
19
|
+
onDragMove(_session: DragSession): void;
|
|
20
|
+
onDragEnd(): void;
|
|
21
|
+
destroy(): void;
|
|
22
|
+
/**
|
|
23
|
+
* Snap a point to the grid
|
|
24
|
+
*/
|
|
25
|
+
snap(x: number, y: number): {
|
|
26
|
+
x: number;
|
|
27
|
+
y: number;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Snap with threshold - only snap when close to grid line
|
|
31
|
+
*/
|
|
32
|
+
snapWithThreshold(x: number, y: number, threshold?: number): {
|
|
33
|
+
x: number;
|
|
34
|
+
y: number;
|
|
35
|
+
};
|
|
36
|
+
private _createGuides;
|
|
37
|
+
private _removeGuides;
|
|
38
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DragEngine orchestrates the drag operation
|
|
3
|
+
* Coordinates between sensors, state, and drop zones
|
|
4
|
+
*/
|
|
5
|
+
import type { SnapOptions, Axis } from '../types/index.js';
|
|
6
|
+
import { DragState } from './DragState.js';
|
|
7
|
+
export interface DragEngineOptions {
|
|
8
|
+
container: HTMLElement | ShadowRoot;
|
|
9
|
+
state: DragState;
|
|
10
|
+
options: SnapOptions;
|
|
11
|
+
getDropZones: () => HTMLElement[];
|
|
12
|
+
getItemData: (element: HTMLElement) => Record<string, unknown> | undefined;
|
|
13
|
+
getItemAxis: (element: HTMLElement) => Axis | undefined;
|
|
14
|
+
}
|
|
15
|
+
export declare class DragEngine {
|
|
16
|
+
private _container;
|
|
17
|
+
private _state;
|
|
18
|
+
private _options;
|
|
19
|
+
private _getDropZones;
|
|
20
|
+
private _getItemData;
|
|
21
|
+
private _getItemAxis;
|
|
22
|
+
private _pointerSensor;
|
|
23
|
+
private _enabled;
|
|
24
|
+
private _ghost;
|
|
25
|
+
private _ghostOffset;
|
|
26
|
+
constructor(engineOptions: DragEngineOptions);
|
|
27
|
+
/**
|
|
28
|
+
* Enable drag engine
|
|
29
|
+
*/
|
|
30
|
+
enable(): void;
|
|
31
|
+
/**
|
|
32
|
+
* Disable drag engine
|
|
33
|
+
*/
|
|
34
|
+
disable(): void;
|
|
35
|
+
/**
|
|
36
|
+
* Check if enabled
|
|
37
|
+
*/
|
|
38
|
+
get isEnabled(): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Update options
|
|
41
|
+
*/
|
|
42
|
+
updateOptions(options: Partial<SnapOptions>): void;
|
|
43
|
+
private _setupListeners;
|
|
44
|
+
private _onPointerDown;
|
|
45
|
+
private _onPointerMove;
|
|
46
|
+
private _onPointerUp;
|
|
47
|
+
private _onPointerCancel;
|
|
48
|
+
private _applyAxisConstraint;
|
|
49
|
+
private _applyGridSnap;
|
|
50
|
+
private _updateDropZone;
|
|
51
|
+
private _createGhost;
|
|
52
|
+
private _updateGhost;
|
|
53
|
+
private _removeGhost;
|
|
54
|
+
private _cleanup;
|
|
55
|
+
private _extractDataAttributes;
|
|
56
|
+
/**
|
|
57
|
+
* Cleanup
|
|
58
|
+
*/
|
|
59
|
+
destroy(): void;
|
|
60
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized drag state management
|
|
3
|
+
* Single source of truth for the current drag session
|
|
4
|
+
*/
|
|
5
|
+
import type { DragSession, Point, StateEvent, Unsubscribe } from '../types/index.js';
|
|
6
|
+
import { EventEmitter } from '../utils/EventEmitter.js';
|
|
7
|
+
interface StateEvents {
|
|
8
|
+
dragstart: DragSession;
|
|
9
|
+
dragmove: DragSession;
|
|
10
|
+
dragend: DragSession;
|
|
11
|
+
dropzoneenter: DragSession;
|
|
12
|
+
dropzoneleave: DragSession;
|
|
13
|
+
drop: DragSession;
|
|
14
|
+
}
|
|
15
|
+
export declare class DragState extends EventEmitter<StateEvents> {
|
|
16
|
+
private _session;
|
|
17
|
+
private _idCounter;
|
|
18
|
+
/**
|
|
19
|
+
* Get current drag session (null if not dragging)
|
|
20
|
+
*/
|
|
21
|
+
get session(): DragSession | null;
|
|
22
|
+
/**
|
|
23
|
+
* Check if currently dragging
|
|
24
|
+
*/
|
|
25
|
+
isDragging(): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Get the element being dragged
|
|
28
|
+
*/
|
|
29
|
+
getActiveElement(): HTMLElement | null;
|
|
30
|
+
/**
|
|
31
|
+
* Get current drop zone
|
|
32
|
+
*/
|
|
33
|
+
getCurrentDropZone(): HTMLElement | null;
|
|
34
|
+
/**
|
|
35
|
+
* Start a new drag session
|
|
36
|
+
*/
|
|
37
|
+
startDrag(element: HTMLElement, origin: Point, initialData?: Record<string, unknown>): DragSession;
|
|
38
|
+
/**
|
|
39
|
+
* Update position during drag
|
|
40
|
+
*/
|
|
41
|
+
updatePosition(point: Point): void;
|
|
42
|
+
/**
|
|
43
|
+
* Set or clear drop zone target
|
|
44
|
+
*/
|
|
45
|
+
setDropTarget(zone: HTMLElement | null): void;
|
|
46
|
+
/**
|
|
47
|
+
* Complete the drag with a drop
|
|
48
|
+
*/
|
|
49
|
+
endDrag(): DragSession | null;
|
|
50
|
+
/**
|
|
51
|
+
* Cancel the current drag
|
|
52
|
+
*/
|
|
53
|
+
cancelDrag(): DragSession | null;
|
|
54
|
+
/**
|
|
55
|
+
* Subscribe to state changes
|
|
56
|
+
*/
|
|
57
|
+
subscribe(event: StateEvent, callback: (session: DragSession) => void): Unsubscribe;
|
|
58
|
+
/**
|
|
59
|
+
* Reset state
|
|
60
|
+
*/
|
|
61
|
+
reset(): void;
|
|
62
|
+
/**
|
|
63
|
+
* Cleanup
|
|
64
|
+
*/
|
|
65
|
+
destroy(): void;
|
|
66
|
+
}
|
|
67
|
+
export {};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DropZone manages individual drop target areas
|
|
3
|
+
* Handles hit testing and insertion index calculation
|
|
4
|
+
*/
|
|
5
|
+
import type { DataTransfer, DropZoneOptions } from '../types/index.js';
|
|
6
|
+
export declare class DropZone {
|
|
7
|
+
private _element;
|
|
8
|
+
private _options;
|
|
9
|
+
private _isActive;
|
|
10
|
+
constructor(element: HTMLElement, options?: DropZoneOptions);
|
|
11
|
+
/**
|
|
12
|
+
* The DOM element for this drop zone
|
|
13
|
+
*/
|
|
14
|
+
get element(): HTMLElement;
|
|
15
|
+
/**
|
|
16
|
+
* Whether this zone is currently active (being hovered)
|
|
17
|
+
*/
|
|
18
|
+
get isActive(): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Update options
|
|
21
|
+
*/
|
|
22
|
+
setOptions(options: Partial<DropZoneOptions>): void;
|
|
23
|
+
/**
|
|
24
|
+
* Check if a point is inside this drop zone
|
|
25
|
+
*/
|
|
26
|
+
containsPoint(x: number, y: number): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Check if this zone accepts the given data
|
|
29
|
+
*/
|
|
30
|
+
accepts(data: DataTransfer): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Set active state and add/remove CSS class
|
|
33
|
+
*/
|
|
34
|
+
setActive(active: boolean): void;
|
|
35
|
+
/**
|
|
36
|
+
* Calculate insertion index for sortable behavior
|
|
37
|
+
* Returns the index where an item should be inserted based on position
|
|
38
|
+
*/
|
|
39
|
+
getInsertionIndex(x: number, y: number, itemSelector: string, excludeElement?: HTMLElement): number;
|
|
40
|
+
/**
|
|
41
|
+
* Get closest item to a point (for visual feedback)
|
|
42
|
+
*/
|
|
43
|
+
getClosestItem(x: number, y: number, itemSelector: string, excludeElement?: HTMLElement): {
|
|
44
|
+
element: HTMLElement;
|
|
45
|
+
position: 'before' | 'after';
|
|
46
|
+
} | null;
|
|
47
|
+
/**
|
|
48
|
+
* Force update cached bounds
|
|
49
|
+
*/
|
|
50
|
+
updateBounds(): void;
|
|
51
|
+
/**
|
|
52
|
+
* Cleanup
|
|
53
|
+
*/
|
|
54
|
+
destroy(): void;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* DropZoneManager handles multiple drop zones
|
|
58
|
+
*/
|
|
59
|
+
export declare class DropZoneManager {
|
|
60
|
+
private _zones;
|
|
61
|
+
/**
|
|
62
|
+
* Register a drop zone
|
|
63
|
+
*/
|
|
64
|
+
register(element: HTMLElement, options?: DropZoneOptions): DropZone;
|
|
65
|
+
/**
|
|
66
|
+
* Unregister a drop zone
|
|
67
|
+
*/
|
|
68
|
+
unregister(element: HTMLElement): void;
|
|
69
|
+
/**
|
|
70
|
+
* Get drop zone for an element
|
|
71
|
+
*/
|
|
72
|
+
get(element: HTMLElement): DropZone | undefined;
|
|
73
|
+
/**
|
|
74
|
+
* Get all drop zone elements
|
|
75
|
+
*/
|
|
76
|
+
getElements(): HTMLElement[];
|
|
77
|
+
/**
|
|
78
|
+
* Get all drop zones
|
|
79
|
+
*/
|
|
80
|
+
getAll(): DropZone[];
|
|
81
|
+
/**
|
|
82
|
+
* Find drop zone at point
|
|
83
|
+
*/
|
|
84
|
+
findAtPoint(x: number, y: number): DropZone | null;
|
|
85
|
+
/**
|
|
86
|
+
* Clear all drop zones
|
|
87
|
+
*/
|
|
88
|
+
clear(): void;
|
|
89
|
+
/**
|
|
90
|
+
* Update all bounds (e.g., after scroll/resize)
|
|
91
|
+
*/
|
|
92
|
+
updateAllBounds(): void;
|
|
93
|
+
/**
|
|
94
|
+
* Cleanup
|
|
95
|
+
*/
|
|
96
|
+
destroy(): void;
|
|
97
|
+
}
|