@marsio/vue-draggable 1.0.9 → 1.0.10
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 +143 -3
- package/build/cjs/Draggable.js +278 -70
- package/build/cjs/DraggableCore.js +884 -74
- package/build/cjs/utils/domFns.js +46 -16
- package/build/cjs/utils/noop.js +9 -0
- package/build/cjs/utils/positionFns.js +78 -40
- package/build/cjs/utils/shims.js +2 -2
- package/build/web/vue-draggable.min.js +1 -1399
- package/package.json +4 -3
- package/typings/index.d.ts +80 -47
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ Draggable and DraggableCore are **Vue3** components designed to make elements dr
|
|
|
6
6
|
<img src="https://user-images.githubusercontent.com/6365230/95649276-f3a02480-0b06-11eb-8504-e0614a780ba4.gif" />
|
|
7
7
|
</p>
|
|
8
8
|
|
|
9
|
-
[**[Demo](
|
|
9
|
+
[**[Demo](https://marsio.top/vue-draggable/) | [Changelog](/CHANGELOG.md) | [View Example](/example/example.js) | [MCP Server](./mcp/README.md)**]
|
|
10
10
|
|
|
11
11
|
## ✨ Features
|
|
12
12
|
- **Compatibility**: Compatible with server-rendered apps, PC, and mobile devices.
|
|
@@ -17,7 +17,62 @@ Draggable and DraggableCore are **Vue3** components designed to make elements dr
|
|
|
17
17
|
- **Position Offset**: Supports an offset for the draggable position (`positionOffset`), enabling adjustments to the element's position without altering its actual position properties.
|
|
18
18
|
- **Grid Snapping**: Allows the draggable element to snap to a grid (`grid` prop), facilitating alignment and precise placement during dragging.
|
|
19
19
|
- **Accessibility and Interaction**: Includes props for disabling the draggable functionality (`disabled`), allowing any mouse button to initiate dragging (`allowAnyClick`), and enabling a hack to prevent text selection during drag (`enableUserSelectHack`), enhancing usability and accessibility.
|
|
20
|
+
## 🤖 AI IDE Integration (MCP Server)
|
|
20
21
|
|
|
22
|
+
**Supercharge your AI-powered development workflow!** Vue-Draggable now includes an official **Model Context Protocol (MCP) Server** that provides your AI assistant with comprehensive, real-time access to all component APIs, props, and examples.
|
|
23
|
+
|
|
24
|
+
### ✨ What is MCP?
|
|
25
|
+
|
|
26
|
+
The Model Context Protocol enables AI assistants like Claude Desktop, Cursor, and other AI IDEs to access live, accurate documentation and code examples directly from the source. No more outdated docs or hallucinated APIs!
|
|
27
|
+
|
|
28
|
+
### 🚀 Key Benefits
|
|
29
|
+
|
|
30
|
+
- **🎯 Accurate API Usage**: AI gets real-time access to all props, types, and default values
|
|
31
|
+
- **📚 Contextual Examples**: Intelligent code suggestions based on actual component usage
|
|
32
|
+
- **🔄 Always Up-to-Date**: Documentation syncs automatically with code changes
|
|
33
|
+
- **⚡ Enhanced Productivity**: Write draggable components faster with AI assistance
|
|
34
|
+
|
|
35
|
+
### 📋 Quick Setup
|
|
36
|
+
|
|
37
|
+
#### For Cursor IDE:
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"mcpServers": {
|
|
41
|
+
"vue-draggable": {
|
|
42
|
+
"command": "npx",
|
|
43
|
+
"args": ["@marsio/vue-draggable-mcp"]
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
#### For Claude Desktop:
|
|
50
|
+
Add to your Claude Desktop configuration:
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"mcpServers": {
|
|
54
|
+
"vue-draggable": {
|
|
55
|
+
"command": "npx",
|
|
56
|
+
"args": ["@marsio/vue-draggable-mcp"]
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 🛠️ Available Tools
|
|
63
|
+
|
|
64
|
+
- **`get_vue_draggable_docs`**: Complete documentation in Markdown format
|
|
65
|
+
- **`list_vue_draggable_props`**: Structured props information with types and defaults
|
|
66
|
+
- **`get_vue_draggable_type`**: Specific type definitions and interfaces
|
|
67
|
+
|
|
68
|
+
### 💡 Example AI Prompts
|
|
69
|
+
|
|
70
|
+
After setup, try asking your AI assistant:
|
|
71
|
+
- *"Create a draggable card component with grid snapping"*
|
|
72
|
+
- *"Show me all available props for Draggable component"*
|
|
73
|
+
- *"How do I constrain dragging to horizontal axis only?"*
|
|
74
|
+
|
|
75
|
+
[Learn more about MCP setup →](./mcp/README.md)
|
|
21
76
|
## 📦 Quick Start
|
|
22
77
|
|
|
23
78
|
To quickly start using `@marsio/vue-draggable`, follow the steps below:
|
|
@@ -98,6 +153,7 @@ A simple component for making elements draggable.
|
|
|
98
153
|
#### Technical Documentation
|
|
99
154
|
|
|
100
155
|
- [Installing](#installing)
|
|
156
|
+
- [AI IDE Integration (MCP)](#🤖-ai-ide-integration-mcp-server)
|
|
101
157
|
- [Exports](#exports)
|
|
102
158
|
- [Draggable](#draggable)
|
|
103
159
|
- [Draggable Usage](#draggable-usage)
|
|
@@ -204,8 +260,8 @@ type DraggableData = {
|
|
|
204
260
|
// If set to `true`, will allow dragging on non left-button clicks.
|
|
205
261
|
allowAnyClick: boolean,
|
|
206
262
|
|
|
207
|
-
// Determines which axis the draggable can move.
|
|
208
|
-
//
|
|
263
|
+
// Determines which axis the draggable can move.
|
|
264
|
+
// Disabled axis movement is ignored and callbacks will report 0 deltas.
|
|
209
265
|
// Accepted values:
|
|
210
266
|
// - `both` allows movement horizontally and vertically (default).
|
|
211
267
|
// - `x` limits movement to horizontal axis.
|
|
@@ -213,6 +269,12 @@ allowAnyClick: boolean,
|
|
|
213
269
|
// - 'none' stops all movement.
|
|
214
270
|
axis: string,
|
|
215
271
|
|
|
272
|
+
// If true, lock to the dominant axis after the drag starts (prevents diagonal drift).
|
|
273
|
+
directionLock: boolean,
|
|
274
|
+
|
|
275
|
+
// Distance (px) before directionLock chooses an axis.
|
|
276
|
+
directionLockThreshold: number,
|
|
277
|
+
|
|
216
278
|
// Specifies movement boundaries. Accepted values:
|
|
217
279
|
// - `parent` restricts movement within the node's offsetParent
|
|
218
280
|
// (nearest node with position relative or absolute), or
|
|
@@ -227,6 +289,10 @@ bounds: {left?: number, top?: number, right?: number, bottom?: number} | string,
|
|
|
227
289
|
// Example: '.body'
|
|
228
290
|
cancel: string,
|
|
229
291
|
|
|
292
|
+
// If true, prevents dragging from starting on common interactive elements
|
|
293
|
+
// (inputs, buttons, links, contenteditable).
|
|
294
|
+
cancelInteractiveElements: boolean,
|
|
295
|
+
|
|
230
296
|
// Class names for draggable UI.
|
|
231
297
|
// Default to 'vue-draggable', 'vue-draggable-dragging', and 'vue-draggable-dragged'
|
|
232
298
|
defaultClassName: string,
|
|
@@ -242,6 +308,48 @@ defaultPosition: {x: number, y: number},
|
|
|
242
308
|
// If true, will not call any drag handlers.
|
|
243
309
|
disabled: boolean,
|
|
244
310
|
|
|
311
|
+
// If true, do not preventDefault() during touch drags, allowing the page/containers to scroll.
|
|
312
|
+
allowMobileScroll: boolean,
|
|
313
|
+
|
|
314
|
+
// If true, auto-scroll nearest scrollable container (and window) when the pointer is near an edge.
|
|
315
|
+
autoScroll: boolean,
|
|
316
|
+
|
|
317
|
+
// Distance (px) from an edge to start auto-scrolling.
|
|
318
|
+
autoScrollThreshold: number,
|
|
319
|
+
|
|
320
|
+
// Max auto-scroll speed (px per frame).
|
|
321
|
+
autoScrollMaxSpeed: number,
|
|
322
|
+
|
|
323
|
+
// Auto-scroll axis.
|
|
324
|
+
autoScrollAxis: 'both' | 'x' | 'y' | 'none',
|
|
325
|
+
|
|
326
|
+
// If false, never auto-scroll the window.
|
|
327
|
+
autoScrollIncludeWindow: boolean,
|
|
328
|
+
|
|
329
|
+
// Optional: specify which container(s) to auto-scroll.
|
|
330
|
+
// Supports selector string, element, 'window', or an array of those.
|
|
331
|
+
autoScrollContainer: string | HTMLElement | Window | Array<string | HTMLElement | Window> | null,
|
|
332
|
+
|
|
333
|
+
// Minimum distance in pixels before the drag starts.
|
|
334
|
+
// Useful to prevent accidental drags on click/tap.
|
|
335
|
+
dragStartThreshold: number,
|
|
336
|
+
|
|
337
|
+
// Touch-only: delay (ms) before drag can start (long-press activation).
|
|
338
|
+
dragStartDelay: number,
|
|
339
|
+
|
|
340
|
+
// Touch-only: movement tolerance (px) allowed during dragStartDelay before cancelling the drag start.
|
|
341
|
+
dragStartDelayTolerance: number,
|
|
342
|
+
|
|
343
|
+
// If true, suppress the click event fired after a drag.
|
|
344
|
+
enableClickSuppression: boolean,
|
|
345
|
+
|
|
346
|
+
// How long (ms) to suppress the next click after drag stop.
|
|
347
|
+
clickSuppressionDuration: number,
|
|
348
|
+
|
|
349
|
+
// If true, coalesce drag updates to requestAnimationFrame (at most once per frame).
|
|
350
|
+
// This significantly reduces work under high-frequency move events.
|
|
351
|
+
useRafDrag: boolean,
|
|
352
|
+
|
|
245
353
|
// Specifies the x and y that dragging should snap to.
|
|
246
354
|
grid: [number, number],
|
|
247
355
|
|
|
@@ -290,6 +398,24 @@ scale: number
|
|
|
290
398
|
Note that sending `class`, `style`, or `transform` as properties will error - set them on the child element
|
|
291
399
|
directly.
|
|
292
400
|
|
|
401
|
+
## Auto Scroll
|
|
402
|
+
|
|
403
|
+
Enable auto-scroll while dragging near an edge:
|
|
404
|
+
|
|
405
|
+
```vue
|
|
406
|
+
<Draggable :autoScroll="true" :autoScrollThreshold="40" :autoScrollMaxSpeed="24" />
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
Scroll only a specific container (and never the window):
|
|
410
|
+
|
|
411
|
+
```vue
|
|
412
|
+
<Draggable :autoScroll="true" autoScrollContainer=".scroll-pane" :autoScrollIncludeWindow="false" />
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
## Pointer Events
|
|
416
|
+
|
|
417
|
+
When supported, `<DraggableCore>` uses Pointer Events (with `setPointerCapture()` for more robust drags outside the element).
|
|
418
|
+
For touch pointers, Pointer Events require `touch-action` to be non-`auto` (e.g. `touch-action: none`); otherwise it falls back to Touch Events.
|
|
293
419
|
|
|
294
420
|
## Controlled vs. Uncontrolled
|
|
295
421
|
|
|
@@ -326,7 +452,21 @@ on itself and thus must have callbacks attached to be useful.
|
|
|
326
452
|
allowAnyClick: boolean,
|
|
327
453
|
cancel: string,
|
|
328
454
|
disabled: boolean,
|
|
455
|
+
allowMobileScroll: boolean,
|
|
456
|
+
autoScroll: boolean,
|
|
457
|
+
autoScrollThreshold: number,
|
|
458
|
+
autoScrollMaxSpeed: number,
|
|
459
|
+
autoScrollAxis: 'both' | 'x' | 'y' | 'none',
|
|
460
|
+
autoScrollIncludeWindow: boolean,
|
|
461
|
+
autoScrollContainer: string | HTMLElement | Window | Array<string | HTMLElement | Window> | null,
|
|
462
|
+
cancelInteractiveElements: boolean,
|
|
463
|
+
enableClickSuppression: boolean,
|
|
464
|
+
clickSuppressionDuration: number,
|
|
465
|
+
dragStartDelay: number,
|
|
466
|
+
dragStartDelayTolerance: number,
|
|
329
467
|
enableUserSelectHack: boolean,
|
|
468
|
+
dragStartThreshold: number,
|
|
469
|
+
useRafDrag: boolean,
|
|
330
470
|
offsetParent: HTMLElement,
|
|
331
471
|
grid: [number, number],
|
|
332
472
|
handle: string,
|
package/build/cjs/Draggable.js
CHANGED
|
@@ -11,15 +11,14 @@ Object.defineProperty(exports, "DraggableCore", {
|
|
|
11
11
|
});
|
|
12
12
|
exports.draggableProps = exports.default = void 0;
|
|
13
13
|
var _vue = require("vue");
|
|
14
|
-
var _get = _interopRequireDefault(require("lodash/get"));
|
|
15
14
|
var _clsx = _interopRequireDefault(require("clsx"));
|
|
16
15
|
var _domFns = require("./utils/domFns");
|
|
17
16
|
var _positionFns = require("./utils/positionFns");
|
|
18
17
|
var _shims = require("./utils/shims");
|
|
19
18
|
var _DraggableCore = _interopRequireWildcard(require("./DraggableCore"));
|
|
20
19
|
var _log = _interopRequireDefault(require("./utils/log"));
|
|
21
|
-
|
|
22
|
-
function _interopRequireWildcard(e, r) { if (!
|
|
20
|
+
var _noop = _interopRequireDefault(require("./utils/noop"));
|
|
21
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
23
22
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
24
23
|
/**
|
|
25
24
|
* Draggable is a Vue component that allows elements to be dragged and dropped.
|
|
@@ -56,15 +55,21 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
|
|
|
56
55
|
* Note:
|
|
57
56
|
* This component requires Vue 3 and is designed to work within a Vue 3 application.
|
|
58
57
|
*/
|
|
59
|
-
|
|
60
|
-
return typeof s === 'function' || Object.prototype.toString.call(s) === '[object Object]' && !(0, _vue.isVNode)(s);
|
|
61
|
-
}
|
|
58
|
+
|
|
62
59
|
const draggableProps = exports.draggableProps = {
|
|
63
60
|
..._DraggableCore.draggableCoreProps,
|
|
64
61
|
axis: {
|
|
65
62
|
type: String,
|
|
66
63
|
default: 'both'
|
|
67
64
|
},
|
|
65
|
+
directionLock: {
|
|
66
|
+
type: Boolean,
|
|
67
|
+
default: false
|
|
68
|
+
},
|
|
69
|
+
directionLockThreshold: {
|
|
70
|
+
type: Number,
|
|
71
|
+
default: 4
|
|
72
|
+
},
|
|
68
73
|
bounds: {
|
|
69
74
|
type: [Object, String, Boolean],
|
|
70
75
|
default: false
|
|
@@ -113,31 +118,86 @@ const Draggable = exports.default = (0, _vue.defineComponent)({
|
|
|
113
118
|
let {
|
|
114
119
|
slots
|
|
115
120
|
} = _ref;
|
|
116
|
-
|
|
117
|
-
const rootElement = (0, _vue.ref)(null);
|
|
118
|
-
if (props.position && !(props.dragFn || props.stopFn)) {
|
|
119
|
-
// eslint-disable-next-line no-console
|
|
120
|
-
console.warn('A `position` was applied to this <Draggable>, without drag handlers. This will make this ' + 'component effectively undraggable. Please attach `dragFn` or `stopFn` handlers so you can adjust the ' + '`position` of this element.');
|
|
121
|
-
}
|
|
121
|
+
const localNodeRef = (0, _vue.ref)(null);
|
|
122
122
|
const state = (0, _vue.reactive)({
|
|
123
123
|
// Whether or not we are currently dragging.
|
|
124
124
|
dragging: false,
|
|
125
125
|
// Whether or not we have been dragged before.
|
|
126
126
|
dragged: false,
|
|
127
127
|
// Current transform x and y.
|
|
128
|
-
x: props.position
|
|
129
|
-
y: props.position
|
|
130
|
-
prevPropsPosition: {
|
|
131
|
-
...props.position
|
|
132
|
-
},
|
|
128
|
+
x: props.position?.x ?? props.defaultPosition.x ?? 0,
|
|
129
|
+
y: props.position?.y ?? props.defaultPosition.y ?? 0,
|
|
133
130
|
// Used for compensating for out-of-bounds drags
|
|
134
131
|
slackX: 0,
|
|
135
132
|
slackY: 0,
|
|
136
133
|
// Can only determine if SVG after mounting
|
|
137
134
|
isElementSVG: false
|
|
138
135
|
});
|
|
136
|
+
const isElementNode = v => {
|
|
137
|
+
return !!v && typeof v === 'object' && 'nodeType' in v && v.nodeType === 1;
|
|
138
|
+
};
|
|
139
|
+
const isRefLike = v => {
|
|
140
|
+
return !!v && typeof v === 'object' && 'value' in v;
|
|
141
|
+
};
|
|
139
142
|
const findDOMNode = () => {
|
|
140
|
-
|
|
143
|
+
const nodeRef = props.nodeRef;
|
|
144
|
+
if (isRefLike(nodeRef)) {
|
|
145
|
+
const v = nodeRef.value;
|
|
146
|
+
if (isElementNode(v)) return v;
|
|
147
|
+
} else if (isElementNode(nodeRef)) {
|
|
148
|
+
return nodeRef;
|
|
149
|
+
}
|
|
150
|
+
return localNodeRef.value;
|
|
151
|
+
};
|
|
152
|
+
const boundsContext = {
|
|
153
|
+
props,
|
|
154
|
+
findDOMNode,
|
|
155
|
+
__boundsCache: {
|
|
156
|
+
key: '',
|
|
157
|
+
node: null,
|
|
158
|
+
boundEl: null,
|
|
159
|
+
boundClientWidth: 0,
|
|
160
|
+
boundClientHeight: 0,
|
|
161
|
+
nodeClientWidth: 0,
|
|
162
|
+
nodeClientHeight: 0,
|
|
163
|
+
bounds: null
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
let rafId = null;
|
|
167
|
+
let internalX = state.x;
|
|
168
|
+
let internalY = state.y;
|
|
169
|
+
let internalSlackX = state.slackX;
|
|
170
|
+
let internalSlackY = state.slackY;
|
|
171
|
+
let directionLockAxis = null;
|
|
172
|
+
let directionLockFixedX = NaN;
|
|
173
|
+
let directionLockFixedY = NaN;
|
|
174
|
+
let directionLockTotalX = 0;
|
|
175
|
+
let directionLockTotalY = 0;
|
|
176
|
+
const resetDirectionLock = () => {
|
|
177
|
+
directionLockAxis = null;
|
|
178
|
+
directionLockFixedX = NaN;
|
|
179
|
+
directionLockFixedY = NaN;
|
|
180
|
+
directionLockTotalX = 0;
|
|
181
|
+
directionLockTotalY = 0;
|
|
182
|
+
};
|
|
183
|
+
const getDirectionLockThreshold = () => {
|
|
184
|
+
const threshold = typeof props.directionLockThreshold === 'number' ? props.directionLockThreshold : 0;
|
|
185
|
+
if (threshold <= 0) return 0;
|
|
186
|
+
const scale = typeof props.scale === 'number' ? props.scale : 1;
|
|
187
|
+
if (!scale) return threshold;
|
|
188
|
+
return threshold / scale;
|
|
189
|
+
};
|
|
190
|
+
const flushToReactiveState = () => {
|
|
191
|
+
rafId = null;
|
|
192
|
+
if (internalX === state.x && internalY === state.y && internalSlackX === state.slackX && internalSlackY === state.slackY) return;
|
|
193
|
+
state.x = internalX;
|
|
194
|
+
state.y = internalY;
|
|
195
|
+
state.slackX = internalSlackX;
|
|
196
|
+
state.slackY = internalSlackY;
|
|
197
|
+
};
|
|
198
|
+
const scheduleFlush = () => {
|
|
199
|
+
if (rafId != null) return;
|
|
200
|
+
rafId = window.requestAnimationFrame(flushToReactiveState);
|
|
141
201
|
};
|
|
142
202
|
(0, _vue.onMounted)(() => {
|
|
143
203
|
if (typeof window.SVGElement !== 'undefined' && findDOMNode() instanceof window.SVGElement) {
|
|
@@ -146,83 +206,190 @@ const Draggable = exports.default = (0, _vue.defineComponent)({
|
|
|
146
206
|
});
|
|
147
207
|
(0, _vue.onUnmounted)(() => {
|
|
148
208
|
state.dragging = false;
|
|
209
|
+
if (rafId != null) {
|
|
210
|
+
window.cancelAnimationFrame(rafId);
|
|
211
|
+
rafId = null;
|
|
212
|
+
}
|
|
149
213
|
});
|
|
150
214
|
const onDragStart = (e, coreData) => {
|
|
151
215
|
(0, _log.default)('Draggable: onDragStart: %j', coreData);
|
|
152
216
|
|
|
153
217
|
// Short-circuit if user's callback killed it.
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
218
|
+
const isControlled = Boolean(props.position);
|
|
219
|
+
if (isControlled) {
|
|
220
|
+
internalX = props.position.x;
|
|
221
|
+
internalY = props.position.y;
|
|
222
|
+
} else {
|
|
223
|
+
internalX = state.x;
|
|
224
|
+
internalY = state.y;
|
|
225
|
+
}
|
|
226
|
+
internalSlackX = state.slackX;
|
|
227
|
+
internalSlackY = state.slackY;
|
|
228
|
+
boundsContext.__boundsCache.key = '';
|
|
229
|
+
boundsContext.__boundsCache.node = null;
|
|
230
|
+
boundsContext.__boundsCache.boundEl = null;
|
|
231
|
+
boundsContext.__boundsCache.boundClientWidth = 0;
|
|
232
|
+
boundsContext.__boundsCache.boundClientHeight = 0;
|
|
233
|
+
boundsContext.__boundsCache.nodeClientWidth = 0;
|
|
234
|
+
boundsContext.__boundsCache.nodeClientHeight = 0;
|
|
235
|
+
boundsContext.__boundsCache.bounds = null;
|
|
236
|
+
resetDirectionLock();
|
|
237
|
+
flushToReactiveState();
|
|
238
|
+
if (props.startFn !== _noop.default) {
|
|
239
|
+
const scale = typeof props.scale === 'number' ? props.scale : 1;
|
|
240
|
+
const uiStart = {
|
|
241
|
+
node: coreData.node,
|
|
242
|
+
x: internalX + coreData.deltaX / scale,
|
|
243
|
+
y: internalY + coreData.deltaY / scale,
|
|
244
|
+
deltaX: coreData.deltaX / scale,
|
|
245
|
+
deltaY: coreData.deltaY / scale,
|
|
246
|
+
lastX: internalX,
|
|
247
|
+
lastY: internalY
|
|
248
|
+
};
|
|
249
|
+
const shouldStart = props.startFn?.(e, uiStart);
|
|
250
|
+
// Kills start event on core as well, so move handlers are never bound.
|
|
251
|
+
if (shouldStart === false) return false;
|
|
252
|
+
}
|
|
160
253
|
state.dragging = true;
|
|
161
254
|
state.dragged = true;
|
|
162
255
|
};
|
|
163
256
|
const onDrag = (e, coreData) => {
|
|
164
257
|
if (!state.dragging) return false;
|
|
165
258
|
(0, _log.default)('Draggable: dragFn: %j', coreData);
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
259
|
+
const scale = typeof props.scale === 'number' ? props.scale : 1;
|
|
260
|
+
const rawDeltaX = coreData.deltaX / scale;
|
|
261
|
+
const rawDeltaY = coreData.deltaY / scale;
|
|
262
|
+
let newX = internalX + rawDeltaX;
|
|
263
|
+
let newY = internalY + rawDeltaY;
|
|
264
|
+
let newSlackX = 0;
|
|
265
|
+
let newSlackY = 0;
|
|
266
|
+
let uiDeltaX = rawDeltaX;
|
|
267
|
+
let uiDeltaY = rawDeltaY;
|
|
268
|
+
const allowAxisX = (0, _positionFns.canDragX)({
|
|
269
|
+
props
|
|
270
|
+
});
|
|
271
|
+
const allowAxisY = (0, _positionFns.canDragY)({
|
|
272
|
+
props
|
|
273
|
+
});
|
|
274
|
+
let effectiveAxisX = allowAxisX;
|
|
275
|
+
let effectiveAxisY = allowAxisY;
|
|
276
|
+
if (props.directionLock && allowAxisX && allowAxisY) {
|
|
277
|
+
if (directionLockAxis == null) {
|
|
278
|
+
directionLockTotalX += rawDeltaX;
|
|
279
|
+
directionLockTotalY += rawDeltaY;
|
|
280
|
+
const threshold = getDirectionLockThreshold();
|
|
281
|
+
if (!threshold || Math.hypot(directionLockTotalX, directionLockTotalY) >= threshold) {
|
|
282
|
+
directionLockAxis = Math.abs(directionLockTotalX) >= Math.abs(directionLockTotalY) ? 'x' : 'y';
|
|
283
|
+
directionLockFixedX = internalX;
|
|
284
|
+
directionLockFixedY = internalY;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if (directionLockAxis === 'x' && Number.isFinite(directionLockFixedY)) {
|
|
288
|
+
newY = directionLockFixedY;
|
|
289
|
+
uiDeltaY = newY - internalY;
|
|
290
|
+
} else if (directionLockAxis === 'y' && Number.isFinite(directionLockFixedX)) {
|
|
291
|
+
newX = directionLockFixedX;
|
|
292
|
+
uiDeltaX = newX - internalX;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (directionLockAxis === 'x') effectiveAxisY = false;
|
|
296
|
+
if (directionLockAxis === 'y') effectiveAxisX = false;
|
|
297
|
+
if (!effectiveAxisX) {
|
|
298
|
+
newX = internalX;
|
|
299
|
+
uiDeltaX = 0;
|
|
300
|
+
}
|
|
301
|
+
if (!effectiveAxisY) {
|
|
302
|
+
newY = internalY;
|
|
303
|
+
uiDeltaY = 0;
|
|
304
|
+
}
|
|
176
305
|
|
|
177
306
|
// Keep within bounds.
|
|
178
307
|
if (props.bounds) {
|
|
179
308
|
// Save original x and y.
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
y
|
|
183
|
-
} = newState;
|
|
309
|
+
const x = newX;
|
|
310
|
+
const y = newY;
|
|
184
311
|
|
|
185
312
|
// Add slack to the values used to calculate bound position. This will ensure that if
|
|
186
313
|
// completely removed.
|
|
187
|
-
|
|
188
|
-
|
|
314
|
+
const slackX = effectiveAxisX ? internalSlackX : 0;
|
|
315
|
+
const slackY = effectiveAxisY ? internalSlackY : 0;
|
|
316
|
+
newX += slackX;
|
|
317
|
+
newY += slackY;
|
|
189
318
|
|
|
190
319
|
// Get bound position. This will ceil/floor the x and y within the boundaries.
|
|
191
|
-
const [
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}, newState.x, newState.y);
|
|
195
|
-
newState.x = newStateX;
|
|
196
|
-
newState.y = newStateY;
|
|
320
|
+
const [boundX, boundY] = (0, _positionFns.getBoundPosition)(boundsContext, newX, newY);
|
|
321
|
+
newX = boundX;
|
|
322
|
+
newY = boundY;
|
|
197
323
|
|
|
198
324
|
// Recalculate slack by noting how much was shaved by the boundPosition handler.
|
|
199
|
-
|
|
200
|
-
|
|
325
|
+
newSlackX = slackX + (x - newX);
|
|
326
|
+
newSlackY = slackY + (y - newY);
|
|
201
327
|
|
|
202
328
|
// Update the event we fire to reflect what really happened after bounds took effect.
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
uiData.deltaX = newState.x - (state.x ?? 0);
|
|
206
|
-
uiData.deltaY = newState.y - (state.y ?? 0);
|
|
329
|
+
uiDeltaX = newX - internalX;
|
|
330
|
+
uiDeltaY = newY - internalY;
|
|
207
331
|
}
|
|
332
|
+
if (!effectiveAxisX) newSlackX = 0;
|
|
333
|
+
if (!effectiveAxisY) newSlackY = 0;
|
|
208
334
|
|
|
209
335
|
// Short-circuit if user's callback killed it.
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
336
|
+
if (props.dragFn !== _noop.default) {
|
|
337
|
+
const uiData = {
|
|
338
|
+
node: coreData.node,
|
|
339
|
+
x: newX,
|
|
340
|
+
y: newY,
|
|
341
|
+
deltaX: uiDeltaX,
|
|
342
|
+
deltaY: uiDeltaY,
|
|
343
|
+
lastX: internalX,
|
|
344
|
+
lastY: internalY
|
|
345
|
+
};
|
|
346
|
+
const shouldUpdate = props.dragFn?.(e, uiData);
|
|
347
|
+
if (shouldUpdate === false) return false;
|
|
348
|
+
}
|
|
349
|
+
internalX = newX;
|
|
350
|
+
internalY = newY;
|
|
351
|
+
internalSlackX = newSlackX;
|
|
352
|
+
internalSlackY = newSlackY;
|
|
353
|
+
if (props.useRafDrag) {
|
|
354
|
+
if (rafId != null) {
|
|
355
|
+
window.cancelAnimationFrame(rafId);
|
|
356
|
+
rafId = null;
|
|
357
|
+
}
|
|
358
|
+
flushToReactiveState();
|
|
359
|
+
} else {
|
|
360
|
+
scheduleFlush();
|
|
361
|
+
}
|
|
215
362
|
};
|
|
216
363
|
const onDragStop = (e, coreData) => {
|
|
217
364
|
if (!state.dragging) return false;
|
|
218
365
|
|
|
219
366
|
// Short-circuit if user's callback killed it.
|
|
220
|
-
|
|
221
|
-
props
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
367
|
+
if (props.stopFn !== _noop.default) {
|
|
368
|
+
const scale = typeof props.scale === 'number' ? props.scale : 1;
|
|
369
|
+
const allowAxisX = (0, _positionFns.canDragX)({
|
|
370
|
+
props
|
|
371
|
+
});
|
|
372
|
+
const allowAxisY = (0, _positionFns.canDragY)({
|
|
373
|
+
props
|
|
374
|
+
});
|
|
375
|
+
const effectiveAxisX = allowAxisX && directionLockAxis !== 'y';
|
|
376
|
+
const effectiveAxisY = allowAxisY && directionLockAxis !== 'x';
|
|
377
|
+
const deltaX = effectiveAxisX ? coreData.deltaX / scale : 0;
|
|
378
|
+
const deltaY = effectiveAxisY ? coreData.deltaY / scale : 0;
|
|
379
|
+
const uiStop = {
|
|
380
|
+
node: coreData.node,
|
|
381
|
+
x: internalX + deltaX,
|
|
382
|
+
y: internalY + deltaY,
|
|
383
|
+
deltaX,
|
|
384
|
+
deltaY,
|
|
385
|
+
lastX: internalX,
|
|
386
|
+
lastY: internalY
|
|
387
|
+
};
|
|
388
|
+
const shouldContinue = props.stopFn?.(e, uiStop);
|
|
389
|
+
if (shouldContinue === false) return false;
|
|
390
|
+
}
|
|
225
391
|
(0, _log.default)('Draggable: onDragStop: %j', coreData);
|
|
392
|
+
resetDirectionLock();
|
|
226
393
|
const newState = {
|
|
227
394
|
dragging: false,
|
|
228
395
|
slackX: 0,
|
|
@@ -240,14 +407,55 @@ const Draggable = exports.default = (0, _vue.defineComponent)({
|
|
|
240
407
|
newState.x = x;
|
|
241
408
|
newState.y = y;
|
|
242
409
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
410
|
+
state.dragging = newState.dragging;
|
|
411
|
+
internalSlackX = newState.slackX;
|
|
412
|
+
internalSlackY = newState.slackY;
|
|
413
|
+
if (typeof newState.x === 'number') internalX = newState.x;
|
|
414
|
+
if (typeof newState.y === 'number') internalY = newState.y;
|
|
415
|
+
if (rafId != null) {
|
|
416
|
+
window.cancelAnimationFrame(rafId);
|
|
417
|
+
rafId = null;
|
|
418
|
+
}
|
|
419
|
+
flushToReactiveState();
|
|
420
|
+
};
|
|
421
|
+
const getFirstUsableChild = () => {
|
|
422
|
+
const raw = slots.default ? slots.default() : [];
|
|
423
|
+
const stack = Array.isArray(raw) ? [...raw] : [raw];
|
|
424
|
+
while (stack.length) {
|
|
425
|
+
const item = stack.shift();
|
|
426
|
+
if (Array.isArray(item)) {
|
|
427
|
+
for (let i = item.length - 1; i >= 0; i -= 1) stack.unshift(item[i]);
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
if (!(0, _vue.isVNode)(item)) continue;
|
|
431
|
+
|
|
432
|
+
// Skip comment nodes and whitespace-only text nodes.
|
|
433
|
+
if (item.type === _vue.Comment) continue;
|
|
434
|
+
if (item.type === _vue.Text) {
|
|
435
|
+
const txt = typeof item.children === 'string' ? item.children : '';
|
|
436
|
+
if (!txt || !txt.trim()) continue;
|
|
437
|
+
// Draggable requires an element/component vnode, not bare text.
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Unwrap fragments to find the first real node.
|
|
442
|
+
if (item.type === _vue.Fragment) {
|
|
443
|
+
const fragChildren = item.children;
|
|
444
|
+
if (Array.isArray(fragChildren)) {
|
|
445
|
+
for (let i = fragChildren.length - 1; i >= 0; i -= 1) stack.unshift(fragChildren[i]);
|
|
446
|
+
}
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
return item;
|
|
450
|
+
}
|
|
451
|
+
return null;
|
|
246
452
|
};
|
|
247
453
|
return () => {
|
|
248
454
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
249
455
|
const {
|
|
250
456
|
axis,
|
|
457
|
+
directionLock,
|
|
458
|
+
directionLockThreshold,
|
|
251
459
|
bounds,
|
|
252
460
|
defaultPosition,
|
|
253
461
|
defaultClassName,
|
|
@@ -292,7 +500,7 @@ const Draggable = exports.default = (0, _vue.defineComponent)({
|
|
|
292
500
|
[defaultClassNameDragging]: state.dragging,
|
|
293
501
|
[defaultClassNameDragged]: state.dragged
|
|
294
502
|
});
|
|
295
|
-
const child =
|
|
503
|
+
const child = getFirstUsableChild();
|
|
296
504
|
if (!child) return null;
|
|
297
505
|
const clonedChildren = (0, _vue.cloneVNode)(child, {
|
|
298
506
|
class: className,
|
|
@@ -305,10 +513,10 @@ const Draggable = exports.default = (0, _vue.defineComponent)({
|
|
|
305
513
|
dragFn: onDrag,
|
|
306
514
|
stopFn: onDragStop
|
|
307
515
|
};
|
|
308
|
-
return (0, _vue.createVNode)(_DraggableCore.default, (0, _vue.mergeProps)({
|
|
309
|
-
"
|
|
310
|
-
}
|
|
311
|
-
default: () =>
|
|
516
|
+
return (0, _vue.createVNode)(_DraggableCore.default, (0, _vue.mergeProps)(coreProps, {
|
|
517
|
+
"nodeRef": props.nodeRef || localNodeRef
|
|
518
|
+
}), {
|
|
519
|
+
default: () => clonedChildren
|
|
312
520
|
});
|
|
313
521
|
};
|
|
314
522
|
}
|