@planningcenter/react-beautiful-dnd 13.2.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 (243) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/LICENSE +13 -0
  3. package/README.md +178 -0
  4. package/dist/react-beautiful-dnd.cjs.js +8728 -0
  5. package/dist/react-beautiful-dnd.cjs.js.flow +3 -0
  6. package/dist/react-beautiful-dnd.esm.js +8715 -0
  7. package/dist/react-beautiful-dnd.js +11726 -0
  8. package/dist/react-beautiful-dnd.min.js +1 -0
  9. package/package.json +155 -0
  10. package/src/animation.js +75 -0
  11. package/src/debug/middleware/action-timing-average.js +52 -0
  12. package/src/debug/middleware/action-timing.js +16 -0
  13. package/src/debug/middleware/log.js +26 -0
  14. package/src/debug/middleware/user-timing.js +16 -0
  15. package/src/debug/timings.js +86 -0
  16. package/src/dev-warning.js +50 -0
  17. package/src/empty.js +6 -0
  18. package/src/index.js +67 -0
  19. package/src/invariant.js +33 -0
  20. package/src/native-with-fallback.js +69 -0
  21. package/src/screen-reader-message-preset.js +134 -0
  22. package/src/state/action-creators.js +328 -0
  23. package/src/state/auto-scroller/auto-scroller-types.js +8 -0
  24. package/src/state/auto-scroller/can-scroll.js +160 -0
  25. package/src/state/auto-scroller/fluid-scroller/config.js +25 -0
  26. package/src/state/auto-scroller/fluid-scroller/did-start-in-scrollable-area.js +1 -0
  27. package/src/state/auto-scroller/fluid-scroller/get-best-scrollable-droppable.js +77 -0
  28. package/src/state/auto-scroller/fluid-scroller/get-droppable-scroll-change.js +50 -0
  29. package/src/state/auto-scroller/fluid-scroller/get-percentage.js +25 -0
  30. package/src/state/auto-scroller/fluid-scroller/get-scroll/adjust-for-size-limits.js +30 -0
  31. package/src/state/auto-scroller/fluid-scroller/get-scroll/buffer-thresholds/calc-axis-scroll-conditions.js +68 -0
  32. package/src/state/auto-scroller/fluid-scroller/get-scroll/buffer-thresholds/get-scroll-conditions.js +56 -0
  33. package/src/state/auto-scroller/fluid-scroller/get-scroll/buffer-thresholds/index.js +66 -0
  34. package/src/state/auto-scroller/fluid-scroller/get-scroll/get-scroll-on-axis/dampen-value-by-time.js +48 -0
  35. package/src/state/auto-scroller/fluid-scroller/get-scroll/get-scroll-on-axis/get-distance-thresholds.js +31 -0
  36. package/src/state/auto-scroller/fluid-scroller/get-scroll/get-scroll-on-axis/get-value-from-distance.js +67 -0
  37. package/src/state/auto-scroller/fluid-scroller/get-scroll/get-scroll-on-axis/get-value.js +51 -0
  38. package/src/state/auto-scroller/fluid-scroller/get-scroll/get-scroll-on-axis/index.js +50 -0
  39. package/src/state/auto-scroller/fluid-scroller/get-scroll/get-scroll-on-axis/min-scroll.js +4 -0
  40. package/src/state/auto-scroller/fluid-scroller/get-scroll/index.js +139 -0
  41. package/src/state/auto-scroller/fluid-scroller/get-window-scroll-change.js +43 -0
  42. package/src/state/auto-scroller/fluid-scroller/index.js +99 -0
  43. package/src/state/auto-scroller/fluid-scroller/scroll.js +80 -0
  44. package/src/state/auto-scroller/index.js +59 -0
  45. package/src/state/auto-scroller/jump-scroller.js +139 -0
  46. package/src/state/axis.js +26 -0
  47. package/src/state/calculate-drag-impact/calculate-reorder-impact.js +136 -0
  48. package/src/state/can-start-drag.js +29 -0
  49. package/src/state/create-store.js +97 -0
  50. package/src/state/did-start-after-critical.js +9 -0
  51. package/src/state/dimension-marshal/dimension-marshal-types.js +46 -0
  52. package/src/state/dimension-marshal/dimension-marshal.js +218 -0
  53. package/src/state/dimension-marshal/get-initial-publish.js +66 -0
  54. package/src/state/dimension-marshal/while-dragging-publisher.js +146 -0
  55. package/src/state/dimension-structures.js +35 -0
  56. package/src/state/droppable/get-droppable.js +101 -0
  57. package/src/state/droppable/is-home-of.js +7 -0
  58. package/src/state/droppable/scroll-droppable.js +53 -0
  59. package/src/state/droppable/should-use-placeholder.js +7 -0
  60. package/src/state/droppable/util/clip.js +17 -0
  61. package/src/state/droppable/util/get-subject.js +63 -0
  62. package/src/state/droppable/what-is-dragged-over-from-result.js +16 -0
  63. package/src/state/droppable/what-is-dragged-over.js +16 -0
  64. package/src/state/droppable/with-placeholder.js +174 -0
  65. package/src/state/get-center-from-impact/get-client-border-box-center/get-client-from-page-border-box-center.js +29 -0
  66. package/src/state/get-center-from-impact/get-client-border-box-center/index.js +44 -0
  67. package/src/state/get-center-from-impact/get-page-border-box-center/index.js +70 -0
  68. package/src/state/get-center-from-impact/get-page-border-box-center/when-combining.js +39 -0
  69. package/src/state/get-center-from-impact/get-page-border-box-center/when-reordering.js +112 -0
  70. package/src/state/get-center-from-impact/move-relative-to.js +66 -0
  71. package/src/state/get-combined-item-displacement.js +34 -0
  72. package/src/state/get-displaced-by.js +17 -0
  73. package/src/state/get-displacement-groups.js +130 -0
  74. package/src/state/get-drag-impact/get-combine-impact.js +130 -0
  75. package/src/state/get-drag-impact/get-reorder-impact.js +143 -0
  76. package/src/state/get-drag-impact/index.js +93 -0
  77. package/src/state/get-draggables-inside-droppable.js +31 -0
  78. package/src/state/get-droppable-over.js +158 -0
  79. package/src/state/get-frame.js +10 -0
  80. package/src/state/get-home-location.js +7 -0
  81. package/src/state/get-impact-location.js +16 -0
  82. package/src/state/get-is-displaced.js +11 -0
  83. package/src/state/get-lift-effect.js +82 -0
  84. package/src/state/get-max-scroll.js +30 -0
  85. package/src/state/is-movement-allowed.js +6 -0
  86. package/src/state/is-within.js +9 -0
  87. package/src/state/middleware/auto-scroll.js +38 -0
  88. package/src/state/middleware/dimension-marshal-stopper.js +20 -0
  89. package/src/state/middleware/drop/drop-animation-finish-middleware.js +21 -0
  90. package/src/state/middleware/drop/drop-animation-flush-on-scroll-middleware.js +63 -0
  91. package/src/state/middleware/drop/drop-middleware.js +146 -0
  92. package/src/state/middleware/drop/get-drop-duration.js +47 -0
  93. package/src/state/middleware/drop/get-drop-impact.js +77 -0
  94. package/src/state/middleware/drop/get-new-home-client-offset.js +54 -0
  95. package/src/state/middleware/drop/index.js +2 -0
  96. package/src/state/middleware/focus.js +42 -0
  97. package/src/state/middleware/lift.js +66 -0
  98. package/src/state/middleware/pending-drop.js +37 -0
  99. package/src/state/middleware/responders/async-marshal.js +55 -0
  100. package/src/state/middleware/responders/expiring-announce.js +44 -0
  101. package/src/state/middleware/responders/index.js +2 -0
  102. package/src/state/middleware/responders/is-equal.js +57 -0
  103. package/src/state/middleware/responders/publisher.js +253 -0
  104. package/src/state/middleware/responders/responders-middleware.js +75 -0
  105. package/src/state/middleware/scroll-listener.js +31 -0
  106. package/src/state/middleware/style.js +22 -0
  107. package/src/state/middleware/util/validate-dimensions.js +71 -0
  108. package/src/state/move-in-direction/index.js +84 -0
  109. package/src/state/move-in-direction/move-cross-axis/get-best-cross-axis-droppable.js +168 -0
  110. package/src/state/move-in-direction/move-cross-axis/get-closest-draggable.js +79 -0
  111. package/src/state/move-in-direction/move-cross-axis/index.js +109 -0
  112. package/src/state/move-in-direction/move-cross-axis/move-to-new-droppable.js +121 -0
  113. package/src/state/move-in-direction/move-cross-axis/without-starting-displacement.js +31 -0
  114. package/src/state/move-in-direction/move-in-direction-types.js +9 -0
  115. package/src/state/move-in-direction/move-to-next-place/index.js +132 -0
  116. package/src/state/move-in-direction/move-to-next-place/is-totally-visible-in-new-location.js +52 -0
  117. package/src/state/move-in-direction/move-to-next-place/move-to-next-combine/index.js +97 -0
  118. package/src/state/move-in-direction/move-to-next-place/move-to-next-index/from-combine.js +52 -0
  119. package/src/state/move-in-direction/move-to-next-place/move-to-next-index/from-reorder.js +43 -0
  120. package/src/state/move-in-direction/move-to-next-place/move-to-next-index/index.js +86 -0
  121. package/src/state/no-impact.js +33 -0
  122. package/src/state/patch-dimension-map.js +11 -0
  123. package/src/state/patch-droppable-map.js +10 -0
  124. package/src/state/position.js +58 -0
  125. package/src/state/post-reducer/when-moving/refresh-snap.js +67 -0
  126. package/src/state/post-reducer/when-moving/update.js +120 -0
  127. package/src/state/publish-while-dragging-in-virtual/adjust-additions-for-scroll-changes.js +58 -0
  128. package/src/state/publish-while-dragging-in-virtual/index.js +158 -0
  129. package/src/state/publish-while-dragging-in-virtual/offset-draggable.js +35 -0
  130. package/src/state/recompute-placeholders.js +99 -0
  131. package/src/state/rect.js +7 -0
  132. package/src/state/reducer.js +457 -0
  133. package/src/state/registry/create-registry.js +162 -0
  134. package/src/state/registry/registry-types.js +98 -0
  135. package/src/state/registry/use-registry.js +20 -0
  136. package/src/state/remove-draggable-from-list.js +13 -0
  137. package/src/state/scroll-viewport.js +34 -0
  138. package/src/state/spacing.js +45 -0
  139. package/src/state/store-types.js +16 -0
  140. package/src/state/update-displacement-visibility/recompute.js +54 -0
  141. package/src/state/update-displacement-visibility/speculatively-increase.js +111 -0
  142. package/src/state/visibility/is-partially-visible-through-frame.js +60 -0
  143. package/src/state/visibility/is-position-in-frame.js +12 -0
  144. package/src/state/visibility/is-totally-visible-through-frame-on-axis.js +19 -0
  145. package/src/state/visibility/is-totally-visible-through-frame.js +18 -0
  146. package/src/state/visibility/is-visible.js +102 -0
  147. package/src/state/with-scroll-change/with-all-displacement.js +15 -0
  148. package/src/state/with-scroll-change/with-droppable-displacement.js +13 -0
  149. package/src/state/with-scroll-change/with-droppable-scroll.js +13 -0
  150. package/src/state/with-scroll-change/with-viewport-displacement.js +7 -0
  151. package/src/types.js +542 -0
  152. package/src/view/animate-in-out/animate-in-out.jsx +95 -0
  153. package/src/view/animate-in-out/index.js +2 -0
  154. package/src/view/check-is-valid-inner-ref.js +15 -0
  155. package/src/view/context/app-context.js +19 -0
  156. package/src/view/context/droppable-context.js +11 -0
  157. package/src/view/context/store-context.js +5 -0
  158. package/src/view/data-attributes.js +37 -0
  159. package/src/view/drag-drop-context/app.jsx +273 -0
  160. package/src/view/drag-drop-context/check-doctype.js +39 -0
  161. package/src/view/drag-drop-context/check-react-version.js +71 -0
  162. package/src/view/drag-drop-context/drag-drop-context-types.js +7 -0
  163. package/src/view/drag-drop-context/drag-drop-context.jsx +68 -0
  164. package/src/view/drag-drop-context/error-boundary.jsx +88 -0
  165. package/src/view/drag-drop-context/index.js +2 -0
  166. package/src/view/drag-drop-context/use-startup-validation.js +13 -0
  167. package/src/view/drag-drop-context/use-unique-context-id.js +13 -0
  168. package/src/view/draggable/connected-draggable.js +372 -0
  169. package/src/view/draggable/draggable-api.jsx +48 -0
  170. package/src/view/draggable/draggable-types.js +191 -0
  171. package/src/view/draggable/draggable.jsx +171 -0
  172. package/src/view/draggable/get-style.js +109 -0
  173. package/src/view/draggable/index.js +2 -0
  174. package/src/view/draggable/use-validation.js +70 -0
  175. package/src/view/droppable/connected-droppable.js +280 -0
  176. package/src/view/droppable/droppable-types.js +91 -0
  177. package/src/view/droppable/droppable.jsx +167 -0
  178. package/src/view/droppable/index.js +2 -0
  179. package/src/view/droppable/use-validation.js +101 -0
  180. package/src/view/event-bindings/bind-events.js +39 -0
  181. package/src/view/event-bindings/event-types.js +14 -0
  182. package/src/view/get-body-element.js +8 -0
  183. package/src/view/get-border-box-center-position.js +5 -0
  184. package/src/view/get-document-element.js +8 -0
  185. package/src/view/get-elements/find-drag-handle.js +38 -0
  186. package/src/view/get-elements/find-draggable.js +30 -0
  187. package/src/view/is-strict-equal.js +2 -0
  188. package/src/view/is-type-of-element/is-element.js +6 -0
  189. package/src/view/is-type-of-element/is-html-element.js +6 -0
  190. package/src/view/is-type-of-element/is-svg-element.js +12 -0
  191. package/src/view/key-codes.js +13 -0
  192. package/src/view/placeholder/index.js +2 -0
  193. package/src/view/placeholder/placeholder-types.js +16 -0
  194. package/src/view/placeholder/placeholder.jsx +198 -0
  195. package/src/view/scroll-listener.js +72 -0
  196. package/src/view/throw-if-invalid-inner-ref.js +15 -0
  197. package/src/view/use-announcer/index.js +2 -0
  198. package/src/view/use-announcer/use-announcer.js +80 -0
  199. package/src/view/use-dev-setup-warning.js +22 -0
  200. package/src/view/use-dev.js +9 -0
  201. package/src/view/use-draggable-publisher/get-dimension.js +44 -0
  202. package/src/view/use-draggable-publisher/index.js +2 -0
  203. package/src/view/use-draggable-publisher/use-draggable-publisher.js +87 -0
  204. package/src/view/use-droppable-publisher/check-for-nested-scroll-container.js +27 -0
  205. package/src/view/use-droppable-publisher/get-closest-scrollable.js +95 -0
  206. package/src/view/use-droppable-publisher/get-dimension.js +139 -0
  207. package/src/view/use-droppable-publisher/get-env.js +31 -0
  208. package/src/view/use-droppable-publisher/get-listener-options.js +12 -0
  209. package/src/view/use-droppable-publisher/get-scroll.js +7 -0
  210. package/src/view/use-droppable-publisher/index.js +2 -0
  211. package/src/view/use-droppable-publisher/is-in-fixed-container.js +21 -0
  212. package/src/view/use-droppable-publisher/use-droppable-publisher.js +283 -0
  213. package/src/view/use-focus-marshal/focus-marshal-types.js +13 -0
  214. package/src/view/use-focus-marshal/index.js +2 -0
  215. package/src/view/use-focus-marshal/use-focus-marshal.js +129 -0
  216. package/src/view/use-hidden-text-element/index.js +2 -0
  217. package/src/view/use-hidden-text-element/use-hidden-text-element.js +60 -0
  218. package/src/view/use-isomorphic-layout-effect.js +18 -0
  219. package/src/view/use-previous-ref.js +14 -0
  220. package/src/view/use-required-context.js +9 -0
  221. package/src/view/use-sensor-marshal/closest.js +50 -0
  222. package/src/view/use-sensor-marshal/find-closest-draggable-id-from-event.js +50 -0
  223. package/src/view/use-sensor-marshal/index.js +5 -0
  224. package/src/view/use-sensor-marshal/is-event-in-interactive-element.js +66 -0
  225. package/src/view/use-sensor-marshal/lock.js +48 -0
  226. package/src/view/use-sensor-marshal/sensors/use-keyboard-sensor.js +243 -0
  227. package/src/view/use-sensor-marshal/sensors/use-mouse-sensor.js +386 -0
  228. package/src/view/use-sensor-marshal/sensors/use-touch-sensor.js +461 -0
  229. package/src/view/use-sensor-marshal/sensors/util/prevent-standard-key-events.js +19 -0
  230. package/src/view/use-sensor-marshal/sensors/util/supported-page-visibility-event-name.js +29 -0
  231. package/src/view/use-sensor-marshal/use-sensor-marshal.js +495 -0
  232. package/src/view/use-sensor-marshal/use-validate-sensor-hooks.js +20 -0
  233. package/src/view/use-style-marshal/get-styles.js +170 -0
  234. package/src/view/use-style-marshal/index.js +2 -0
  235. package/src/view/use-style-marshal/style-marshal-types.js +8 -0
  236. package/src/view/use-style-marshal/use-style-marshal.js +126 -0
  237. package/src/view/use-unique-id.js +25 -0
  238. package/src/view/visually-hidden-style.js +16 -0
  239. package/src/view/window/get-max-window-scroll.js +19 -0
  240. package/src/view/window/get-viewport.js +49 -0
  241. package/src/view/window/get-window-from-el.js +3 -0
  242. package/src/view/window/get-window-scroll.js +30 -0
  243. package/src/view/window/scroll-window.js +7 -0
@@ -0,0 +1,130 @@
1
+ // @flow
2
+ import { type Rect, type Spacing, expand, getRect } from 'css-box-model';
3
+ import type {
4
+ DraggableId,
5
+ Displacement,
6
+ DraggableDimension,
7
+ DroppableDimension,
8
+ DisplacementGroups,
9
+ DisplacedBy,
10
+ } from '../types';
11
+ import { isPartiallyVisible } from './visibility/is-visible';
12
+
13
+ type Args = {|
14
+ afterDragging: DraggableDimension[],
15
+ destination: DroppableDimension,
16
+ displacedBy: DisplacedBy,
17
+ last: ?DisplacementGroups,
18
+ viewport: Rect,
19
+ forceShouldAnimate?: boolean,
20
+ |};
21
+
22
+ const getShouldAnimate = (
23
+ id,
24
+ last: ?DisplacementGroups,
25
+ forceShouldAnimate: ?boolean,
26
+ ) => {
27
+ // Use a forced value if provided
28
+ if (typeof forceShouldAnimate === 'boolean') {
29
+ return forceShouldAnimate;
30
+ }
31
+
32
+ // nothing to gauge animation from
33
+ if (!last) {
34
+ return true;
35
+ }
36
+
37
+ const { invisible, visible } = last;
38
+
39
+ // it was previously invisible - no animation
40
+ if (invisible[id]) {
41
+ return false;
42
+ }
43
+
44
+ const previous: ?Displacement = visible[id];
45
+
46
+ return previous ? previous.shouldAnimate : true;
47
+ };
48
+
49
+ // Note: it is also an optimisation to not render the displacement on
50
+ // items when they are not longer visible.
51
+ // This prevents a lot of .render() calls when leaving / entering a list
52
+
53
+ function getTarget(
54
+ draggable: DraggableDimension,
55
+ displacedBy: DisplacedBy,
56
+ ): Rect {
57
+ const marginBox: Rect = draggable.page.marginBox;
58
+
59
+ // ## Visibility overscanning
60
+ // We are expanding rather than offsetting the marginBox.
61
+ // In some cases we want
62
+ // - the target based on the starting position (such as when dropping outside of any list)
63
+ // - the target based on the items position without starting displacement (such as when moving inside a list)
64
+ // To keep things simple we just expand the whole area for this check
65
+ // The worst case is some minor redundant offscreen movements
66
+ const expandBy: Spacing = {
67
+ // pull backwards into viewport
68
+ top: displacedBy.point.y,
69
+ right: 0,
70
+ bottom: 0,
71
+ // pull backwards into viewport
72
+ left: displacedBy.point.x,
73
+ };
74
+
75
+ return getRect(expand(marginBox, expandBy));
76
+ }
77
+
78
+ export default function getDisplacementGroups({
79
+ afterDragging,
80
+ destination,
81
+ displacedBy,
82
+ viewport,
83
+ forceShouldAnimate,
84
+ last,
85
+ }: Args): DisplacementGroups {
86
+ return afterDragging.reduce(
87
+ function process(
88
+ groups: DisplacementGroups,
89
+ draggable: DraggableDimension,
90
+ ): DisplacementGroups {
91
+ const target: Rect = getTarget(draggable, displacedBy);
92
+ const id: DraggableId = draggable.descriptor.id;
93
+
94
+ groups.all.push(id);
95
+
96
+ const isVisible: boolean = isPartiallyVisible({
97
+ target,
98
+ destination,
99
+ viewport,
100
+ withDroppableDisplacement: true,
101
+ });
102
+
103
+ if (!isVisible) {
104
+ groups.invisible[draggable.descriptor.id] = true;
105
+ return groups;
106
+ }
107
+
108
+ // item is visible
109
+
110
+ const shouldAnimate: boolean = getShouldAnimate(
111
+ id,
112
+ last,
113
+ forceShouldAnimate,
114
+ );
115
+
116
+ const displacement: Displacement = {
117
+ draggableId: id,
118
+ shouldAnimate,
119
+ };
120
+
121
+ groups.visible[id] = displacement;
122
+ return groups;
123
+ },
124
+ {
125
+ all: [],
126
+ visible: {},
127
+ invisible: {},
128
+ },
129
+ );
130
+ }
@@ -0,0 +1,130 @@
1
+ // @flow
2
+ import type { Rect } from 'css-box-model';
3
+ import type {
4
+ DraggableId,
5
+ Axis,
6
+ DraggableDimension,
7
+ DroppableDimension,
8
+ DragImpact,
9
+ LiftEffect,
10
+ DisplacedBy,
11
+ } from '../../types';
12
+ import { find } from '../../native-with-fallback';
13
+ import getDidStartAfterCritical from '../did-start-after-critical';
14
+ import getDisplacedBy from '../get-displaced-by';
15
+ import getIsDisplaced from '../get-is-displaced';
16
+ import removeDraggableFromList from '../remove-draggable-from-list';
17
+
18
+ type Args = {|
19
+ draggable: DraggableDimension,
20
+ pageBorderBoxWithDroppableScroll: Rect,
21
+ previousImpact: DragImpact,
22
+ destination: DroppableDimension,
23
+ insideDestination: DraggableDimension[],
24
+ afterCritical: LiftEffect,
25
+ |};
26
+
27
+ // exported for testing
28
+ export const combineThresholdDivisor: number = 4;
29
+
30
+ export default ({
31
+ draggable,
32
+ pageBorderBoxWithDroppableScroll: targetRect,
33
+ previousImpact,
34
+ destination,
35
+ insideDestination,
36
+ afterCritical,
37
+ }: Args): ?DragImpact => {
38
+ if (!destination.isCombineEnabled) {
39
+ return null;
40
+ }
41
+ const axis: Axis = destination.axis;
42
+ const displacedBy: DisplacedBy = getDisplacedBy(
43
+ destination.axis,
44
+ draggable.displaceBy,
45
+ );
46
+ const displacement: number = displacedBy.value;
47
+
48
+ const targetStart: number = targetRect[axis.start];
49
+ const targetEnd: number = targetRect[axis.end];
50
+
51
+ const withoutDragging: DraggableDimension[] = removeDraggableFromList(
52
+ draggable,
53
+ insideDestination,
54
+ );
55
+
56
+ const combineWith: ?DraggableDimension = find(
57
+ withoutDragging,
58
+ (child: DraggableDimension): boolean => {
59
+ const id: DraggableId = child.descriptor.id;
60
+ const childRect: Rect = child.page.borderBox;
61
+ const childSize: number = childRect[axis.size];
62
+ const threshold: number = childSize / combineThresholdDivisor;
63
+
64
+ const didStartAfterCritical: boolean = getDidStartAfterCritical(
65
+ id,
66
+ afterCritical,
67
+ );
68
+
69
+ const isDisplaced: boolean = getIsDisplaced({
70
+ displaced: previousImpact.displaced,
71
+ id,
72
+ });
73
+
74
+ /*
75
+ Only combining when in the combine region
76
+ As soon as a boundary is hit then no longer combining
77
+ */
78
+
79
+ if (didStartAfterCritical) {
80
+ // In original position
81
+ // Will combine with item when inside a band
82
+ if (isDisplaced) {
83
+ return (
84
+ targetEnd > childRect[axis.start] + threshold &&
85
+ targetEnd < childRect[axis.end] - threshold
86
+ );
87
+ }
88
+
89
+ // child is now 'displaced' backwards from where it started
90
+ // want to combine when we move backwards onto it
91
+ return (
92
+ targetStart > childRect[axis.start] - displacement + threshold &&
93
+ targetStart < childRect[axis.end] - displacement - threshold
94
+ );
95
+ }
96
+
97
+ // item has moved forwards
98
+ if (isDisplaced) {
99
+ return (
100
+ targetEnd > childRect[axis.start] + displacement + threshold &&
101
+ targetEnd < childRect[axis.end] + displacement - threshold
102
+ );
103
+ }
104
+
105
+ // is in resting position - being moved backwards on to
106
+ return (
107
+ targetStart > childRect[axis.start] + threshold &&
108
+ targetStart < childRect[axis.end] - threshold
109
+ );
110
+ },
111
+ );
112
+
113
+ if (!combineWith) {
114
+ return null;
115
+ }
116
+
117
+ const impact: DragImpact = {
118
+ // no change to displacement when combining
119
+ displacedBy,
120
+ displaced: previousImpact.displaced,
121
+ at: {
122
+ type: 'COMBINE',
123
+ combine: {
124
+ draggableId: combineWith.descriptor.id,
125
+ droppableId: destination.descriptor.id,
126
+ },
127
+ },
128
+ };
129
+ return impact;
130
+ };
@@ -0,0 +1,143 @@
1
+ // @flow
2
+ import { type Rect } from 'css-box-model';
3
+ import type {
4
+ DraggableId,
5
+ DraggableDimension,
6
+ DroppableDimension,
7
+ DragImpact,
8
+ Axis,
9
+ DisplacementGroups,
10
+ Viewport,
11
+ DisplacedBy,
12
+ LiftEffect,
13
+ } from '../../types';
14
+ import getDisplacedBy from '../get-displaced-by';
15
+ import removeDraggableFromList from '../remove-draggable-from-list';
16
+ import isHomeOf from '../droppable/is-home-of';
17
+ import { find } from '../../native-with-fallback';
18
+ import getDidStartAfterCritical from '../did-start-after-critical';
19
+ import calculateReorderImpact from '../calculate-drag-impact/calculate-reorder-impact';
20
+ import getIsDisplaced from '../get-is-displaced';
21
+
22
+ type Args = {|
23
+ pageBorderBoxWithDroppableScroll: Rect,
24
+ draggable: DraggableDimension,
25
+ destination: DroppableDimension,
26
+ insideDestination: DraggableDimension[],
27
+ last: DisplacementGroups,
28
+ viewport: Viewport,
29
+ afterCritical: LiftEffect,
30
+ |};
31
+
32
+ type AtIndexArgs = {|
33
+ draggable: DraggableDimension,
34
+ closest: ?DraggableDimension,
35
+ inHomeList: boolean,
36
+ |};
37
+
38
+ function atIndex({ draggable, closest, inHomeList }: AtIndexArgs): ?number {
39
+ if (!closest) {
40
+ return null;
41
+ }
42
+
43
+ if (!inHomeList) {
44
+ return closest.descriptor.index;
45
+ }
46
+
47
+ if (closest.descriptor.index > draggable.descriptor.index) {
48
+ return closest.descriptor.index - 1;
49
+ }
50
+
51
+ return closest.descriptor.index;
52
+ }
53
+
54
+ export default ({
55
+ pageBorderBoxWithDroppableScroll: targetRect,
56
+ draggable,
57
+ destination,
58
+ insideDestination,
59
+ last,
60
+ viewport,
61
+ afterCritical,
62
+ }: Args): DragImpact => {
63
+ const axis: Axis = destination.axis;
64
+ const displacedBy: DisplacedBy = getDisplacedBy(
65
+ destination.axis,
66
+ draggable.displaceBy,
67
+ );
68
+ const displacement: number = displacedBy.value;
69
+
70
+ const targetStart: number = targetRect[axis.start];
71
+ const targetEnd: number = targetRect[axis.end];
72
+
73
+ const withoutDragging: DraggableDimension[] = removeDraggableFromList(
74
+ draggable,
75
+ insideDestination,
76
+ );
77
+
78
+ const closest: ?DraggableDimension = find(
79
+ withoutDragging,
80
+ (child: DraggableDimension): boolean => {
81
+ const id: DraggableId = child.descriptor.id;
82
+ const childCenter: number = child.page.borderBox.center[axis.line];
83
+
84
+ const didStartAfterCritical: boolean = getDidStartAfterCritical(
85
+ id,
86
+ afterCritical,
87
+ );
88
+
89
+ const isDisplaced: boolean = getIsDisplaced({ displaced: last, id });
90
+
91
+ /*
92
+ Note: we change things when moving *past* the child center - not when it hits the center
93
+ If we make it when we *hit* the child center then there can be
94
+ a hit on the next update causing a flicker.
95
+
96
+ - Update 1: targetBottom hits center => displace backwards
97
+ - Update 2: targetStart is now hitting the displaced center => displace forwards
98
+ - Update 3: goto 1 (boom)
99
+ */
100
+
101
+ if (didStartAfterCritical) {
102
+ // Continue to displace while targetEnd before the childCenter
103
+ // Move once we *move forward past* the childCenter
104
+ if (isDisplaced) {
105
+ return targetEnd <= childCenter;
106
+ }
107
+
108
+ // Has been moved backwards from where it started
109
+ // Displace forwards when targetStart *moves backwards past* the displaced childCenter
110
+ return targetStart < childCenter - displacement;
111
+ }
112
+
113
+ // Item has been shifted forward.
114
+ // Remove displacement when targetEnd moves forward past the displaced center
115
+ if (isDisplaced) {
116
+ return targetEnd <= childCenter + displacement;
117
+ }
118
+
119
+ // Item is behind the dragging item
120
+ // We want to displace it if the targetStart goes *backwards past* the childCenter
121
+ return targetStart < childCenter;
122
+ },
123
+ );
124
+
125
+ const newIndex: ?number = atIndex({
126
+ draggable,
127
+ closest,
128
+ inHomeList: isHomeOf(draggable, destination),
129
+ });
130
+
131
+ // TODO: index cannot be null?
132
+ // otherwise return null from there and return empty impact
133
+ // that was calculate reorder impact does not need to account for a null index
134
+ return calculateReorderImpact({
135
+ draggable,
136
+ insideDestination,
137
+ destination,
138
+ viewport,
139
+ last,
140
+ displacedBy,
141
+ index: newIndex,
142
+ });
143
+ };
@@ -0,0 +1,93 @@
1
+ // @flow
2
+ import { type Position, type Rect } from 'css-box-model';
3
+ import type {
4
+ DroppableId,
5
+ DraggableDimension,
6
+ DroppableDimension,
7
+ DraggableDimensionMap,
8
+ DroppableDimensionMap,
9
+ DragImpact,
10
+ Viewport,
11
+ LiftEffect,
12
+ } from '../../types';
13
+ import getDroppableOver from '../get-droppable-over';
14
+ import getDraggablesInsideDroppable from '../get-draggables-inside-droppable';
15
+ import withDroppableScroll from '../with-scroll-change/with-droppable-scroll';
16
+ import getReorderImpact from './get-reorder-impact';
17
+ import getCombineImpact from './get-combine-impact';
18
+ import noImpact from '../no-impact';
19
+ import { offsetRectByPosition } from '../rect';
20
+
21
+ type Args = {|
22
+ pageOffset: Position,
23
+ draggable: DraggableDimension,
24
+ // all dimensions in system
25
+ draggables: DraggableDimensionMap,
26
+ droppables: DroppableDimensionMap,
27
+ previousImpact: DragImpact,
28
+ viewport: Viewport,
29
+ afterCritical: LiftEffect,
30
+ |};
31
+
32
+ export default ({
33
+ pageOffset,
34
+ draggable,
35
+ draggables,
36
+ droppables,
37
+ previousImpact,
38
+ viewport,
39
+ afterCritical,
40
+ }: Args): DragImpact => {
41
+ const pageBorderBox: Rect = offsetRectByPosition(
42
+ draggable.page.borderBox,
43
+ pageOffset,
44
+ );
45
+
46
+ const destinationId: ?DroppableId = getDroppableOver({
47
+ pageBorderBox,
48
+ draggable,
49
+ droppables,
50
+ });
51
+
52
+ // not dragging over anything
53
+
54
+ if (!destinationId) {
55
+ // A big design decision was made here to collapse the home list
56
+ // when not over any list. This yielded the most consistently beautiful experience.
57
+ return noImpact;
58
+ }
59
+
60
+ const destination: DroppableDimension = droppables[destinationId];
61
+ const insideDestination: DraggableDimension[] = getDraggablesInsideDroppable(
62
+ destination.descriptor.id,
63
+ draggables,
64
+ );
65
+
66
+ // Where the element actually is now.
67
+ // Need to take into account the change of scroll in the droppable
68
+ const pageBorderBoxWithDroppableScroll: Rect = withDroppableScroll(
69
+ destination,
70
+ pageBorderBox,
71
+ );
72
+
73
+ // checking combine first so we combine before any reordering
74
+ return (
75
+ getCombineImpact({
76
+ pageBorderBoxWithDroppableScroll,
77
+ draggable,
78
+ previousImpact,
79
+ destination,
80
+ insideDestination,
81
+ afterCritical,
82
+ }) ||
83
+ getReorderImpact({
84
+ pageBorderBoxWithDroppableScroll,
85
+ draggable,
86
+ destination,
87
+ insideDestination,
88
+ last: previousImpact.displaced,
89
+ viewport,
90
+ afterCritical,
91
+ })
92
+ );
93
+ };
@@ -0,0 +1,31 @@
1
+ // @flow
2
+ import memoizeOne from 'memoize-one';
3
+ import { toDraggableList } from './dimension-structures';
4
+ import type {
5
+ DraggableDimension,
6
+ DroppableId,
7
+ DraggableDimensionMap,
8
+ } from '../types';
9
+
10
+ export default memoizeOne(
11
+ (
12
+ // using droppableId to avoid cache busted when we
13
+ // update the droppable (such as when it scrolls)
14
+ droppableId: DroppableId,
15
+ draggables: DraggableDimensionMap,
16
+ ): DraggableDimension[] => {
17
+ const result = toDraggableList(draggables)
18
+ .filter(
19
+ (draggable: DraggableDimension): boolean =>
20
+ droppableId === draggable.descriptor.droppableId,
21
+ )
22
+ // Dimensions are not guarenteed to be ordered in the same order as keys
23
+ // So we need to sort them so they are in the correct order
24
+ .sort(
25
+ (a: DraggableDimension, b: DraggableDimension): number =>
26
+ a.descriptor.index - b.descriptor.index,
27
+ );
28
+
29
+ return result;
30
+ },
31
+ );
@@ -0,0 +1,158 @@
1
+ // @flow
2
+ import { type Position, type Rect } from 'css-box-model';
3
+ import type {
4
+ DroppableDimension,
5
+ DroppableDimensionMap,
6
+ DroppableId,
7
+ DraggableDimension,
8
+ Axis,
9
+ } from '../types';
10
+ import { toDroppableList } from './dimension-structures';
11
+ import isPositionInFrame from './visibility/is-position-in-frame';
12
+ import { distance, patch } from './position';
13
+ import isWithin from './is-within';
14
+
15
+ // https://stackoverflow.com/questions/306316/determine-if-two-rectangles-overlap-each-other
16
+ // https://silentmatt.com/rectangle-intersection/
17
+ function getHasOverlap(first: Rect, second: Rect): boolean {
18
+ return (
19
+ first.left < second.right &&
20
+ first.right > second.left &&
21
+ first.top < second.bottom &&
22
+ first.bottom > second.top
23
+ );
24
+ }
25
+
26
+ type Args = {|
27
+ pageBorderBox: Rect,
28
+ draggable: DraggableDimension,
29
+ droppables: DroppableDimensionMap,
30
+ |};
31
+
32
+ type WithDistance = {|
33
+ distance: number,
34
+ id: DroppableId,
35
+ |};
36
+
37
+ type GetFurthestArgs = {|
38
+ pageBorderBox: Rect,
39
+ draggable: DraggableDimension,
40
+ candidates: DroppableDimension[],
41
+ |};
42
+
43
+ function getFurthestAway({
44
+ pageBorderBox,
45
+ draggable,
46
+ candidates,
47
+ }: GetFurthestArgs): ?DroppableId {
48
+ // We are not comparing the center of the home list with the target list as it would
49
+ // give preference to giant lists
50
+
51
+ // We are measuring the distance from where the draggable started
52
+ // to where it is *hitting* the candidate
53
+ // Note: The hit point might technically not be in the bounds of the candidate
54
+
55
+ const startCenter: Position = draggable.page.borderBox.center;
56
+ const sorted: WithDistance[] = candidates
57
+ .map((candidate: DroppableDimension): WithDistance => {
58
+ const axis: Axis = candidate.axis;
59
+ const target: Position = patch(
60
+ candidate.axis.line,
61
+ // use the current center of the dragging item on the main axis
62
+ pageBorderBox.center[axis.line],
63
+ // use the center of the list on the cross axis
64
+ candidate.page.borderBox.center[axis.crossAxisLine],
65
+ );
66
+
67
+ return {
68
+ id: candidate.descriptor.id,
69
+ distance: distance(startCenter, target),
70
+ };
71
+ })
72
+ // largest value will be first
73
+ .sort((a: WithDistance, b: WithDistance) => b.distance - a.distance);
74
+
75
+ // just being safe
76
+ return sorted[0] ? sorted[0].id : null;
77
+ }
78
+
79
+ export default function getDroppableOver({
80
+ pageBorderBox,
81
+ draggable,
82
+ droppables,
83
+ }: Args): ?DroppableId {
84
+ // We know at this point that some overlap has to exist
85
+ const candidates: DroppableDimension[] = toDroppableList(droppables).filter(
86
+ (item: DroppableDimension): boolean => {
87
+ // Cannot be a candidate when disabled
88
+ if (!item.isEnabled) {
89
+ return false;
90
+ }
91
+
92
+ // Cannot be a candidate when there is no visible area
93
+ const active: ?Rect = item.subject.active;
94
+ if (!active) {
95
+ return false;
96
+ }
97
+
98
+ // Cannot be a candidate when dragging item is not over the droppable at all
99
+ if (!getHasOverlap(pageBorderBox, active)) {
100
+ return false;
101
+ }
102
+
103
+ // 1. Candidate if the center position is over a droppable
104
+ if (isPositionInFrame(active)(pageBorderBox.center)) {
105
+ return true;
106
+ }
107
+
108
+ // 2. Candidate if an edge is over the cross axis half way point
109
+ // 3. Candidate if dragging item is totally over droppable on cross axis
110
+
111
+ const axis: Axis = item.axis;
112
+ const childCenter: number = active.center[axis.crossAxisLine];
113
+ const crossAxisStart: number = pageBorderBox[axis.crossAxisStart];
114
+ const crossAxisEnd: number = pageBorderBox[axis.crossAxisEnd];
115
+
116
+ const isContained = isWithin(
117
+ active[axis.crossAxisStart],
118
+ active[axis.crossAxisEnd],
119
+ );
120
+
121
+ const isStartContained: boolean = isContained(crossAxisStart);
122
+ const isEndContained: boolean = isContained(crossAxisEnd);
123
+
124
+ // Dragging item is totally covering the active area
125
+ if (!isStartContained && !isEndContained) {
126
+ return true;
127
+ }
128
+
129
+ /**
130
+ * edges must go beyond the center line in order to avoid
131
+ * cases were both conditions are satisfied.
132
+ */
133
+ if (isStartContained) {
134
+ return crossAxisStart < childCenter;
135
+ }
136
+
137
+ return crossAxisEnd > childCenter;
138
+ },
139
+ );
140
+
141
+ if (!candidates.length) {
142
+ return null;
143
+ }
144
+
145
+ // Only one candidate - use that!
146
+ if (candidates.length === 1) {
147
+ return candidates[0].descriptor.id;
148
+ }
149
+
150
+ // Multiple options returned
151
+ // Should only occur with really large items
152
+ // Going to use fallback: distance from home
153
+ return getFurthestAway({
154
+ pageBorderBox,
155
+ draggable,
156
+ candidates,
157
+ });
158
+ }
@@ -0,0 +1,10 @@
1
+ // @flow
2
+ import { invariant } from '../invariant';
3
+
4
+ import type { DroppableDimension, Scrollable } from '../types';
5
+
6
+ export default (droppable: DroppableDimension): Scrollable => {
7
+ const frame: ?Scrollable = droppable.frame;
8
+ invariant(frame, 'Expected Droppable to have a frame');
9
+ return frame;
10
+ };
@@ -0,0 +1,7 @@
1
+ // @flow
2
+ import type { DraggableDescriptor, DraggableLocation } from '../types';
3
+
4
+ export default (descriptor: DraggableDescriptor): DraggableLocation => ({
5
+ index: descriptor.index,
6
+ droppableId: descriptor.droppableId,
7
+ });