@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,198 @@
1
+ // @flow
2
+ import React, { useState, useRef, useEffect, type Node } from 'react';
3
+ import { useCallback } from 'use-memo-one';
4
+ import type { Spacing } from 'css-box-model';
5
+ import type {
6
+ Placeholder as PlaceholderType,
7
+ InOutAnimationMode,
8
+ ContextId,
9
+ } from '../../types';
10
+ import { transitions } from '../../animation';
11
+ import { noSpacing } from '../../state/spacing';
12
+
13
+ function noop() {}
14
+
15
+ export type PlaceholderStyle = {|
16
+ display: string,
17
+ boxSizing: 'border-box',
18
+ width: number,
19
+ height: number,
20
+ marginTop: number,
21
+ marginRight: number,
22
+ marginBottom: number,
23
+ marginLeft: number,
24
+ flexShrink: '0',
25
+ flexGrow: '0',
26
+ pointerEvents: 'none',
27
+ transition: ?string,
28
+ |};
29
+ export type Props = {|
30
+ placeholder: PlaceholderType,
31
+ animate: InOutAnimationMode,
32
+ onClose: () => void,
33
+ innerRef?: () => ?HTMLElement,
34
+ onTransitionEnd: () => void,
35
+ contextId: ContextId,
36
+ |};
37
+
38
+ type Size = {|
39
+ width: number,
40
+ height: number,
41
+ // Need to animate in/out animation as well as size
42
+ margin: Spacing,
43
+ |};
44
+
45
+ type HelperArgs = {|
46
+ isAnimatingOpenOnMount: boolean,
47
+ placeholder: PlaceholderType,
48
+ animate: InOutAnimationMode,
49
+ |};
50
+
51
+ const empty: Size = {
52
+ width: 0,
53
+ height: 0,
54
+ margin: noSpacing,
55
+ };
56
+
57
+ const getSize = ({
58
+ isAnimatingOpenOnMount,
59
+ placeholder,
60
+ animate,
61
+ }: HelperArgs): Size => {
62
+ if (isAnimatingOpenOnMount) {
63
+ return empty;
64
+ }
65
+
66
+ if (animate === 'close') {
67
+ return empty;
68
+ }
69
+
70
+ return {
71
+ height: placeholder.client.borderBox.height,
72
+ width: placeholder.client.borderBox.width,
73
+ margin: placeholder.client.margin,
74
+ };
75
+ };
76
+
77
+ const getStyle = ({
78
+ isAnimatingOpenOnMount,
79
+ placeholder,
80
+ animate,
81
+ }: HelperArgs): PlaceholderStyle => {
82
+ const size: Size = getSize({ isAnimatingOpenOnMount, placeholder, animate });
83
+
84
+ return {
85
+ display: placeholder.display,
86
+ // ## Recreating the box model
87
+ // We created the borderBox and then apply the margins directly
88
+ // this is to maintain any margin collapsing behaviour
89
+
90
+ // creating borderBox
91
+ // background: 'green',
92
+ boxSizing: 'border-box',
93
+ width: size.width,
94
+ height: size.height,
95
+ // creating marginBox
96
+ marginTop: size.margin.top,
97
+ marginRight: size.margin.right,
98
+ marginBottom: size.margin.bottom,
99
+ marginLeft: size.margin.left,
100
+
101
+ // ## Avoiding collapsing
102
+ // Avoiding the collapsing or growing of this element when pushed by flex child siblings.
103
+ // We have already taken a snapshot the current dimensions we do not want this element
104
+ // to recalculate its dimensions
105
+ // It is okay for these properties to be applied on elements that are not flex children
106
+ flexShrink: '0',
107
+ flexGrow: '0',
108
+ // Just a little performance optimisation: avoiding the browser needing
109
+ // to worry about pointer events for this element
110
+ pointerEvents: 'none',
111
+
112
+ // Animate the placeholder size and margin
113
+ transition: animate !== 'none' ? transitions.placeholder : null,
114
+ };
115
+ };
116
+
117
+ function Placeholder(props: Props): Node {
118
+ const animateOpenTimerRef = useRef<?TimeoutID>(null);
119
+
120
+ const tryClearAnimateOpenTimer = useCallback(() => {
121
+ if (!animateOpenTimerRef.current) {
122
+ return;
123
+ }
124
+ clearTimeout(animateOpenTimerRef.current);
125
+ animateOpenTimerRef.current = null;
126
+ }, []);
127
+
128
+ const { animate, onTransitionEnd, onClose, contextId } = props;
129
+ const [isAnimatingOpenOnMount, setIsAnimatingOpenOnMount] = useState<boolean>(
130
+ props.animate === 'open',
131
+ );
132
+
133
+ // Will run after a render is flushed
134
+ // Still need to wait a timeout to ensure that the
135
+ // update is completely applied to the DOM
136
+ useEffect(() => {
137
+ // No need to do anything
138
+ if (!isAnimatingOpenOnMount) {
139
+ return noop;
140
+ }
141
+
142
+ // might need to clear the timer
143
+ if (animate !== 'open') {
144
+ tryClearAnimateOpenTimer();
145
+ setIsAnimatingOpenOnMount(false);
146
+ return noop;
147
+ }
148
+
149
+ // timer already pending
150
+ if (animateOpenTimerRef.current) {
151
+ return noop;
152
+ }
153
+
154
+ animateOpenTimerRef.current = setTimeout(() => {
155
+ animateOpenTimerRef.current = null;
156
+ setIsAnimatingOpenOnMount(false);
157
+ });
158
+
159
+ // clear the timer if needed
160
+ return tryClearAnimateOpenTimer;
161
+ }, [animate, isAnimatingOpenOnMount, tryClearAnimateOpenTimer]);
162
+
163
+ const onSizeChangeEnd = useCallback(
164
+ (event: TransitionEvent) => {
165
+ // We transition height, width and margin
166
+ // each of those transitions will independently call this callback
167
+ // Because they all have the same duration we can just respond to one of them
168
+ // 'height' was chosen for no particular reason :D
169
+ if (event.propertyName !== 'height') {
170
+ return;
171
+ }
172
+
173
+ onTransitionEnd();
174
+
175
+ if (animate === 'close') {
176
+ onClose();
177
+ }
178
+ },
179
+ [animate, onClose, onTransitionEnd],
180
+ );
181
+
182
+ const style: PlaceholderStyle = getStyle({
183
+ isAnimatingOpenOnMount,
184
+ animate: props.animate,
185
+ placeholder: props.placeholder,
186
+ });
187
+
188
+ return React.createElement(props.placeholder.tagName, {
189
+ style,
190
+ 'data-rbd-placeholder-context-id': contextId,
191
+ onTransitionEnd: onSizeChangeEnd,
192
+ ref: props.innerRef,
193
+ });
194
+ }
195
+
196
+ export default React.memo<Props>(Placeholder);
197
+ // enzyme does not work well with memo, so exporting the non-memo version
198
+ export const WithoutMemo = Placeholder;
@@ -0,0 +1,72 @@
1
+ // @flow
2
+ import type { Position } from 'css-box-model';
3
+ import rafSchd from 'raf-schd';
4
+ import { invariant } from '../invariant';
5
+ import bindEvents from './event-bindings/bind-events';
6
+ import type { EventBinding } from './event-bindings/event-types';
7
+ import getWindowScroll from './window/get-window-scroll';
8
+ import { noop } from '../empty';
9
+
10
+ type OnWindowScroll = (newScroll: Position) => void;
11
+
12
+ type Args = {|
13
+ onWindowScroll: OnWindowScroll,
14
+ |};
15
+
16
+ type Result = {|
17
+ start: () => void,
18
+ stop: () => void,
19
+ isActive: () => boolean,
20
+ |};
21
+
22
+ function getWindowScrollBinding(update: () => void): EventBinding {
23
+ return {
24
+ eventName: 'scroll',
25
+ // ## Passive: true
26
+ // Eventual consistency is fine because we use position: fixed on the item
27
+ // ## Capture: false
28
+ // Scroll events on elements do not bubble, but they go through the capture phase
29
+ // https://twitter.com/alexandereardon/status/985994224867819520
30
+ // Using capture: false here as we want to avoid intercepting droppable scroll requests
31
+ options: { passive: true, capture: false },
32
+ fn: (event: UIEvent) => {
33
+ // IE11 fix
34
+ // All scrollable events still bubble up and are caught by this handler in ie11.
35
+ // On a window scroll the event.target should be the window or the document.
36
+ // If this is not the case then it is not a 'window' scroll event and can be ignored
37
+ if (event.target !== window && event.target !== window.document) {
38
+ return;
39
+ }
40
+
41
+ update();
42
+ },
43
+ };
44
+ }
45
+
46
+ export default function getScrollListener({ onWindowScroll }: Args): Result {
47
+ function updateScroll() {
48
+ // letting the update function read the latest scroll when called
49
+ onWindowScroll(getWindowScroll());
50
+ }
51
+
52
+ const scheduled = rafSchd(updateScroll);
53
+ const binding: EventBinding = getWindowScrollBinding(scheduled);
54
+ let unbind: () => void = noop;
55
+
56
+ function isActive(): boolean {
57
+ return unbind !== noop;
58
+ }
59
+
60
+ function start() {
61
+ invariant(!isActive(), 'Cannot start scroll listener when already active');
62
+ unbind = bindEvents(window, [binding]);
63
+ }
64
+ function stop() {
65
+ invariant(isActive(), 'Cannot stop scroll listener when not active');
66
+ scheduled.cancel();
67
+ unbind();
68
+ unbind = noop;
69
+ }
70
+
71
+ return { start, stop, isActive };
72
+ }
@@ -0,0 +1,15 @@
1
+ // @flow
2
+ import { invariant } from '../invariant';
3
+ import isHtmlElement from './is-type-of-element/is-html-element';
4
+
5
+ export default (ref: ?mixed) => {
6
+ invariant(
7
+ ref && isHtmlElement(ref),
8
+ `
9
+ provided.innerRef has not been provided with a HTMLElement.
10
+
11
+ You can find a guide on using the innerRef callback functions at:
12
+ https://github.com/atlassian/react-beautiful-dnd/blob/master/docs/guides/using-inner-ref.md
13
+ `,
14
+ );
15
+ };
@@ -0,0 +1,2 @@
1
+ // @flow
2
+ export { default } from './use-announcer';
@@ -0,0 +1,80 @@
1
+ // @flow
2
+ import { useRef, useEffect } from 'react';
3
+ import { useMemo, useCallback } from 'use-memo-one';
4
+ import type { Announce, ContextId } from '../../types';
5
+ import { warning } from '../../dev-warning';
6
+ import getBodyElement from '../get-body-element';
7
+ import visuallyHidden from '../visually-hidden-style';
8
+
9
+ export const getId = (contextId: ContextId): string =>
10
+ `rbd-announcement-${contextId}`;
11
+
12
+ export default function useAnnouncer(contextId: ContextId): Announce {
13
+ const id: string = useMemo(() => getId(contextId), [contextId]);
14
+ const ref = useRef<?HTMLElement>(null);
15
+
16
+ useEffect(
17
+ function setup() {
18
+ const el: HTMLElement = document.createElement('div');
19
+ // storing reference for usage in announce
20
+ ref.current = el;
21
+
22
+ // identifier
23
+ el.id = id;
24
+
25
+ // Aria live region
26
+
27
+ // will force itself to be read
28
+ el.setAttribute('aria-live', 'assertive');
29
+ // must read the whole thing every time
30
+ el.setAttribute('aria-atomic', 'true');
31
+
32
+ // hide the element visually
33
+ Object.assign(el.style, visuallyHidden);
34
+
35
+ // Add to body
36
+ getBodyElement().appendChild(el);
37
+
38
+ return function cleanup() {
39
+ // Not clearing the ref as it might be used by announce before the timeout expires
40
+
41
+ // unmounting after a timeout to let any announcements
42
+ // during a mount be published
43
+ setTimeout(function remove() {
44
+ // checking if element exists as the body might have been changed by things like 'turbolinks'
45
+ const body: HTMLBodyElement = getBodyElement();
46
+ if (body.contains(el)) {
47
+ body.removeChild(el);
48
+ }
49
+ // if el was the current ref - clear it so that
50
+ // we can get a warning if announce is called
51
+ if (el === ref.current) {
52
+ ref.current = null;
53
+ }
54
+ });
55
+ };
56
+ },
57
+ [id],
58
+ );
59
+
60
+ const announce: Announce = useCallback((message: string): void => {
61
+ const el: ?HTMLElement = ref.current;
62
+ if (el) {
63
+ el.textContent = message;
64
+ return;
65
+ }
66
+
67
+ warning(`
68
+ A screen reader message was trying to be announced but it was unable to do so.
69
+ This can occur if you unmount your <DragDropContext /> in your onDragEnd.
70
+ Consider calling provided.announce() before the unmount so that the instruction will
71
+ not be lost for users relying on a screen reader.
72
+
73
+ Message not passed to screen reader:
74
+
75
+ "${message}"
76
+ `);
77
+ }, []);
78
+
79
+ return announce;
80
+ }
@@ -0,0 +1,22 @@
1
+ // @flow
2
+ import { useEffect } from 'react';
3
+ import { error } from '../dev-warning';
4
+ import useDev from './use-dev';
5
+
6
+ export default function useDevSetupWarning(fn: () => void, inputs?: mixed[]) {
7
+ useDev(() => {
8
+ // eslint-disable-next-line react-hooks/rules-of-hooks
9
+ useEffect(() => {
10
+ try {
11
+ fn();
12
+ } catch (e) {
13
+ error(`
14
+ A setup problem was encountered.
15
+
16
+ > ${e.message}
17
+ `);
18
+ }
19
+ // eslint-disable-next-line react-hooks/exhaustive-deps
20
+ }, inputs);
21
+ });
22
+ }
@@ -0,0 +1,9 @@
1
+ // @flow
2
+
3
+ export default function useDev(useHook: () => void) {
4
+ // Don't run any validation in production
5
+ if (process.env.NODE_ENV !== 'production') {
6
+ // eslint-disable-next-line react-hooks/rules-of-hooks
7
+ useHook();
8
+ }
9
+ }
@@ -0,0 +1,44 @@
1
+ // @flow
2
+ import {
3
+ type BoxModel,
4
+ type Position,
5
+ calculateBox,
6
+ withScroll,
7
+ } from 'css-box-model';
8
+ import type {
9
+ DraggableDescriptor,
10
+ DraggableDimension,
11
+ Placeholder,
12
+ } from '../../types';
13
+ import { origin } from '../../state/position';
14
+
15
+ export default function getDimension(
16
+ descriptor: DraggableDescriptor,
17
+ el: HTMLElement,
18
+ windowScroll?: Position = origin,
19
+ ): DraggableDimension {
20
+ const computedStyles: CSSStyleDeclaration = window.getComputedStyle(el);
21
+ const borderBox: ClientRect = el.getBoundingClientRect();
22
+ const client: BoxModel = calculateBox(borderBox, computedStyles);
23
+ const page: BoxModel = withScroll(client, windowScroll);
24
+
25
+ const placeholder: Placeholder = {
26
+ client,
27
+ tagName: el.tagName.toLowerCase(),
28
+ display: computedStyles.display,
29
+ };
30
+ const displaceBy: Position = {
31
+ x: client.marginBox.width,
32
+ y: client.marginBox.height,
33
+ };
34
+
35
+ const dimension: DraggableDimension = {
36
+ descriptor,
37
+ placeholder,
38
+ displaceBy,
39
+ client,
40
+ page,
41
+ };
42
+
43
+ return dimension;
44
+ }
@@ -0,0 +1,2 @@
1
+ // @flow
2
+ export { default } from './use-draggable-publisher';
@@ -0,0 +1,87 @@
1
+ // @flow
2
+ import { type Position } from 'css-box-model';
3
+ import { useMemo, useCallback } from 'use-memo-one';
4
+ import { useRef } from 'react';
5
+ import { invariant } from '../../invariant';
6
+ import type {
7
+ DraggableDescriptor,
8
+ DraggableDimension,
9
+ Id,
10
+ DraggableOptions,
11
+ } from '../../types';
12
+ import type {
13
+ Registry,
14
+ DraggableEntry,
15
+ } from '../../state/registry/registry-types';
16
+ import makeDimension from './get-dimension';
17
+ import useLayoutEffect from '../use-isomorphic-layout-effect';
18
+ import useUniqueId from '../use-unique-id';
19
+
20
+ export type Args = {|
21
+ descriptor: DraggableDescriptor,
22
+ getDraggableRef: () => ?HTMLElement,
23
+ registry: Registry,
24
+ ...DraggableOptions,
25
+ |};
26
+
27
+ export default function useDraggablePublisher(args: Args) {
28
+ const uniqueId: Id = useUniqueId('draggable');
29
+
30
+ const {
31
+ descriptor,
32
+ registry,
33
+ getDraggableRef,
34
+ canDragInteractiveElements,
35
+ shouldRespectForcePress,
36
+ isEnabled,
37
+ } = args;
38
+
39
+ const options: DraggableOptions = useMemo(
40
+ () => ({
41
+ canDragInteractiveElements,
42
+ shouldRespectForcePress,
43
+ isEnabled,
44
+ }),
45
+ [canDragInteractiveElements, isEnabled, shouldRespectForcePress],
46
+ );
47
+
48
+ const getDimension = useCallback(
49
+ (windowScroll?: Position): DraggableDimension => {
50
+ const el: ?HTMLElement = getDraggableRef();
51
+ invariant(el, 'Cannot get dimension when no ref is set');
52
+ return makeDimension(descriptor, el, windowScroll);
53
+ },
54
+ [descriptor, getDraggableRef],
55
+ );
56
+
57
+ const entry: DraggableEntry = useMemo(
58
+ () => ({
59
+ uniqueId,
60
+ descriptor,
61
+ options,
62
+ getDimension,
63
+ }),
64
+ [descriptor, getDimension, options, uniqueId],
65
+ );
66
+
67
+ const publishedRef = useRef<DraggableEntry>(entry);
68
+ const isFirstPublishRef = useRef<boolean>(true);
69
+
70
+ // mounting and unmounting
71
+ useLayoutEffect(() => {
72
+ registry.draggable.register(publishedRef.current);
73
+ return () => registry.draggable.unregister(publishedRef.current);
74
+ }, [registry.draggable]);
75
+
76
+ // updates while mounted
77
+ useLayoutEffect(() => {
78
+ if (isFirstPublishRef.current) {
79
+ isFirstPublishRef.current = false;
80
+ return;
81
+ }
82
+
83
+ const last: DraggableEntry = publishedRef.current;
84
+ publishedRef.current = entry;
85
+ registry.draggable.update(entry, last);
86
+ }, [entry, registry.draggable]);
87
+ }
@@ -0,0 +1,27 @@
1
+ // @flow
2
+ import getClosestScrollable from './get-closest-scrollable';
3
+ import { warning } from '../../dev-warning';
4
+
5
+ // We currently do not support nested scroll containers
6
+ // But will hopefully support this soon!
7
+ export default (scrollable: ?Element) => {
8
+ if (!scrollable) {
9
+ return;
10
+ }
11
+
12
+ const anotherScrollParent: ?Element = getClosestScrollable(
13
+ scrollable.parentElement,
14
+ );
15
+
16
+ if (!anotherScrollParent) {
17
+ return;
18
+ }
19
+
20
+ warning(`
21
+ Droppable: unsupported nested scroll container detected.
22
+ A Droppable can only have one scroll parent (which can be itself)
23
+ Nested scroll containers are currently not supported.
24
+
25
+ We hope to support nested scroll containers soon: https://github.com/atlassian/react-beautiful-dnd/issues/131
26
+ `);
27
+ };
@@ -0,0 +1,95 @@
1
+ // @flow
2
+ import { invariant } from '../../invariant';
3
+ import { warning } from '../../dev-warning';
4
+ import getBodyElement from '../get-body-element';
5
+
6
+ type Overflow = {|
7
+ overflowX: string,
8
+ overflowY: string,
9
+ |};
10
+
11
+ const isEqual = (base: string) => (value: string): boolean => base === value;
12
+ const isScroll = isEqual('scroll');
13
+ const isAuto = isEqual('auto');
14
+ const isVisible = isEqual('visible');
15
+ const isEither = (overflow: Overflow, fn: (value: string) => boolean) =>
16
+ fn(overflow.overflowX) || fn(overflow.overflowY);
17
+ const isBoth = (overflow: Overflow, fn: (value: string) => boolean) =>
18
+ fn(overflow.overflowX) && fn(overflow.overflowY);
19
+
20
+ const isElementScrollable = (el: Element): boolean => {
21
+ const style: CSSStyleDeclaration = window.getComputedStyle(el);
22
+ const overflow: Overflow = {
23
+ overflowX: style.overflowX,
24
+ overflowY: style.overflowY,
25
+ };
26
+
27
+ return isEither(overflow, isScroll) || isEither(overflow, isAuto);
28
+ };
29
+
30
+ // Special case for a body element
31
+ // Playground: https://codepen.io/alexreardon/pen/ZmyLgX?editors=1111
32
+ const isBodyScrollable = (): boolean => {
33
+ // Because we always return false for now, we can skip any actual processing in production
34
+ if (process.env.NODE_ENV === 'production') {
35
+ return false;
36
+ }
37
+
38
+ const body: HTMLBodyElement = getBodyElement();
39
+ const html: ?HTMLElement = document.documentElement;
40
+ invariant(html);
41
+
42
+ // 1. The `body` has `overflow-[x|y]: auto | scroll`
43
+ if (!isElementScrollable(body)) {
44
+ return false;
45
+ }
46
+
47
+ const htmlStyle: CSSStyleDeclaration = window.getComputedStyle(html);
48
+ const htmlOverflow: Overflow = {
49
+ overflowX: htmlStyle.overflowX,
50
+ overflowY: htmlStyle.overflowY,
51
+ };
52
+
53
+ if (isBoth(htmlOverflow, isVisible)) {
54
+ return false;
55
+ }
56
+
57
+ warning(`
58
+ We have detected that your <body> element might be a scroll container.
59
+ We have found no reliable way of detecting whether the <body> element is a scroll container.
60
+ Under most circumstances a <body> scroll bar will be on the <html> element (document.documentElement)
61
+
62
+ Because we cannot determine if the <body> is a scroll container, and generally it is not one,
63
+ we will be treating the <body> as *not* a scroll container
64
+
65
+ More information: https://github.com/atlassian/react-beautiful-dnd/blob/master/docs/guides/how-we-detect-scroll-containers.md
66
+ `);
67
+ return false;
68
+ };
69
+
70
+ const getClosestScrollable = (el: ?Element): ?Element => {
71
+ // cannot do anything else!
72
+ if (el == null) {
73
+ return null;
74
+ }
75
+
76
+ // not allowing us to go higher then body
77
+ if (el === document.body) {
78
+ return isBodyScrollable() ? el : null;
79
+ }
80
+
81
+ // Should never get here, but just being safe
82
+ if (el === document.documentElement) {
83
+ return null;
84
+ }
85
+
86
+ if (!isElementScrollable(el)) {
87
+ // keep recursing
88
+ return getClosestScrollable(el.parentElement);
89
+ }
90
+
91
+ // success!
92
+ return el;
93
+ };
94
+
95
+ export default getClosestScrollable;