@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,233 @@
|
|
|
1
|
+
import { addAttribute } from '../util/add-attribute';
|
|
2
|
+
import { combine } from '../util/combine';
|
|
3
|
+
function copyReverse(array) {
|
|
4
|
+
return array.slice(0).reverse();
|
|
5
|
+
}
|
|
6
|
+
export function makeDropTarget({
|
|
7
|
+
typeKey,
|
|
8
|
+
defaultDropEffect
|
|
9
|
+
}) {
|
|
10
|
+
const registry = new WeakMap();
|
|
11
|
+
const dropTargetDataAtt = `data-drop-target-for-${typeKey}`;
|
|
12
|
+
const dropTargetSelector = `[${dropTargetDataAtt}]`;
|
|
13
|
+
function addToRegistry(args) {
|
|
14
|
+
registry.set(args.element, args);
|
|
15
|
+
return () => registry.delete(args.element);
|
|
16
|
+
}
|
|
17
|
+
function dropTargetForConsumers(args) {
|
|
18
|
+
// Guardrail: warn if the draggable element is already registered
|
|
19
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
20
|
+
const existing = registry.get(args.element);
|
|
21
|
+
if (existing) {
|
|
22
|
+
// eslint-disable-next-line no-console
|
|
23
|
+
console.warn('You have already registered a `droppable` on the same element', {
|
|
24
|
+
existing,
|
|
25
|
+
proposed: args
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return combine(addAttribute(args.element, {
|
|
30
|
+
attribute: dropTargetDataAtt,
|
|
31
|
+
value: 'true'
|
|
32
|
+
}), addToRegistry(args));
|
|
33
|
+
}
|
|
34
|
+
function getActualDropTargets({
|
|
35
|
+
source,
|
|
36
|
+
target,
|
|
37
|
+
input,
|
|
38
|
+
result = []
|
|
39
|
+
}) {
|
|
40
|
+
var _args$getData, _args$getData2, _args$getDropEffect, _args$getDropEffect2, _args$getIsSticky;
|
|
41
|
+
if (!(target instanceof Element)) {
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
const closest = target.closest(dropTargetSelector);
|
|
45
|
+
|
|
46
|
+
// Cannot find anything else
|
|
47
|
+
if (closest == null) {
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
const args = registry.get(closest);
|
|
51
|
+
|
|
52
|
+
// error: something had a dropTargetSelector but we could not
|
|
53
|
+
// find a match. For now, failing silently
|
|
54
|
+
if (args == null) {
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
const feedback = {
|
|
58
|
+
input,
|
|
59
|
+
source,
|
|
60
|
+
element: args.element
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// if dropping is not allowed, skip this drop target
|
|
64
|
+
// and continue looking up the DOM tree
|
|
65
|
+
if (args.canDrop && !args.canDrop(feedback)) {
|
|
66
|
+
return getActualDropTargets({
|
|
67
|
+
source,
|
|
68
|
+
target: args.element.parentElement,
|
|
69
|
+
input,
|
|
70
|
+
result
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
const data = (_args$getData = (_args$getData2 = args.getData) === null || _args$getData2 === void 0 ? void 0 : _args$getData2.call(args, feedback)) !== null && _args$getData !== void 0 ? _args$getData : {};
|
|
74
|
+
const dropEffect = (_args$getDropEffect = (_args$getDropEffect2 = args.getDropEffect) === null || _args$getDropEffect2 === void 0 ? void 0 : _args$getDropEffect2.call(args, feedback)) !== null && _args$getDropEffect !== void 0 ? _args$getDropEffect : defaultDropEffect;
|
|
75
|
+
const sticky = Boolean((_args$getIsSticky = args.getIsSticky) === null || _args$getIsSticky === void 0 ? void 0 : _args$getIsSticky.call(args, feedback));
|
|
76
|
+
const record = {
|
|
77
|
+
data,
|
|
78
|
+
element: args.element,
|
|
79
|
+
dropEffect,
|
|
80
|
+
sticky
|
|
81
|
+
};
|
|
82
|
+
return getActualDropTargets({
|
|
83
|
+
source,
|
|
84
|
+
target: args.element.parentElement,
|
|
85
|
+
input,
|
|
86
|
+
// Using bubble ordering. Same ordering as `event.getPath()`
|
|
87
|
+
result: [...result, record]
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function notifyCurrent({
|
|
91
|
+
eventName,
|
|
92
|
+
payload
|
|
93
|
+
}) {
|
|
94
|
+
for (const record of payload.location.current.dropTargets) {
|
|
95
|
+
var _entry$eventName;
|
|
96
|
+
const entry = registry.get(record.element);
|
|
97
|
+
const args = {
|
|
98
|
+
...payload,
|
|
99
|
+
self: record
|
|
100
|
+
};
|
|
101
|
+
entry === null || entry === void 0 ? void 0 : (_entry$eventName = entry[eventName]) === null || _entry$eventName === void 0 ? void 0 : _entry$eventName.call(entry,
|
|
102
|
+
// I cannot seem to get the types right here.
|
|
103
|
+
// TS doesn't seem to like that one event can need `nativeSetDragImage`
|
|
104
|
+
// @ts-expect-error
|
|
105
|
+
args);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const actions = {
|
|
109
|
+
onGenerateDragPreview: notifyCurrent,
|
|
110
|
+
onDrag: notifyCurrent,
|
|
111
|
+
onDragStart: notifyCurrent,
|
|
112
|
+
onDrop: notifyCurrent,
|
|
113
|
+
onDropTargetChange: ({
|
|
114
|
+
payload
|
|
115
|
+
}) => {
|
|
116
|
+
const isCurrent = new Set(payload.location.current.dropTargets.map(record => record.element));
|
|
117
|
+
const visited = new Set();
|
|
118
|
+
for (const record of payload.location.previous.dropTargets) {
|
|
119
|
+
var _entry$onDropTargetCh;
|
|
120
|
+
visited.add(record.element);
|
|
121
|
+
const entry = registry.get(record.element);
|
|
122
|
+
const isOver = isCurrent.has(record.element);
|
|
123
|
+
const args = {
|
|
124
|
+
...payload,
|
|
125
|
+
self: record
|
|
126
|
+
};
|
|
127
|
+
entry === null || entry === void 0 ? void 0 : (_entry$onDropTargetCh = entry.onDropTargetChange) === null || _entry$onDropTargetCh === void 0 ? void 0 : _entry$onDropTargetCh.call(entry, args);
|
|
128
|
+
|
|
129
|
+
// if we cannot find the drop target in the current array, then it has been left
|
|
130
|
+
if (!isOver) {
|
|
131
|
+
var _entry$onDragLeave;
|
|
132
|
+
entry === null || entry === void 0 ? void 0 : (_entry$onDragLeave = entry.onDragLeave) === null || _entry$onDragLeave === void 0 ? void 0 : _entry$onDragLeave.call(entry, args);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
for (const record of payload.location.current.dropTargets) {
|
|
136
|
+
var _entry$onDropTargetCh2, _entry$onDragEnter;
|
|
137
|
+
// already published an update to this drop target
|
|
138
|
+
if (visited.has(record.element)) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
// at this point we have a new drop target that is being entered into
|
|
142
|
+
const args = {
|
|
143
|
+
...payload,
|
|
144
|
+
self: record
|
|
145
|
+
};
|
|
146
|
+
const entry = registry.get(record.element);
|
|
147
|
+
entry === null || entry === void 0 ? void 0 : (_entry$onDropTargetCh2 = entry.onDropTargetChange) === null || _entry$onDropTargetCh2 === void 0 ? void 0 : _entry$onDropTargetCh2.call(entry, args);
|
|
148
|
+
entry === null || entry === void 0 ? void 0 : (_entry$onDragEnter = entry.onDragEnter) === null || _entry$onDragEnter === void 0 ? void 0 : _entry$onDragEnter.call(entry, args);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
function dispatchEvent(args) {
|
|
153
|
+
// This line does not work in TS 4.2
|
|
154
|
+
// It does work in TS 4.7
|
|
155
|
+
// @ts-expect-error
|
|
156
|
+
actions[args.eventName](args);
|
|
157
|
+
}
|
|
158
|
+
function getIsOver({
|
|
159
|
+
source,
|
|
160
|
+
target,
|
|
161
|
+
input,
|
|
162
|
+
current
|
|
163
|
+
}) {
|
|
164
|
+
const actual = getActualDropTargets({
|
|
165
|
+
source,
|
|
166
|
+
target,
|
|
167
|
+
input
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// stickiness is only relevant when we have less
|
|
171
|
+
// drop targets than we did before
|
|
172
|
+
if (actual.length >= current.length) {
|
|
173
|
+
return actual;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// less 'actual' drop targets than before,
|
|
177
|
+
// we need to see if 'stickiness' applies
|
|
178
|
+
|
|
179
|
+
// An old drop target will continue to be dropped on if:
|
|
180
|
+
// 1. it has the same parent
|
|
181
|
+
// 2. nothing exists in it's previous index
|
|
182
|
+
|
|
183
|
+
const lastCaptureOrdered = copyReverse(current);
|
|
184
|
+
const actualCaptureOrdered = copyReverse(actual);
|
|
185
|
+
const resultCaptureOrdered = [];
|
|
186
|
+
for (let index = 0; index < lastCaptureOrdered.length; index++) {
|
|
187
|
+
const last = lastCaptureOrdered[index];
|
|
188
|
+
const fresh = actualCaptureOrdered[index];
|
|
189
|
+
|
|
190
|
+
// if a record is in the new index -> use that
|
|
191
|
+
// it will have the latest data + dropEffect
|
|
192
|
+
if (fresh != null) {
|
|
193
|
+
resultCaptureOrdered.push(fresh);
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// At this point we have no drop target in the old spot
|
|
198
|
+
// Check to see if we can use a previous sticky drop target
|
|
199
|
+
|
|
200
|
+
// stickiness is based on relationships to a parent
|
|
201
|
+
// so if we hit a drop target that is not sticky we
|
|
202
|
+
// can finish our search
|
|
203
|
+
if (!last.sticky) {
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// We only want the previous sticky item to 'stick' if
|
|
208
|
+
// the parent of the sticky item is unchanged
|
|
209
|
+
|
|
210
|
+
// The "parent" is the one inside of `resultCaptureOrdered`
|
|
211
|
+
// (the parent might be a drop target that was sticky)
|
|
212
|
+
const parent = resultCaptureOrdered[index - 1];
|
|
213
|
+
const lastParent = lastCaptureOrdered[index - 1];
|
|
214
|
+
|
|
215
|
+
// parents are the same (might both be undefined for index == 0)
|
|
216
|
+
// we can add the last entry and keep searching
|
|
217
|
+
if ((parent === null || parent === void 0 ? void 0 : parent.element) === (lastParent === null || lastParent === void 0 ? void 0 : lastParent.element)) {
|
|
218
|
+
resultCaptureOrdered.push(last);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
// parents are not the same, we can exit our search
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// return bubble ordered result
|
|
226
|
+
return copyReverse(resultCaptureOrdered);
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
dropTargetForConsumers,
|
|
230
|
+
getIsOver,
|
|
231
|
+
dispatchEvent
|
|
232
|
+
};
|
|
233
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export function makeMonitor() {
|
|
2
|
+
const registry = new Set();
|
|
3
|
+
let dragging = null;
|
|
4
|
+
function tryAddToActive(monitor) {
|
|
5
|
+
if (!dragging) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
// Monitor is allowed to monitor events if:
|
|
9
|
+
// 1. It has no `canMonitor` function (default is that a monitor can listen to everything)
|
|
10
|
+
// 2. `canMonitor` returns true
|
|
11
|
+
if (!monitor.canMonitor || monitor.canMonitor(dragging.canMonitorArgs)) {
|
|
12
|
+
dragging.active.add(monitor);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function monitorForConsumers(args) {
|
|
16
|
+
// We are giving each `args` a new reference so that you
|
|
17
|
+
// can create multiple monitors with the same `args`.
|
|
18
|
+
const entry = {
|
|
19
|
+
...args
|
|
20
|
+
};
|
|
21
|
+
registry.add(entry);
|
|
22
|
+
|
|
23
|
+
// if there is an active drag we need to see if this new monitor is relevant
|
|
24
|
+
tryAddToActive(entry);
|
|
25
|
+
return function cleanup() {
|
|
26
|
+
registry.delete(entry);
|
|
27
|
+
|
|
28
|
+
// We need to stop publishing events during a drag to this monitor!
|
|
29
|
+
if (dragging) {
|
|
30
|
+
dragging.active.delete(entry);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function dispatchEvent({
|
|
35
|
+
eventName,
|
|
36
|
+
payload
|
|
37
|
+
}) {
|
|
38
|
+
if (eventName === 'onGenerateDragPreview') {
|
|
39
|
+
dragging = {
|
|
40
|
+
canMonitorArgs: {
|
|
41
|
+
initial: payload.location.initial,
|
|
42
|
+
source: payload.source
|
|
43
|
+
},
|
|
44
|
+
active: new Set()
|
|
45
|
+
};
|
|
46
|
+
for (const monitor of registry) {
|
|
47
|
+
tryAddToActive(monitor);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// This should never happen.
|
|
52
|
+
if (!dragging) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Creating an array from the set _before_ iterating
|
|
57
|
+
// This is so that monitors added during the current event will not be called.
|
|
58
|
+
// This behaviour matches native EventTargets where an event listener
|
|
59
|
+
// cannot add another event listener during an active event to the same
|
|
60
|
+
// event target in the same event (for us we have a single global event target)
|
|
61
|
+
const active = Array.from(dragging.active);
|
|
62
|
+
for (const monitor of active) {
|
|
63
|
+
// A monitor can be removed by another monitor during an event.
|
|
64
|
+
// We need to check that the monitor is still registered before calling it
|
|
65
|
+
if (dragging.active.has(monitor)) {
|
|
66
|
+
var _monitor$eventName;
|
|
67
|
+
// @ts-expect-error: I cannot get this type working!
|
|
68
|
+
(_monitor$eventName = monitor[eventName]) === null || _monitor$eventName === void 0 ? void 0 : _monitor$eventName.call(monitor, payload);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (eventName === 'onDrop') {
|
|
72
|
+
dragging.active.clear();
|
|
73
|
+
dragging = null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
dispatchEvent,
|
|
78
|
+
monitorForConsumers
|
|
79
|
+
};
|
|
80
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// In order to disable the native drag preview you can
|
|
2
|
+
// use `event.dataTransfer.setDragImage()` to set a small
|
|
3
|
+
// invisible image as the drag preview.
|
|
4
|
+
// There are alternative techniques,
|
|
5
|
+
// (eg setting opacity to in onGenerateDragPreview and then 1 in onDragStart)
|
|
6
|
+
// but the technique in this file worked best across browsers and platforms
|
|
7
|
+
|
|
8
|
+
// Here we are preloading the image so that it is ready for the first drag.
|
|
9
|
+
// Even though the image is base64 encoded, the browser queues an async task
|
|
10
|
+
// to decode the image. The image needs to be decoded before it can be used
|
|
11
|
+
const tinyTransparentImage = (() => {
|
|
12
|
+
// SSR safe
|
|
13
|
+
if (typeof window === 'undefined') {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Image generated by: https://png-pixel.com/
|
|
18
|
+
// It is a 1x1 transparent gif
|
|
19
|
+
// It is the smallest possible transparent image we could find that works on all platforms
|
|
20
|
+
// Note: using an encoded SVG would be nicer code, but it doesn't work on iOS
|
|
21
|
+
const img = new Image();
|
|
22
|
+
img.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==';
|
|
23
|
+
return img;
|
|
24
|
+
})();
|
|
25
|
+
export function disableNativeDragPreview({
|
|
26
|
+
nativeSetDragImage
|
|
27
|
+
}) {
|
|
28
|
+
if (nativeSetDragImage && tinyTransparentImage) {
|
|
29
|
+
nativeSetDragImage(tinyTransparentImage, 0, 0);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { bindAll } from 'bind-event-listener';
|
|
2
|
+
|
|
3
|
+
// *Usually* to detect if you are entering / leaving a window you can
|
|
4
|
+
// use the `event.relatedTarget` property:
|
|
5
|
+
|
|
6
|
+
// "dragenter":
|
|
7
|
+
// - `event.relatedTarget` should point to the element that you are coming from
|
|
8
|
+
// - Scenario: A -> B
|
|
9
|
+
// - `event.target`: `B` (entering B)
|
|
10
|
+
// - `event.relatedTarget`: `A`: (leaving A - where you are coming from)
|
|
11
|
+
// - if `event.relatedTarget` is `null` then you are entering the window (coming from `null`)
|
|
12
|
+
//
|
|
13
|
+
// "dragleave"
|
|
14
|
+
// - `event.relatedTarget` should point to the element you are going to
|
|
15
|
+
// - Scenario: A -> B (entered B, leaving A)
|
|
16
|
+
// - `event.target`: `A` (leaving A)
|
|
17
|
+
// - `event.relatedTarget`: `B`: (entering into B - where you are going to)
|
|
18
|
+
// - if `event.relatedTarget` is `null` then you are leaving the window (going to `null`)
|
|
19
|
+
//
|
|
20
|
+
// Unfortunately in Safari `event.relatedTarget` is *always* set to `null`
|
|
21
|
+
// Safari bug: https://bugs.webkit.org/show_bug.cgi?id=242627
|
|
22
|
+
// To work around this we count "dragenter" and "dragleave" events
|
|
23
|
+
const safariFix = {
|
|
24
|
+
isSafari: false,
|
|
25
|
+
// Using symbols for event properties so we don't clash with
|
|
26
|
+
// anything on the `event` object
|
|
27
|
+
leavingWindow: Symbol('leaving'),
|
|
28
|
+
enteringWindow: Symbol('entering')
|
|
29
|
+
};
|
|
30
|
+
export function isEnteringWindow({
|
|
31
|
+
dragEnter
|
|
32
|
+
}) {
|
|
33
|
+
if (dragEnter.type !== 'dragenter') {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
if (safariFix.isSafari) {
|
|
37
|
+
return dragEnter.hasOwnProperty(safariFix.enteringWindow);
|
|
38
|
+
}
|
|
39
|
+
// This is the standard check.
|
|
40
|
+
// if `relatedTarget` is `null` during a "dragenter"
|
|
41
|
+
// then we are entering the`window`
|
|
42
|
+
return dragEnter.relatedTarget == null;
|
|
43
|
+
}
|
|
44
|
+
export function isLeavingWindow({
|
|
45
|
+
dragLeave
|
|
46
|
+
}) {
|
|
47
|
+
if (dragLeave.type !== 'dragleave') {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
if (safariFix.isSafari) {
|
|
51
|
+
return dragLeave.hasOwnProperty(safariFix.leavingWindow);
|
|
52
|
+
}
|
|
53
|
+
// This is the standard check.
|
|
54
|
+
// if `relatedTarget` is `null` during a "dragleave"
|
|
55
|
+
// then we are leave the `window`
|
|
56
|
+
return dragLeave.relatedTarget == null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// NOTE: this fix currently has no test coverage
|
|
60
|
+
// - our drag and drop browser tests currently only run in Chrome due to tooling limitations
|
|
61
|
+
// - it didn't feel helpful to unit test as it is merely replicating a bug
|
|
62
|
+
|
|
63
|
+
(function fixSafari() {
|
|
64
|
+
// Don't do anything when server side rendering
|
|
65
|
+
if (typeof window === 'undefined') {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// rather than checking the userAgent for "jsdom" we can do this check
|
|
70
|
+
// so that the check will be removed completely in production code
|
|
71
|
+
if (process.env.NODE_ENV === 'test') {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const {
|
|
75
|
+
userAgent
|
|
76
|
+
} = navigator;
|
|
77
|
+
const isSafari = userAgent.includes('AppleWebKit') && !userAgent.includes('Chrome');
|
|
78
|
+
if (!isSafari) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
safariFix.isSafari = true;
|
|
82
|
+
function getInitialState() {
|
|
83
|
+
return {
|
|
84
|
+
enterCount: 0,
|
|
85
|
+
isOverWindow: false
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
let state = getInitialState();
|
|
89
|
+
function resetState() {
|
|
90
|
+
state = getInitialState();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// These event listeners are bound _forever_ and _never_ removed
|
|
94
|
+
// We don't bother cleaning up these event listeners (for now)
|
|
95
|
+
// as this workaround is only for Safari
|
|
96
|
+
|
|
97
|
+
// This is how the event count works:
|
|
98
|
+
//
|
|
99
|
+
// lift (+1 enterCount)
|
|
100
|
+
// - dragstart(draggable) [enterCount: 0]
|
|
101
|
+
// - dragenter(draggable) [enterCount: 1]
|
|
102
|
+
// leaving draggable (+0 enterCount)
|
|
103
|
+
// - dragenter(document.body) [enterCount: 2]
|
|
104
|
+
// - dragleave(draggable) [enterCount: 1]
|
|
105
|
+
// leaving window (-1 enterCount)
|
|
106
|
+
// - dragleave(document.body) [enterCount: 0] {leaving the window}
|
|
107
|
+
|
|
108
|
+
// Things to note:
|
|
109
|
+
// - dragenter and dragleave bubble
|
|
110
|
+
// - the first dragenter when entering a window might not be on `window`
|
|
111
|
+
// - it could be on an element that is pressed up against the window
|
|
112
|
+
// - (so we cannot rely on `event.target` values)
|
|
113
|
+
|
|
114
|
+
bindAll(window, [{
|
|
115
|
+
type: 'dragstart',
|
|
116
|
+
listener: () => {
|
|
117
|
+
state.enterCount = 0;
|
|
118
|
+
// drag start occurs in the source window
|
|
119
|
+
state.isOverWindow = true;
|
|
120
|
+
|
|
121
|
+
// When a drag first starts it will also trigger a "dragenter" on the draggable element
|
|
122
|
+
}
|
|
123
|
+
}, {
|
|
124
|
+
type: 'drop',
|
|
125
|
+
listener: resetState
|
|
126
|
+
}, {
|
|
127
|
+
type: 'dragend',
|
|
128
|
+
listener: resetState
|
|
129
|
+
}, {
|
|
130
|
+
type: 'dragenter',
|
|
131
|
+
listener: event => {
|
|
132
|
+
if (!state.isOverWindow && state.enterCount === 0) {
|
|
133
|
+
// Patching the `event` object
|
|
134
|
+
// The `event` object is shared with all event listeners for the event
|
|
135
|
+
// @ts-ignore
|
|
136
|
+
event[safariFix.enteringWindow] = true;
|
|
137
|
+
}
|
|
138
|
+
state.isOverWindow = true;
|
|
139
|
+
state.enterCount++;
|
|
140
|
+
}
|
|
141
|
+
}, {
|
|
142
|
+
type: 'dragleave',
|
|
143
|
+
listener: event => {
|
|
144
|
+
state.enterCount--;
|
|
145
|
+
if (state.isOverWindow && state.enterCount === 0) {
|
|
146
|
+
// Patching the `event` object as it is shared with all event listeners
|
|
147
|
+
// The `event` object is shared with all event listeners for the event
|
|
148
|
+
// @ts-ignore
|
|
149
|
+
event[safariFix.leavingWindow] = true;
|
|
150
|
+
state.isOverWindow = false;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}],
|
|
154
|
+
// using `capture: true` so that adding event listeners
|
|
155
|
+
// in bubble phase will have the correct symbols
|
|
156
|
+
{
|
|
157
|
+
capture: true
|
|
158
|
+
});
|
|
159
|
+
})();
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { bindAll } from 'bind-event-listener';
|
|
2
|
+
/** Set a `style` property on a `HTMLElement`
|
|
3
|
+
*
|
|
4
|
+
* @returns a `cleanup` function to restore the `style` property to it's original state
|
|
5
|
+
*/
|
|
6
|
+
function setStyle(el, {
|
|
7
|
+
property,
|
|
8
|
+
rule,
|
|
9
|
+
priority = ''
|
|
10
|
+
}) {
|
|
11
|
+
const originalValue = el.style.getPropertyValue(property);
|
|
12
|
+
const originalPriority = el.style.getPropertyPriority(property);
|
|
13
|
+
el.style.setProperty(property, rule, priority);
|
|
14
|
+
return function cleanup() {
|
|
15
|
+
el.style.setProperty(property, originalValue, originalPriority);
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Allow the user to continue to interact with the element their pointer is over at the end of the drag.
|
|
21
|
+
* This is important to allow the user to be able to click, drag (etc) after they have finished a drag
|
|
22
|
+
*
|
|
23
|
+
* @returns a `cleanup` function to restore all elements under the users pointer to their original state
|
|
24
|
+
*/
|
|
25
|
+
function allowPointerEventsOnElementUnderPointer({
|
|
26
|
+
current
|
|
27
|
+
}) {
|
|
28
|
+
const underUsersPointer = document.elementFromPoint(current.input.clientX, current.input.clientY);
|
|
29
|
+
if (!(underUsersPointer instanceof HTMLElement)) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Debug note: change from 'pointer-events: none' to 'background: green'
|
|
34
|
+
// to get a better sense of what is being achieved
|
|
35
|
+
return setStyle(underUsersPointer, {
|
|
36
|
+
property: 'pointer-events',
|
|
37
|
+
rule: 'auto',
|
|
38
|
+
priority: 'important'
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
function blockPointerEventsOnEverything() {
|
|
42
|
+
var _element$sheet;
|
|
43
|
+
const element = document.createElement('style');
|
|
44
|
+
// Adding a data attribute so to make it super clear to consumers
|
|
45
|
+
// (and to our tests) what this temporary style tag is for
|
|
46
|
+
element.setAttribute('pdnd-post-drag-fix', 'true');
|
|
47
|
+
document.head.appendChild(element);
|
|
48
|
+
|
|
49
|
+
// Debug note: change from 'pointer-events: none' to 'background: red'
|
|
50
|
+
// to get a better sense of what is being achieved
|
|
51
|
+
(_element$sheet = element.sheet) === null || _element$sheet === void 0 ? void 0 : _element$sheet.insertRule('* { pointer-events: none !important; }');
|
|
52
|
+
return function cleanup() {
|
|
53
|
+
document.head.removeChild(element);
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** 🔥🤮 Fix (Chrome, Safari and Firefox) bug where the element under where the user started dragging
|
|
58
|
+
* (on the viewport) is entered into by the browser after a drag finishes ("drop" or "dragend")
|
|
59
|
+
*
|
|
60
|
+
* @description
|
|
61
|
+
*
|
|
62
|
+
* Block pointer events on all elements except for the specific element that pointer is currently over
|
|
63
|
+
*
|
|
64
|
+
* - [Visual explanation of bug](https://twitter.com/alexandereardon/status/1633614212873465856)
|
|
65
|
+
* - [Chrome bug](https://bugs.chromium.org/p/chromium/issues/detail?id=410328)
|
|
66
|
+
*/
|
|
67
|
+
export function fixPostDragPointerBug({
|
|
68
|
+
current
|
|
69
|
+
}) {
|
|
70
|
+
// Queuing a microtask to give any opportunity for frameworks to update their UI in a microtask
|
|
71
|
+
// Note: react@18 does standard state updates in a microtask
|
|
72
|
+
// We do this so our `atDestination` gets the _actual_ element that is under the users pointer
|
|
73
|
+
// at the end of the drag.
|
|
74
|
+
queueMicrotask(() => {
|
|
75
|
+
const undoUnderPointer = allowPointerEventsOnElementUnderPointer({
|
|
76
|
+
current
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// This will also block pointer-events on the children of the element under the users pointer.
|
|
80
|
+
// This is what we want. If the user drops on a container element we don't want the children
|
|
81
|
+
// of the container to be incorrectly entered into
|
|
82
|
+
const undoGlobalBlock = blockPointerEventsOnEverything();
|
|
83
|
+
function cleanup() {
|
|
84
|
+
unbindEvents();
|
|
85
|
+
undoUnderPointer === null || undoUnderPointer === void 0 ? void 0 : undoUnderPointer();
|
|
86
|
+
undoGlobalBlock();
|
|
87
|
+
}
|
|
88
|
+
const unbindEvents = bindAll(window, [{
|
|
89
|
+
type: 'pointerdown',
|
|
90
|
+
listener: cleanup
|
|
91
|
+
}, {
|
|
92
|
+
type: 'pointermove',
|
|
93
|
+
listener: cleanup
|
|
94
|
+
}, {
|
|
95
|
+
type: 'focusin',
|
|
96
|
+
listener: cleanup
|
|
97
|
+
}, {
|
|
98
|
+
type: 'focusout',
|
|
99
|
+
listener: cleanup
|
|
100
|
+
},
|
|
101
|
+
// a 'pointerdown' should happen before 'dragstart', but just being super safe
|
|
102
|
+
{
|
|
103
|
+
type: 'dragstart',
|
|
104
|
+
listener: cleanup
|
|
105
|
+
}], {
|
|
106
|
+
// Using `capture` is more likely to not be impacted by consumers stopping events
|
|
107
|
+
capture: true
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function getInput(event) {
|
|
2
|
+
return {
|
|
3
|
+
altKey: event.altKey,
|
|
4
|
+
button: event.button,
|
|
5
|
+
buttons: event.buttons,
|
|
6
|
+
ctrlKey: event.ctrlKey,
|
|
7
|
+
metaKey: event.metaKey,
|
|
8
|
+
shiftKey: event.shiftKey,
|
|
9
|
+
clientX: event.clientX,
|
|
10
|
+
clientY: event.clientY,
|
|
11
|
+
pageX: event.pageX,
|
|
12
|
+
pageY: event.pageY
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Provide a function that you only ever want to be called a single time */
|
|
2
|
+
export function once(fn) {
|
|
3
|
+
let cache = null;
|
|
4
|
+
return function wrapped(...args) {
|
|
5
|
+
if (!cache) {
|
|
6
|
+
const result = fn.apply(this, args);
|
|
7
|
+
cache = {
|
|
8
|
+
result: result
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
return cache.result;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reorder a provided `list`
|
|
3
|
+
* Returns a new array and does not modify the original array
|
|
4
|
+
*/
|
|
5
|
+
export function reorder({
|
|
6
|
+
list,
|
|
7
|
+
startIndex,
|
|
8
|
+
finishIndex
|
|
9
|
+
}) {
|
|
10
|
+
if (startIndex === -1 || finishIndex === -1) {
|
|
11
|
+
return list;
|
|
12
|
+
}
|
|
13
|
+
const result = Array.from(list);
|
|
14
|
+
const [removed] = result.splice(startIndex, 1);
|
|
15
|
+
result.splice(finishIndex, 0, removed);
|
|
16
|
+
return result;
|
|
17
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scroll an `element` just enough into view so that the element becomes totally visible.
|
|
3
|
+
* If the element is already totally visible then no scrolling will occur.
|
|
4
|
+
*/
|
|
5
|
+
export function scrollJustEnoughIntoView({
|
|
6
|
+
element
|
|
7
|
+
}) {
|
|
8
|
+
element.scrollIntoView({
|
|
9
|
+
block: 'nearest',
|
|
10
|
+
inline: 'nearest'
|
|
11
|
+
});
|
|
12
|
+
}
|