@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.
Files changed (155) hide show
  1. package/CHANGELOG.md +209 -0
  2. package/LICENSE.md +13 -0
  3. package/README.md +43 -0
  4. package/__perf__/add-example.todo +1 -0
  5. package/adapter/element/package.json +15 -0
  6. package/adapter/file/package.json +15 -0
  7. package/addon/cancel-unhandled/package.json +15 -0
  8. package/constellation/index/about.mdx +329 -0
  9. package/constellation/index/props.mdx +3 -0
  10. package/dist/cjs/adapter/element-adapter.js +151 -0
  11. package/dist/cjs/adapter/file-adapter.js +98 -0
  12. package/dist/cjs/addon/cancel-unhandled.js +50 -0
  13. package/dist/cjs/entry-point/adapter/element.js +24 -0
  14. package/dist/cjs/entry-point/adapter/file.js +18 -0
  15. package/dist/cjs/entry-point/addon/cancel-unhandled.js +12 -0
  16. package/dist/cjs/entry-point/experimental/cross-with-element-adapter.js +30 -0
  17. package/dist/cjs/entry-point/types.js +5 -0
  18. package/dist/cjs/entry-point/util/combine.js +12 -0
  19. package/dist/cjs/entry-point/util/disable-native-drag-preview.js +12 -0
  20. package/dist/cjs/entry-point/util/once.js +12 -0
  21. package/dist/cjs/entry-point/util/reorder.js +12 -0
  22. package/dist/cjs/entry-point/util/scroll-just-enough-into-view.js +12 -0
  23. package/dist/cjs/entry-point/util/set-custom-native-drag-preview.js +12 -0
  24. package/dist/cjs/experimental/cross-window-element-adapter.js +131 -0
  25. package/dist/cjs/index.js +12 -0
  26. package/dist/cjs/internal-types.js +5 -0
  27. package/dist/cjs/ledger/dispatch-consumer-event.js +132 -0
  28. package/dist/cjs/ledger/lifecycle-manager.js +335 -0
  29. package/dist/cjs/ledger/usage-ledger.js +37 -0
  30. package/dist/cjs/make-adapter/make-adapter.js +59 -0
  31. package/dist/cjs/make-adapter/make-drop-target.js +271 -0
  32. package/dist/cjs/make-adapter/make-monitor.js +100 -0
  33. package/dist/cjs/util/add-attribute.js +14 -0
  34. package/dist/cjs/util/combine.js +17 -0
  35. package/dist/cjs/util/disable-native-drag-preview.js +36 -0
  36. package/dist/cjs/util/entering-and-leaving-the-window.js +162 -0
  37. package/dist/cjs/util/fix-post-drag-pointer-bug.js +114 -0
  38. package/dist/cjs/util/get-input.js +20 -0
  39. package/dist/cjs/util/once.js +22 -0
  40. package/dist/cjs/util/reorder.js +26 -0
  41. package/dist/cjs/util/scroll-just-enough-into-view.js +17 -0
  42. package/dist/cjs/util/set-custom-native-drag-preview.js +109 -0
  43. package/dist/cjs/version.json +5 -0
  44. package/dist/es2019/adapter/element-adapter.js +143 -0
  45. package/dist/es2019/adapter/file-adapter.js +90 -0
  46. package/dist/es2019/addon/cancel-unhandled.js +43 -0
  47. package/dist/es2019/entry-point/adapter/element.js +1 -0
  48. package/dist/es2019/entry-point/adapter/file.js +1 -0
  49. package/dist/es2019/entry-point/addon/cancel-unhandled.js +1 -0
  50. package/dist/es2019/entry-point/experimental/cross-with-element-adapter.js +1 -0
  51. package/dist/es2019/entry-point/types.js +1 -0
  52. package/dist/es2019/entry-point/util/combine.js +1 -0
  53. package/dist/es2019/entry-point/util/disable-native-drag-preview.js +1 -0
  54. package/dist/es2019/entry-point/util/once.js +1 -0
  55. package/dist/es2019/entry-point/util/reorder.js +1 -0
  56. package/dist/es2019/entry-point/util/scroll-just-enough-into-view.js +1 -0
  57. package/dist/es2019/entry-point/util/set-custom-native-drag-preview.js +1 -0
  58. package/dist/es2019/experimental/cross-window-element-adapter.js +121 -0
  59. package/dist/es2019/index.js +7 -0
  60. package/dist/es2019/internal-types.js +1 -0
  61. package/dist/es2019/ledger/dispatch-consumer-event.js +128 -0
  62. package/dist/es2019/ledger/lifecycle-manager.js +333 -0
  63. package/dist/es2019/ledger/usage-ledger.js +32 -0
  64. package/dist/es2019/make-adapter/make-adapter.js +55 -0
  65. package/dist/es2019/make-adapter/make-drop-target.js +233 -0
  66. package/dist/es2019/make-adapter/make-monitor.js +80 -0
  67. package/dist/es2019/util/add-attribute.js +7 -0
  68. package/dist/es2019/util/combine.js +6 -0
  69. package/dist/es2019/util/disable-native-drag-preview.js +31 -0
  70. package/dist/es2019/util/entering-and-leaving-the-window.js +159 -0
  71. package/dist/es2019/util/fix-post-drag-pointer-bug.js +110 -0
  72. package/dist/es2019/util/get-input.js +14 -0
  73. package/dist/es2019/util/once.js +13 -0
  74. package/dist/es2019/util/reorder.js +17 -0
  75. package/dist/es2019/util/scroll-just-enough-into-view.js +12 -0
  76. package/dist/es2019/util/set-custom-native-drag-preview.js +106 -0
  77. package/dist/es2019/version.json +5 -0
  78. package/dist/esm/adapter/element-adapter.js +142 -0
  79. package/dist/esm/adapter/file-adapter.js +90 -0
  80. package/dist/esm/addon/cancel-unhandled.js +43 -0
  81. package/dist/esm/entry-point/adapter/element.js +1 -0
  82. package/dist/esm/entry-point/adapter/file.js +1 -0
  83. package/dist/esm/entry-point/addon/cancel-unhandled.js +1 -0
  84. package/dist/esm/entry-point/experimental/cross-with-element-adapter.js +1 -0
  85. package/dist/esm/entry-point/types.js +1 -0
  86. package/dist/esm/entry-point/util/combine.js +1 -0
  87. package/dist/esm/entry-point/util/disable-native-drag-preview.js +1 -0
  88. package/dist/esm/entry-point/util/once.js +1 -0
  89. package/dist/esm/entry-point/util/reorder.js +1 -0
  90. package/dist/esm/entry-point/util/scroll-just-enough-into-view.js +1 -0
  91. package/dist/esm/entry-point/util/set-custom-native-drag-preview.js +1 -0
  92. package/dist/esm/experimental/cross-window-element-adapter.js +120 -0
  93. package/dist/esm/index.js +7 -0
  94. package/dist/esm/internal-types.js +1 -0
  95. package/dist/esm/ledger/dispatch-consumer-event.js +125 -0
  96. package/dist/esm/ledger/lifecycle-manager.js +328 -0
  97. package/dist/esm/ledger/usage-ledger.js +31 -0
  98. package/dist/esm/make-adapter/make-adapter.js +53 -0
  99. package/dist/esm/make-adapter/make-drop-target.js +264 -0
  100. package/dist/esm/make-adapter/make-monitor.js +93 -0
  101. package/dist/esm/util/add-attribute.js +8 -0
  102. package/dist/esm/util/combine.js +11 -0
  103. package/dist/esm/util/disable-native-drag-preview.js +30 -0
  104. package/dist/esm/util/entering-and-leaving-the-window.js +156 -0
  105. package/dist/esm/util/fix-post-drag-pointer-bug.js +108 -0
  106. package/dist/esm/util/get-input.js +14 -0
  107. package/dist/esm/util/once.js +16 -0
  108. package/dist/esm/util/reorder.js +19 -0
  109. package/dist/esm/util/scroll-just-enough-into-view.js +11 -0
  110. package/dist/esm/util/set-custom-native-drag-preview.js +104 -0
  111. package/dist/esm/version.json +5 -0
  112. package/dist/types/adapter/element-adapter.d.ts +42 -0
  113. package/dist/types/adapter/file-adapter.d.ts +18 -0
  114. package/dist/types/addon/cancel-unhandled.d.ts +7 -0
  115. package/dist/types/entry-point/adapter/element.d.ts +2 -0
  116. package/dist/types/entry-point/adapter/file.d.ts +2 -0
  117. package/dist/types/entry-point/addon/cancel-unhandled.d.ts +1 -0
  118. package/dist/types/entry-point/experimental/cross-with-element-adapter.d.ts +1 -0
  119. package/dist/types/entry-point/types.d.ts +1 -0
  120. package/dist/types/entry-point/util/combine.d.ts +1 -0
  121. package/dist/types/entry-point/util/disable-native-drag-preview.d.ts +1 -0
  122. package/dist/types/entry-point/util/once.d.ts +1 -0
  123. package/dist/types/entry-point/util/reorder.d.ts +1 -0
  124. package/dist/types/entry-point/util/scroll-just-enough-into-view.d.ts +1 -0
  125. package/dist/types/entry-point/util/set-custom-native-drag-preview.d.ts +1 -0
  126. package/dist/types/experimental/cross-window-element-adapter.d.ts +17 -0
  127. package/dist/types/index.d.ts +2 -0
  128. package/dist/types/internal-types.d.ts +275 -0
  129. package/dist/types/ledger/dispatch-consumer-event.d.ts +26 -0
  130. package/dist/types/ledger/lifecycle-manager.d.ts +16 -0
  131. package/dist/types/ledger/usage-ledger.d.ts +5 -0
  132. package/dist/types/make-adapter/make-adapter.d.ts +14 -0
  133. package/dist/types/make-adapter/make-drop-target.d.ts +5 -0
  134. package/dist/types/make-adapter/make-monitor.d.ts +8 -0
  135. package/dist/types/util/add-attribute.d.ts +5 -0
  136. package/dist/types/util/combine.d.ts +3 -0
  137. package/dist/types/util/disable-native-drag-preview.d.ts +3 -0
  138. package/dist/types/util/entering-and-leaving-the-window.d.ts +6 -0
  139. package/dist/types/util/fix-post-drag-pointer-bug.d.ts +14 -0
  140. package/dist/types/util/get-input.d.ts +2 -0
  141. package/dist/types/util/once.d.ts +2 -0
  142. package/dist/types/util/reorder.d.ts +9 -0
  143. package/dist/types/util/scroll-just-enough-into-view.d.ts +7 -0
  144. package/dist/types/util/set-custom-native-drag-preview.d.ts +52 -0
  145. package/experimental/cross-window-element-adapter/package.json +15 -0
  146. package/package.json +87 -0
  147. package/report.api.md +35 -0
  148. package/tmp/api-report-tmp.d.ts +13 -0
  149. package/types/package.json +15 -0
  150. package/util/combine/package.json +15 -0
  151. package/util/disable-native-drag-preview/package.json +15 -0
  152. package/util/once/package.json +15 -0
  153. package/util/reorder/package.json +15 -0
  154. package/util/scroll-just-enough-into-view/package.json +15 -0
  155. 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,7 @@
1
+ // This file exists for module resolution purposes.
2
+
3
+ // Not exporting anything from the root entry point
4
+ // Please use individual entry points in order to always
5
+ // obtain minimum kbs.
6
+
7
+ export default {};
@@ -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
+ }