@atlaskit/pragmatic-drag-and-drop 0.17.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/CHANGELOG.md +209 -0
- package/LICENSE.md +13 -0
- package/README.md +43 -0
- package/__perf__/add-example.todo +1 -0
- package/adapter/element/package.json +15 -0
- package/adapter/file/package.json +15 -0
- package/addon/cancel-unhandled/package.json +15 -0
- package/constellation/index/about.mdx +329 -0
- package/constellation/index/props.mdx +3 -0
- package/dist/cjs/adapter/element-adapter.js +151 -0
- package/dist/cjs/adapter/file-adapter.js +98 -0
- package/dist/cjs/addon/cancel-unhandled.js +50 -0
- package/dist/cjs/entry-point/adapter/element.js +24 -0
- package/dist/cjs/entry-point/adapter/file.js +18 -0
- package/dist/cjs/entry-point/addon/cancel-unhandled.js +12 -0
- package/dist/cjs/entry-point/experimental/cross-with-element-adapter.js +30 -0
- package/dist/cjs/entry-point/types.js +5 -0
- package/dist/cjs/entry-point/util/combine.js +12 -0
- package/dist/cjs/entry-point/util/disable-native-drag-preview.js +12 -0
- package/dist/cjs/entry-point/util/once.js +12 -0
- package/dist/cjs/entry-point/util/reorder.js +12 -0
- package/dist/cjs/entry-point/util/scroll-just-enough-into-view.js +12 -0
- package/dist/cjs/entry-point/util/set-custom-native-drag-preview.js +12 -0
- package/dist/cjs/experimental/cross-window-element-adapter.js +131 -0
- package/dist/cjs/index.js +12 -0
- package/dist/cjs/internal-types.js +5 -0
- package/dist/cjs/ledger/dispatch-consumer-event.js +132 -0
- package/dist/cjs/ledger/lifecycle-manager.js +335 -0
- package/dist/cjs/ledger/usage-ledger.js +37 -0
- package/dist/cjs/make-adapter/make-adapter.js +59 -0
- package/dist/cjs/make-adapter/make-drop-target.js +271 -0
- package/dist/cjs/make-adapter/make-monitor.js +100 -0
- package/dist/cjs/util/add-attribute.js +14 -0
- package/dist/cjs/util/combine.js +17 -0
- package/dist/cjs/util/disable-native-drag-preview.js +36 -0
- package/dist/cjs/util/entering-and-leaving-the-window.js +162 -0
- package/dist/cjs/util/fix-post-drag-pointer-bug.js +114 -0
- package/dist/cjs/util/get-input.js +20 -0
- package/dist/cjs/util/once.js +22 -0
- package/dist/cjs/util/reorder.js +26 -0
- package/dist/cjs/util/scroll-just-enough-into-view.js +17 -0
- package/dist/cjs/util/set-custom-native-drag-preview.js +109 -0
- package/dist/cjs/version.json +5 -0
- package/dist/es2019/adapter/element-adapter.js +143 -0
- package/dist/es2019/adapter/file-adapter.js +90 -0
- package/dist/es2019/addon/cancel-unhandled.js +43 -0
- package/dist/es2019/entry-point/adapter/element.js +1 -0
- package/dist/es2019/entry-point/adapter/file.js +1 -0
- package/dist/es2019/entry-point/addon/cancel-unhandled.js +1 -0
- package/dist/es2019/entry-point/experimental/cross-with-element-adapter.js +1 -0
- package/dist/es2019/entry-point/types.js +1 -0
- package/dist/es2019/entry-point/util/combine.js +1 -0
- package/dist/es2019/entry-point/util/disable-native-drag-preview.js +1 -0
- package/dist/es2019/entry-point/util/once.js +1 -0
- package/dist/es2019/entry-point/util/reorder.js +1 -0
- package/dist/es2019/entry-point/util/scroll-just-enough-into-view.js +1 -0
- package/dist/es2019/entry-point/util/set-custom-native-drag-preview.js +1 -0
- package/dist/es2019/experimental/cross-window-element-adapter.js +121 -0
- package/dist/es2019/index.js +7 -0
- package/dist/es2019/internal-types.js +1 -0
- package/dist/es2019/ledger/dispatch-consumer-event.js +128 -0
- package/dist/es2019/ledger/lifecycle-manager.js +333 -0
- package/dist/es2019/ledger/usage-ledger.js +32 -0
- package/dist/es2019/make-adapter/make-adapter.js +55 -0
- package/dist/es2019/make-adapter/make-drop-target.js +233 -0
- package/dist/es2019/make-adapter/make-monitor.js +80 -0
- package/dist/es2019/util/add-attribute.js +7 -0
- package/dist/es2019/util/combine.js +6 -0
- package/dist/es2019/util/disable-native-drag-preview.js +31 -0
- package/dist/es2019/util/entering-and-leaving-the-window.js +159 -0
- package/dist/es2019/util/fix-post-drag-pointer-bug.js +110 -0
- package/dist/es2019/util/get-input.js +14 -0
- package/dist/es2019/util/once.js +13 -0
- package/dist/es2019/util/reorder.js +17 -0
- package/dist/es2019/util/scroll-just-enough-into-view.js +12 -0
- package/dist/es2019/util/set-custom-native-drag-preview.js +106 -0
- package/dist/es2019/version.json +5 -0
- package/dist/esm/adapter/element-adapter.js +142 -0
- package/dist/esm/adapter/file-adapter.js +90 -0
- package/dist/esm/addon/cancel-unhandled.js +43 -0
- package/dist/esm/entry-point/adapter/element.js +1 -0
- package/dist/esm/entry-point/adapter/file.js +1 -0
- package/dist/esm/entry-point/addon/cancel-unhandled.js +1 -0
- package/dist/esm/entry-point/experimental/cross-with-element-adapter.js +1 -0
- package/dist/esm/entry-point/types.js +1 -0
- package/dist/esm/entry-point/util/combine.js +1 -0
- package/dist/esm/entry-point/util/disable-native-drag-preview.js +1 -0
- package/dist/esm/entry-point/util/once.js +1 -0
- package/dist/esm/entry-point/util/reorder.js +1 -0
- package/dist/esm/entry-point/util/scroll-just-enough-into-view.js +1 -0
- package/dist/esm/entry-point/util/set-custom-native-drag-preview.js +1 -0
- package/dist/esm/experimental/cross-window-element-adapter.js +120 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/internal-types.js +1 -0
- package/dist/esm/ledger/dispatch-consumer-event.js +125 -0
- package/dist/esm/ledger/lifecycle-manager.js +328 -0
- package/dist/esm/ledger/usage-ledger.js +31 -0
- package/dist/esm/make-adapter/make-adapter.js +53 -0
- package/dist/esm/make-adapter/make-drop-target.js +264 -0
- package/dist/esm/make-adapter/make-monitor.js +93 -0
- package/dist/esm/util/add-attribute.js +8 -0
- package/dist/esm/util/combine.js +11 -0
- package/dist/esm/util/disable-native-drag-preview.js +30 -0
- package/dist/esm/util/entering-and-leaving-the-window.js +156 -0
- package/dist/esm/util/fix-post-drag-pointer-bug.js +108 -0
- package/dist/esm/util/get-input.js +14 -0
- package/dist/esm/util/once.js +16 -0
- package/dist/esm/util/reorder.js +19 -0
- package/dist/esm/util/scroll-just-enough-into-view.js +11 -0
- package/dist/esm/util/set-custom-native-drag-preview.js +104 -0
- package/dist/esm/version.json +5 -0
- package/dist/types/adapter/element-adapter.d.ts +42 -0
- package/dist/types/adapter/file-adapter.d.ts +18 -0
- package/dist/types/addon/cancel-unhandled.d.ts +7 -0
- package/dist/types/entry-point/adapter/element.d.ts +2 -0
- package/dist/types/entry-point/adapter/file.d.ts +2 -0
- package/dist/types/entry-point/addon/cancel-unhandled.d.ts +1 -0
- package/dist/types/entry-point/experimental/cross-with-element-adapter.d.ts +1 -0
- package/dist/types/entry-point/types.d.ts +1 -0
- package/dist/types/entry-point/util/combine.d.ts +1 -0
- package/dist/types/entry-point/util/disable-native-drag-preview.d.ts +1 -0
- package/dist/types/entry-point/util/once.d.ts +1 -0
- package/dist/types/entry-point/util/reorder.d.ts +1 -0
- package/dist/types/entry-point/util/scroll-just-enough-into-view.d.ts +1 -0
- package/dist/types/entry-point/util/set-custom-native-drag-preview.d.ts +1 -0
- package/dist/types/experimental/cross-window-element-adapter.d.ts +17 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/internal-types.d.ts +275 -0
- package/dist/types/ledger/dispatch-consumer-event.d.ts +26 -0
- package/dist/types/ledger/lifecycle-manager.d.ts +16 -0
- package/dist/types/ledger/usage-ledger.d.ts +5 -0
- package/dist/types/make-adapter/make-adapter.d.ts +14 -0
- package/dist/types/make-adapter/make-drop-target.d.ts +5 -0
- package/dist/types/make-adapter/make-monitor.d.ts +8 -0
- package/dist/types/util/add-attribute.d.ts +5 -0
- package/dist/types/util/combine.d.ts +3 -0
- package/dist/types/util/disable-native-drag-preview.d.ts +3 -0
- package/dist/types/util/entering-and-leaving-the-window.d.ts +6 -0
- package/dist/types/util/fix-post-drag-pointer-bug.d.ts +14 -0
- package/dist/types/util/get-input.d.ts +2 -0
- package/dist/types/util/once.d.ts +2 -0
- package/dist/types/util/reorder.d.ts +9 -0
- package/dist/types/util/scroll-just-enough-into-view.d.ts +7 -0
- package/dist/types/util/set-custom-native-drag-preview.d.ts +52 -0
- package/experimental/cross-window-element-adapter/package.json +15 -0
- package/package.json +87 -0
- package/report.api.md +35 -0
- package/tmp/api-report-tmp.d.ts +13 -0
- package/types/package.json +15 -0
- package/util/combine/package.json +15 -0
- package/util/disable-native-drag-preview/package.json +15 -0
- package/util/once/package.json +15 -0
- package/util/reorder/package.json +15 -0
- package/util/scroll-just-enough-into-view/package.json +15 -0
- package/util/set-custom-native-drag-preview/package.json +15 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { bindAll } from 'bind-event-listener';
|
|
2
|
+
import { makeAdapter } from '../make-adapter/make-adapter';
|
|
3
|
+
import { combine } from '../util/combine';
|
|
4
|
+
import { isEnteringWindow } from '../util/entering-and-leaving-the-window';
|
|
5
|
+
const storage = (() => {
|
|
6
|
+
const dataKey = 'private-pdnd-data';
|
|
7
|
+
const resultKey = 'private-pdnd-result';
|
|
8
|
+
function tryParse(raw) {
|
|
9
|
+
if (raw == null) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
try {
|
|
13
|
+
return JSON.parse(raw);
|
|
14
|
+
} catch (e) {
|
|
15
|
+
// failed to parse
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function setData(data) {
|
|
20
|
+
// clearing a result when we set data
|
|
21
|
+
clearStorage();
|
|
22
|
+
localStorage.setItem(dataKey, JSON.stringify(data));
|
|
23
|
+
}
|
|
24
|
+
function findData() {
|
|
25
|
+
return tryParse(localStorage.getItem(dataKey));
|
|
26
|
+
}
|
|
27
|
+
function setResult(result) {
|
|
28
|
+
localStorage.setItem(resultKey, result);
|
|
29
|
+
}
|
|
30
|
+
function findResult() {
|
|
31
|
+
const raw = localStorage.getItem(resultKey);
|
|
32
|
+
if (raw == null) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// TODO: improve type
|
|
37
|
+
return raw;
|
|
38
|
+
}
|
|
39
|
+
function clearStorage() {
|
|
40
|
+
localStorage.removeItem(dataKey);
|
|
41
|
+
localStorage.removeItem(resultKey);
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
clearStorage,
|
|
45
|
+
setData,
|
|
46
|
+
findData,
|
|
47
|
+
setResult,
|
|
48
|
+
findResult
|
|
49
|
+
};
|
|
50
|
+
})();
|
|
51
|
+
const adapter = makeAdapter({
|
|
52
|
+
typeKey: 'experimental-cross-window-element',
|
|
53
|
+
defaultDropEffect: 'move',
|
|
54
|
+
mount(api) {
|
|
55
|
+
return combine(bindAll(window, [{
|
|
56
|
+
type: 'dragenter',
|
|
57
|
+
listener(event) {
|
|
58
|
+
if (!api.canStart(event)) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// already cancelled by something else
|
|
63
|
+
if (event.defaultPrevented) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (!isEnteringWindow({
|
|
67
|
+
dragEnter: event
|
|
68
|
+
})) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// we only care about when the window is being first entered
|
|
73
|
+
if (event.relatedTarget != null) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
const data = storage.findData();
|
|
77
|
+
if (data == null) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const dragInterface = {
|
|
81
|
+
key: 'experimental-cross-window-element',
|
|
82
|
+
startedFrom: 'external',
|
|
83
|
+
payload: {
|
|
84
|
+
data
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
api.start({
|
|
88
|
+
event,
|
|
89
|
+
dragInterface
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
// always clear a external drag result from another window before a drag starts
|
|
94
|
+
{
|
|
95
|
+
type: 'dragstart',
|
|
96
|
+
listener: storage.clearStorage,
|
|
97
|
+
options: {
|
|
98
|
+
capture: true
|
|
99
|
+
}
|
|
100
|
+
}]), adapter.monitor({
|
|
101
|
+
onDrop(payload) {
|
|
102
|
+
const result = payload.location.current.dropTargets.length > 0 ? 'moved' : 'none';
|
|
103
|
+
storage.setResult(result);
|
|
104
|
+
}
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
export function setCrossWindowData({
|
|
109
|
+
data
|
|
110
|
+
}) {
|
|
111
|
+
storage.setData(data);
|
|
112
|
+
}
|
|
113
|
+
export function extractCrossWindowResult() {
|
|
114
|
+
return storage.findResult();
|
|
115
|
+
}
|
|
116
|
+
export const dropTargetForCrossWindowElements = args => {
|
|
117
|
+
return combine(
|
|
118
|
+
// at least one drop target required before we will start listening for cross element dragging
|
|
119
|
+
adapter.registerUsage(), adapter.dropTarget(args));
|
|
120
|
+
};
|
|
121
|
+
export const monitorForCrossWindowElements = adapter.monitor;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import rafSchd from 'raf-schd';
|
|
2
|
+
const scheduleOnDrag = rafSchd(fn => fn());
|
|
3
|
+
const dragStart = (() => {
|
|
4
|
+
let scheduled = null;
|
|
5
|
+
function schedule(fn) {
|
|
6
|
+
const frameId = requestAnimationFrame(() => {
|
|
7
|
+
scheduled = null;
|
|
8
|
+
fn();
|
|
9
|
+
});
|
|
10
|
+
scheduled = {
|
|
11
|
+
frameId: frameId,
|
|
12
|
+
fn
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function flush() {
|
|
16
|
+
if (scheduled) {
|
|
17
|
+
cancelAnimationFrame(scheduled.frameId);
|
|
18
|
+
scheduled.fn();
|
|
19
|
+
scheduled = null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
schedule,
|
|
24
|
+
flush
|
|
25
|
+
};
|
|
26
|
+
})();
|
|
27
|
+
export function makeDispatch({
|
|
28
|
+
source,
|
|
29
|
+
initial,
|
|
30
|
+
dispatchEvent
|
|
31
|
+
}) {
|
|
32
|
+
let previous = {
|
|
33
|
+
dropTargets: []
|
|
34
|
+
};
|
|
35
|
+
function safeDispatch(args) {
|
|
36
|
+
dispatchEvent(args);
|
|
37
|
+
previous = {
|
|
38
|
+
dropTargets: args.payload.location.current.dropTargets
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const dispatch = {
|
|
42
|
+
start({
|
|
43
|
+
nativeSetDragImage
|
|
44
|
+
}) {
|
|
45
|
+
// Ensuring that both `onGenerateDragPreview` and `onDragStart` get the same location.
|
|
46
|
+
// We do this so that `previous` is`[]` in `onDragStart` (which is logical)
|
|
47
|
+
const location = {
|
|
48
|
+
current: initial,
|
|
49
|
+
previous,
|
|
50
|
+
initial
|
|
51
|
+
};
|
|
52
|
+
// a `onGenerateDragPreview` does _not_ add another entry for `previous`
|
|
53
|
+
// onDragPreview
|
|
54
|
+
safeDispatch({
|
|
55
|
+
eventName: 'onGenerateDragPreview',
|
|
56
|
+
payload: {
|
|
57
|
+
source,
|
|
58
|
+
location,
|
|
59
|
+
nativeSetDragImage
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
dragStart.schedule(() => {
|
|
63
|
+
safeDispatch({
|
|
64
|
+
eventName: 'onDragStart',
|
|
65
|
+
payload: {
|
|
66
|
+
source,
|
|
67
|
+
location
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
},
|
|
72
|
+
dragUpdate({
|
|
73
|
+
current
|
|
74
|
+
}) {
|
|
75
|
+
dragStart.flush();
|
|
76
|
+
scheduleOnDrag.cancel();
|
|
77
|
+
safeDispatch({
|
|
78
|
+
eventName: 'onDropTargetChange',
|
|
79
|
+
payload: {
|
|
80
|
+
source,
|
|
81
|
+
location: {
|
|
82
|
+
initial,
|
|
83
|
+
previous,
|
|
84
|
+
current
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
},
|
|
89
|
+
drag({
|
|
90
|
+
current
|
|
91
|
+
}) {
|
|
92
|
+
scheduleOnDrag(() => {
|
|
93
|
+
dragStart.flush();
|
|
94
|
+
const location = {
|
|
95
|
+
initial,
|
|
96
|
+
previous,
|
|
97
|
+
current
|
|
98
|
+
};
|
|
99
|
+
safeDispatch({
|
|
100
|
+
eventName: 'onDrag',
|
|
101
|
+
payload: {
|
|
102
|
+
source,
|
|
103
|
+
location
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
},
|
|
108
|
+
drop({
|
|
109
|
+
current,
|
|
110
|
+
updatedExternalPayload: updatedSourcePayload
|
|
111
|
+
}) {
|
|
112
|
+
dragStart.flush();
|
|
113
|
+
scheduleOnDrag.cancel();
|
|
114
|
+
safeDispatch({
|
|
115
|
+
eventName: 'onDrop',
|
|
116
|
+
payload: {
|
|
117
|
+
source: updatedSourcePayload !== null && updatedSourcePayload !== void 0 ? updatedSourcePayload : source,
|
|
118
|
+
location: {
|
|
119
|
+
current,
|
|
120
|
+
previous,
|
|
121
|
+
initial
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
return dispatch;
|
|
128
|
+
}
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import { bindAll } from 'bind-event-listener';
|
|
2
|
+
import { isLeavingWindow } from '../util/entering-and-leaving-the-window';
|
|
3
|
+
import { fixPostDragPointerBug } from '../util/fix-post-drag-pointer-bug';
|
|
4
|
+
import { getInput } from '../util/get-input';
|
|
5
|
+
import { makeDispatch } from './dispatch-consumer-event';
|
|
6
|
+
let isActive = false;
|
|
7
|
+
function canStart() {
|
|
8
|
+
return !isActive;
|
|
9
|
+
}
|
|
10
|
+
function getNativeSetDragImage(event) {
|
|
11
|
+
if (event.dataTransfer) {
|
|
12
|
+
// need to use `.bind` as `setDragImage` is required
|
|
13
|
+
// to be run with `event.dataTransfer` as the "this" context
|
|
14
|
+
return event.dataTransfer.setDragImage.bind(event.dataTransfer);
|
|
15
|
+
}
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
function hasHierarchyChanged({
|
|
19
|
+
current,
|
|
20
|
+
next
|
|
21
|
+
}) {
|
|
22
|
+
if (current.length !== next.length) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
// not checking stickiness, data or dropEffect,
|
|
26
|
+
// just whether the hierarchy has changed
|
|
27
|
+
for (let i = 0; i < current.length; i++) {
|
|
28
|
+
if (current[i].element !== next[i].element) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
function start({
|
|
35
|
+
event,
|
|
36
|
+
dragInterface,
|
|
37
|
+
getDropTargetsOver,
|
|
38
|
+
dispatchEvent
|
|
39
|
+
}) {
|
|
40
|
+
if (!canStart()) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
isActive = true;
|
|
44
|
+
const initial = getStartLocation({
|
|
45
|
+
event,
|
|
46
|
+
dragInterface,
|
|
47
|
+
getDropTargetsOver
|
|
48
|
+
});
|
|
49
|
+
let current = initial;
|
|
50
|
+
// Setting initial drop effect for the drag
|
|
51
|
+
setDropEffect({
|
|
52
|
+
event,
|
|
53
|
+
current: initial.dropTargets
|
|
54
|
+
});
|
|
55
|
+
const dispatch = makeDispatch({
|
|
56
|
+
source: dragInterface.payload,
|
|
57
|
+
dispatchEvent,
|
|
58
|
+
initial
|
|
59
|
+
});
|
|
60
|
+
function updateDropTargets(next) {
|
|
61
|
+
// only looking at whether hierarchy has changed to determine whether something as 'changed'
|
|
62
|
+
const hasChanged = hasHierarchyChanged({
|
|
63
|
+
current: current.dropTargets,
|
|
64
|
+
next: next.dropTargets
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Always updating the state to include latest data, dropEffect and stickiness
|
|
68
|
+
// Only updating consumers if the hierarchy has changed in some way
|
|
69
|
+
// Consumers can get the latest data by using `onDrag`
|
|
70
|
+
current = next;
|
|
71
|
+
if (hasChanged) {
|
|
72
|
+
dispatch.dragUpdate({
|
|
73
|
+
current
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function onUpdateEvent(event) {
|
|
78
|
+
const input = getInput(event);
|
|
79
|
+
const nextDropTargets = getDropTargetsOver({
|
|
80
|
+
target: event.target,
|
|
81
|
+
input,
|
|
82
|
+
source: dragInterface.payload,
|
|
83
|
+
current: current.dropTargets
|
|
84
|
+
});
|
|
85
|
+
if (nextDropTargets.length) {
|
|
86
|
+
// 🩸 must call `event.preventDefault()` to allow a browser drop to occur
|
|
87
|
+
event.preventDefault();
|
|
88
|
+
setDropEffect({
|
|
89
|
+
event,
|
|
90
|
+
current: nextDropTargets
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
updateDropTargets({
|
|
94
|
+
dropTargets: nextDropTargets,
|
|
95
|
+
input
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
function onDrop({
|
|
99
|
+
updatedExternalPayload
|
|
100
|
+
}) {
|
|
101
|
+
dispatch.drop({
|
|
102
|
+
current,
|
|
103
|
+
updatedExternalPayload
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
function cancel() {
|
|
107
|
+
// The spec behaviour is that when a drag is cancelled, or when dropping on no drop targets,
|
|
108
|
+
// a "dragleave" event is fired on the active drop target before a "dragend" event.
|
|
109
|
+
// We are replicating that behaviour in `cancel` if there are any active drop targets to
|
|
110
|
+
// ensure consistent behaviour.
|
|
111
|
+
//
|
|
112
|
+
// Note: When cancelling, or dropping on no drop targets, a "dragleave" event
|
|
113
|
+
// will have already cleared the dropTargets to `[]` (as that particular "dragleave" has a `relatedTarget` of `null`)
|
|
114
|
+
|
|
115
|
+
if (current.dropTargets.length) {
|
|
116
|
+
updateDropTargets({
|
|
117
|
+
dropTargets: [],
|
|
118
|
+
input: current.input
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
onDrop({
|
|
122
|
+
updatedExternalPayload: null
|
|
123
|
+
});
|
|
124
|
+
finish();
|
|
125
|
+
}
|
|
126
|
+
function finish() {
|
|
127
|
+
isActive = false;
|
|
128
|
+
unbindEvents();
|
|
129
|
+
}
|
|
130
|
+
const unbindEvents = bindAll(window, [{
|
|
131
|
+
// 👋 Note: we are repurposing the `dragover` event as our `drag` event
|
|
132
|
+
// this is because firefox does not publish pointer coordinates during
|
|
133
|
+
// a `drag` event, but does for every other type of drag event
|
|
134
|
+
// `dragover` fires on all elements that are being dragged over
|
|
135
|
+
// Because we are binding to `window` - our `dragover` is effectively the same as a `drag`
|
|
136
|
+
// 🦊😤
|
|
137
|
+
type: 'dragover',
|
|
138
|
+
listener(event) {
|
|
139
|
+
// We need to regularly calculate the drop targets in order to allow:
|
|
140
|
+
// - dynamic `canDrop()` checks
|
|
141
|
+
// - rapid updating `getData()` calls to attach data in response to user input (eg for edge detection)
|
|
142
|
+
// Sadly we cannot schedule inspecting changes resulting from this event
|
|
143
|
+
// we need to be able to conditionally cancel the event with `event.preventDefault()`
|
|
144
|
+
// to enable the correct native drop experience.
|
|
145
|
+
|
|
146
|
+
// 1. check to see if anything has changed
|
|
147
|
+
onUpdateEvent(event);
|
|
148
|
+
|
|
149
|
+
// 2. let consumers know a move has occurred
|
|
150
|
+
// This will include the latest 'input' values
|
|
151
|
+
dispatch.drag({
|
|
152
|
+
current
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}, {
|
|
156
|
+
type: 'dragenter',
|
|
157
|
+
listener: onUpdateEvent
|
|
158
|
+
}, {
|
|
159
|
+
// This was the only reliable cross browser way I found to detect
|
|
160
|
+
// when the user is leaving the `window`.
|
|
161
|
+
// Internal drags: when we leave the `window` we want to clear any active drop targets,
|
|
162
|
+
// but the drag is not yet over. The user could drag back into the window.
|
|
163
|
+
// We only need to do this because of stickiness
|
|
164
|
+
// External drags: when we leave the `window` the drag operation is over,
|
|
165
|
+
// we will start another drag operation
|
|
166
|
+
type: 'dragleave',
|
|
167
|
+
listener(event) {
|
|
168
|
+
if (!isLeavingWindow({
|
|
169
|
+
dragLeave: event
|
|
170
|
+
})) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
// When a drag is ending without a drop target (or when the drag is cancelled),
|
|
174
|
+
// All browsers fire:
|
|
175
|
+
// 1. "drag"
|
|
176
|
+
// 2. "dragleave"
|
|
177
|
+
// These events have `event.relatedTarget == null` so this code path is also hit in those cases.
|
|
178
|
+
// This is all good! We would be clearing the dropTargets in `cancel()` after the "dragend"
|
|
179
|
+
|
|
180
|
+
// 🐛 Bug workaround: intentionally not updating `input` in "dragleave"
|
|
181
|
+
// In Chrome, this final "dragleave" has default input values (eg clientX == 0)
|
|
182
|
+
// rather than the users current input values
|
|
183
|
+
//
|
|
184
|
+
// - [Conversation](https://twitter.com/alexandereardon/status/1642697633864241152)
|
|
185
|
+
// - [Bug](https://bugs.chromium.org/p/chromium/issues/detail?id=1429937)
|
|
186
|
+
updateDropTargets({
|
|
187
|
+
input: current.input,
|
|
188
|
+
dropTargets: []
|
|
189
|
+
});
|
|
190
|
+
if (dragInterface.startedFrom === 'external') {
|
|
191
|
+
cancel();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}, {
|
|
195
|
+
type: 'drop',
|
|
196
|
+
listener(event) {
|
|
197
|
+
var _dragInterface$getDro;
|
|
198
|
+
// A "drop" can only happen if the browser allowed the drop
|
|
199
|
+
|
|
200
|
+
// Opting out of standard browser drop behaviour for the drag
|
|
201
|
+
event.preventDefault();
|
|
202
|
+
|
|
203
|
+
// applying the latest drop effect to the event
|
|
204
|
+
setDropEffect({
|
|
205
|
+
event,
|
|
206
|
+
current: current.dropTargets
|
|
207
|
+
});
|
|
208
|
+
onDrop({
|
|
209
|
+
updatedExternalPayload: dragInterface.startedFrom === 'external' ? ((_dragInterface$getDro = dragInterface.getDropPayload) === null || _dragInterface$getDro === void 0 ? void 0 : _dragInterface$getDro.call(dragInterface, event)) || null : null
|
|
210
|
+
});
|
|
211
|
+
finish();
|
|
212
|
+
|
|
213
|
+
// Applying this fix after `dispatch.drop` so that frameworks have the opportunity
|
|
214
|
+
// to update UI in response to a "onDrop".
|
|
215
|
+
if (dragInterface.startedFrom === 'internal') {
|
|
216
|
+
fixPostDragPointerBug({
|
|
217
|
+
current
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}, {
|
|
222
|
+
// "dragend" fires when on the drag source (eg a draggable element)
|
|
223
|
+
// when the drag is finished.
|
|
224
|
+
// "dragend" will fire after "drop"(if there was a successful drop)
|
|
225
|
+
// "dragend" does not fire if the draggable source has been removed during the drag
|
|
226
|
+
// or for external drag sources (eg files)
|
|
227
|
+
type: 'dragend',
|
|
228
|
+
listener() {
|
|
229
|
+
cancel();
|
|
230
|
+
|
|
231
|
+
// Applying this fix after `dispatch.drop` so that frameworks have the opportunity
|
|
232
|
+
// to update UI in response to a "onDrop".
|
|
233
|
+
if (dragInterface.startedFrom === 'internal') {
|
|
234
|
+
fixPostDragPointerBug({
|
|
235
|
+
current
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
// ## Detecting drag ending for removed draggables
|
|
241
|
+
//
|
|
242
|
+
// If a draggable element is removed during a drag and the user drops:
|
|
243
|
+
// 1. if over a valid drop target: we get a "drop" event to know the drag is finished
|
|
244
|
+
// 2. if not over a valid drop target (or cancelled): we get nothing
|
|
245
|
+
// The "dragend" event will not fire on the source draggable if it has been
|
|
246
|
+
// removed from the DOM.
|
|
247
|
+
// So we need to figure out if a drag operation has finished by looking at other events
|
|
248
|
+
// We can do this by looking at other events
|
|
249
|
+
|
|
250
|
+
// ### First detection: "pointermove" events
|
|
251
|
+
|
|
252
|
+
// 1. "pointermove" events cannot fire during a drag and drop operation
|
|
253
|
+
// according to the spec. So if we get a "pointermove" it means that
|
|
254
|
+
// the drag and drop operations has finished. So if we get a "pointermove"
|
|
255
|
+
// we know that the drag is over
|
|
256
|
+
// 2. 🦊😤 Drag and drop operations are _supposed_ to suppress
|
|
257
|
+
// other pointer events. However, firefox will allow a few
|
|
258
|
+
// pointer event to get through after a drag starts.
|
|
259
|
+
// The most I've seen is 3
|
|
260
|
+
{
|
|
261
|
+
type: 'pointermove',
|
|
262
|
+
listener: (() => {
|
|
263
|
+
let callCount = 0;
|
|
264
|
+
return function listener() {
|
|
265
|
+
// Using 20 as it is far bigger than the most observed (3)
|
|
266
|
+
if (callCount < 20) {
|
|
267
|
+
callCount++;
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
cancel();
|
|
271
|
+
};
|
|
272
|
+
})()
|
|
273
|
+
},
|
|
274
|
+
// ### Second detection: "pointerdown" events
|
|
275
|
+
|
|
276
|
+
// If we receive this event then we know that a drag operation has finished
|
|
277
|
+
// and potentially another one is about to start.
|
|
278
|
+
// Note: `pointerdown` fires on all browsers / platforms before "dragstart"
|
|
279
|
+
{
|
|
280
|
+
type: 'pointerdown',
|
|
281
|
+
listener: cancel
|
|
282
|
+
}],
|
|
283
|
+
// Once we have started a managed drag operation it is important that we see / own all drag events
|
|
284
|
+
// We got one adoption bug pop up where some code was stopping (`event.stopPropagation()`)
|
|
285
|
+
// all "drop" events in the bubble phase on the `document.body`.
|
|
286
|
+
// This meant that we never saw the "drop" event.
|
|
287
|
+
{
|
|
288
|
+
capture: true
|
|
289
|
+
});
|
|
290
|
+
dispatch.start({
|
|
291
|
+
nativeSetDragImage: getNativeSetDragImage(event)
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
function setDropEffect({
|
|
295
|
+
event,
|
|
296
|
+
current
|
|
297
|
+
}) {
|
|
298
|
+
var _current$;
|
|
299
|
+
// setting the `dropEffect` to be the innerMost drop targets dropEffect
|
|
300
|
+
const innerMost = (_current$ = current[0]) === null || _current$ === void 0 ? void 0 : _current$.dropEffect;
|
|
301
|
+
if (innerMost != null && event.dataTransfer) {
|
|
302
|
+
event.dataTransfer.dropEffect = innerMost;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
function getStartLocation({
|
|
306
|
+
event,
|
|
307
|
+
dragInterface,
|
|
308
|
+
getDropTargetsOver
|
|
309
|
+
}) {
|
|
310
|
+
const input = getInput(event);
|
|
311
|
+
|
|
312
|
+
// When dragging from outside of the browser, we don't have any starting drop targets
|
|
313
|
+
if (dragInterface.startedFrom === 'external') {
|
|
314
|
+
return {
|
|
315
|
+
input,
|
|
316
|
+
dropTargets: []
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
const dropTargets = getDropTargetsOver({
|
|
320
|
+
input,
|
|
321
|
+
source: dragInterface.payload,
|
|
322
|
+
target: event.target,
|
|
323
|
+
current: []
|
|
324
|
+
});
|
|
325
|
+
return {
|
|
326
|
+
input,
|
|
327
|
+
dropTargets
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
export const lifecycle = {
|
|
331
|
+
canStart,
|
|
332
|
+
start
|
|
333
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Extending `Map` to allow us to link Key and Values together
|
|
2
|
+
|
|
3
|
+
const ledger = new Map();
|
|
4
|
+
function registerUsage({
|
|
5
|
+
typeKey,
|
|
6
|
+
mount
|
|
7
|
+
}) {
|
|
8
|
+
const entry = ledger.get(typeKey);
|
|
9
|
+
if (entry) {
|
|
10
|
+
entry.usageCount++;
|
|
11
|
+
return entry;
|
|
12
|
+
}
|
|
13
|
+
const initial = {
|
|
14
|
+
typeKey,
|
|
15
|
+
unmount: mount(),
|
|
16
|
+
usageCount: 1
|
|
17
|
+
};
|
|
18
|
+
ledger.set(typeKey, initial);
|
|
19
|
+
return initial;
|
|
20
|
+
}
|
|
21
|
+
export function register(args) {
|
|
22
|
+
const entry = registerUsage(args);
|
|
23
|
+
return function unregister() {
|
|
24
|
+
entry.usageCount--;
|
|
25
|
+
if (entry.usageCount > 0) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
// Only a single usage left, remove it
|
|
29
|
+
entry.unmount();
|
|
30
|
+
ledger.delete(args.typeKey);
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { lifecycle } from '../ledger/lifecycle-manager';
|
|
2
|
+
import { register } from '../ledger/usage-ledger';
|
|
3
|
+
import { makeDropTarget } from './make-drop-target';
|
|
4
|
+
import { makeMonitor } from './make-monitor';
|
|
5
|
+
export function makeAdapter({
|
|
6
|
+
typeKey,
|
|
7
|
+
mount,
|
|
8
|
+
dispatchEventToSource,
|
|
9
|
+
defaultDropEffect
|
|
10
|
+
}) {
|
|
11
|
+
const monitorAPI = makeMonitor();
|
|
12
|
+
const dropTargetAPI = makeDropTarget({
|
|
13
|
+
typeKey,
|
|
14
|
+
defaultDropEffect: defaultDropEffect
|
|
15
|
+
});
|
|
16
|
+
function dispatchEvent(args) {
|
|
17
|
+
// 1. forward the event to source
|
|
18
|
+
dispatchEventToSource === null || dispatchEventToSource === void 0 ? void 0 : dispatchEventToSource(args);
|
|
19
|
+
|
|
20
|
+
// 2. forward the event to relevant dropTargets
|
|
21
|
+
dropTargetAPI.dispatchEvent(args);
|
|
22
|
+
|
|
23
|
+
// 3. forward event to monitors
|
|
24
|
+
monitorAPI.dispatchEvent(args);
|
|
25
|
+
}
|
|
26
|
+
function start({
|
|
27
|
+
event,
|
|
28
|
+
dragInterface
|
|
29
|
+
}) {
|
|
30
|
+
lifecycle.start({
|
|
31
|
+
event,
|
|
32
|
+
dragInterface,
|
|
33
|
+
getDropTargetsOver: dropTargetAPI.getIsOver,
|
|
34
|
+
dispatchEvent
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function registerUsage() {
|
|
38
|
+
function mountAdapter() {
|
|
39
|
+
const api = {
|
|
40
|
+
canStart: lifecycle.canStart,
|
|
41
|
+
start
|
|
42
|
+
};
|
|
43
|
+
return mount(api);
|
|
44
|
+
}
|
|
45
|
+
return register({
|
|
46
|
+
typeKey,
|
|
47
|
+
mount: mountAdapter
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
registerUsage,
|
|
52
|
+
dropTarget: dropTargetAPI.dropTargetForConsumers,
|
|
53
|
+
monitor: monitorAPI.monitorForConsumers
|
|
54
|
+
};
|
|
55
|
+
}
|