@startlibs/components 1.0.3 → 1.1.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 (114) hide show
  1. package/lib/BasicBulletList.mjs +9 -0
  2. package/lib/BasicBulletList.mjs.map +1 -0
  3. package/lib/Button.mjs +95 -0
  4. package/lib/Button.mjs.map +1 -0
  5. package/lib/ContextMenu.mjs +452 -0
  6. package/lib/ContextMenu.mjs.map +1 -0
  7. package/lib/Dialog.mjs +232 -0
  8. package/lib/Dialog.mjs.map +1 -0
  9. package/lib/Draggable.mjs +880 -0
  10. package/lib/Draggable.mjs.map +1 -0
  11. package/lib/ErrorBoundary.mjs +108 -0
  12. package/lib/ErrorBoundary.mjs.map +1 -0
  13. package/lib/FillLastLineResizer.mjs +238 -0
  14. package/lib/FillLastLineResizer.mjs.map +1 -0
  15. package/lib/Flip.mjs +604 -0
  16. package/lib/Flip.mjs.map +1 -0
  17. package/lib/FlipList.mjs +186 -0
  18. package/lib/FlipList.mjs.map +1 -0
  19. package/lib/FormatDate.mjs +25 -0
  20. package/lib/FormatDate.mjs.map +1 -0
  21. package/lib/Icon.mjs +50 -0
  22. package/lib/Icon.mjs.map +1 -0
  23. package/lib/Image.mjs +41 -0
  24. package/lib/Image.mjs.map +1 -0
  25. package/lib/Link.mjs +116 -0
  26. package/lib/Link.mjs.map +1 -0
  27. package/lib/Loading.mjs +10 -0
  28. package/lib/Loading.mjs.map +1 -0
  29. package/lib/LoadingDiv.mjs +83 -0
  30. package/lib/LoadingDiv.mjs.map +1 -0
  31. package/lib/Notifications.mjs +63 -0
  32. package/lib/Notifications.mjs.map +1 -0
  33. package/lib/PageLoader.mjs +42 -0
  34. package/lib/PageLoader.mjs.map +1 -0
  35. package/lib/PersistentNotifications.mjs +130 -0
  36. package/lib/PersistentNotifications.mjs.map +1 -0
  37. package/lib/Popup.mjs +49 -0
  38. package/lib/Popup.mjs.map +1 -0
  39. package/lib/SplitColumnsContainer.mjs +43 -0
  40. package/lib/SplitColumnsContainer.mjs.map +1 -0
  41. package/lib/SwitchDiv.mjs +61 -0
  42. package/lib/SwitchDiv.mjs.map +1 -0
  43. package/lib/TextButton.mjs +41 -0
  44. package/lib/TextButton.mjs.map +1 -0
  45. package/lib/ToastNotifications.mjs +215 -0
  46. package/lib/ToastNotifications.mjs.map +1 -0
  47. package/lib/Tooltip.mjs +160 -0
  48. package/lib/Tooltip.mjs.map +1 -0
  49. package/lib/TransitionDiv.mjs +56 -0
  50. package/lib/TransitionDiv.mjs.map +1 -0
  51. package/lib/WarningDialog.mjs +84 -0
  52. package/lib/WarningDialog.mjs.map +1 -0
  53. package/lib/alt/buttons/OnClickLoadingButton.mjs +36 -0
  54. package/lib/alt/buttons/OnClickLoadingButton.mjs.map +1 -0
  55. package/lib/alt/dialogs/DialogWithImageHeader.mjs +14 -0
  56. package/lib/alt/dialogs/DialogWithImageHeader.mjs.map +1 -0
  57. package/lib/alt/dialogs/DialogWithSidebar.mjs +106 -0
  58. package/lib/alt/dialogs/DialogWithSidebar.mjs.map +1 -0
  59. package/lib/alt/dialogs/MobileBubbleDialog.mjs +49 -0
  60. package/lib/alt/dialogs/MobileBubbleDialog.mjs.map +1 -0
  61. package/lib/containers/DialogContainer.mjs +413 -0
  62. package/lib/containers/DialogContainer.mjs.map +1 -0
  63. package/lib/containers/Reposition.mjs +35 -0
  64. package/lib/containers/Reposition.mjs.map +1 -0
  65. package/lib/containers/usePositioning.mjs +294 -0
  66. package/lib/containers/usePositioning.mjs.map +1 -0
  67. package/lib/index.cjs +5000 -0
  68. package/lib/index.cjs.map +1 -0
  69. package/lib/index.mjs +37 -0
  70. package/lib/index.mjs.map +1 -0
  71. package/lib/useDialogWithToggle.mjs +18 -0
  72. package/lib/useDialogWithToggle.mjs.map +1 -0
  73. package/lib/willUseIsSticky.mjs +123 -0
  74. package/lib/willUseIsSticky.mjs.map +1 -0
  75. package/package.json +44 -29
  76. package/src/BasicBulletList.jsx +21 -0
  77. package/src/Button.jsx +288 -0
  78. package/src/ContextMenu.jsx +621 -0
  79. package/src/Dialog.jsx +413 -0
  80. package/src/Draggable.jsx +801 -0
  81. package/src/ErrorBoundary.jsx +75 -0
  82. package/src/FillLastLineResizer.jsx +226 -0
  83. package/src/Flip.jsx +560 -0
  84. package/src/FlipList.jsx +136 -0
  85. package/src/FormatDate.jsx +9 -0
  86. package/src/Icon.jsx +49 -0
  87. package/src/Image.jsx +23 -0
  88. package/src/Link.jsx +68 -0
  89. package/src/Loading.jsx +29 -0
  90. package/src/LoadingDiv.jsx +75 -0
  91. package/src/Notifications.jsx +89 -0
  92. package/src/PageLoader.jsx +48 -0
  93. package/src/PersistentNotifications.jsx +182 -0
  94. package/src/Popup.jsx +49 -0
  95. package/src/SplitColumnsContainer.jsx +63 -0
  96. package/src/SwitchDiv.jsx +30 -0
  97. package/src/TextButton.jsx +34 -0
  98. package/src/ToastNotifications.jsx +298 -0
  99. package/src/Tooltip.jsx +158 -0
  100. package/src/TransitionDiv.jsx +34 -0
  101. package/src/WarningDialog.jsx +70 -0
  102. package/src/alt/buttons/OnClickLoadingButton.jsx +16 -0
  103. package/src/alt/dialogs/DialogWithImageHeader.jsx +42 -0
  104. package/src/alt/dialogs/DialogWithSidebar.jsx +135 -0
  105. package/src/alt/dialogs/MobileBubbleDialog.jsx +56 -0
  106. package/src/containers/DialogContainer.jsx +423 -0
  107. package/src/containers/Reposition.jsx +24 -0
  108. package/src/containers/usePositioning.jsx +305 -0
  109. package/src/index.js +35 -0
  110. package/src/useDialogWithToggle.jsx +12 -0
  111. package/src/willUseIsSticky.jsx +113 -0
  112. package/.babelrc +0 -27
  113. package/lib/index.js +0 -5196
  114. package/rollup.config.js +0 -52
@@ -0,0 +1,801 @@
1
+ import React from 'react'
2
+ import ReactDOM from 'react-dom'
3
+ import _ from 'lodash/fp'
4
+ import styled from 'styled-components'
5
+ import {getScrollParent, normalizeMouse, normalizeTouch} from '@startlibs/utils'
6
+ import {FlipList} from './FlipList'
7
+
8
+ const {Provider, Consumer} = React.createContext({})
9
+
10
+ const areComponentsEqual = (a,b) => a === b
11
+
12
+ const DropPosition = styled.div`
13
+ border: 2px dashed rgba(0,0,0,0.1);
14
+ background-color: rgba(0,0,0,0.025);
15
+ border-radius: 18px;
16
+ position: absolute;
17
+ top: 0;
18
+ bottom: 0;
19
+ right: 0;
20
+ left: 0;
21
+ visibility: visible;
22
+ display: block;
23
+ border-radius: inherit;
24
+ ${props => props.rounded && `
25
+ border-radius: 1000rem;
26
+ `}
27
+ `
28
+ const EmptyPlaceholder = styled.div`
29
+ position:absolute;
30
+ `
31
+
32
+ const HORIZONTAL_DIMENSIONS = {
33
+ min: 'left',
34
+ max: 'right',
35
+ axis: 'x',
36
+ size: 'Width'
37
+ }
38
+ const VERTICAL_DIMENSIONS = {
39
+ min: 'top',
40
+ max: 'bottom',
41
+ axis: 'y',
42
+ size: 'Height'
43
+ }
44
+
45
+
46
+ const AUTO_SCROLL_BORDER_SIZE = 30
47
+ const AUTO_SCROLL_MAX_VELOCITY = 20
48
+ const AUTO_SCROLL_OVERFLOW_BUFFER = 100
49
+
50
+ function easeInCubic(t) { return Math.ceil(t * t * t / (AUTO_SCROLL_MAX_VELOCITY * AUTO_SCROLL_MAX_VELOCITY)) }
51
+
52
+ const disableWillChange = (el) => {
53
+ const willChange = el?.closest(".JS-has-wc")
54
+ if (willChange) {
55
+ willChange.classList.add('JS-wc-off')
56
+ disableWillChange(willChange.parentNode)
57
+ }
58
+ }
59
+ const reenableWillChange = (el) => {
60
+ const willChange = el?.closest(".JS-has-wc")
61
+ if (willChange) {
62
+ willChange.classList.remove('JS-wc-off')
63
+ reenableWillChange(willChange.parentNode)
64
+ }
65
+ }
66
+
67
+
68
+ const shouldPreventDrag = (e) => !(e.target).closest('body,.drag-drop-item,[data-drag="false"],input,textarea,button').matches('body,.drag-drop-item,[data-drag="true"]')
69
+
70
+ export class DraggableItem extends React.Component {
71
+
72
+ events = this.props.eventHandlers(this)
73
+
74
+ render() {
75
+ const {DragTag, as = 'div', eventHandlers, className, isDragging, droppable, dragIndex,roundedPlaceholder, inheritRadius, children, ...props} = this.props
76
+ const Wrapper = DragTag || as
77
+ const extraClassName = (isDragging ? ' drag-drop-placeholder' : '') + ' drag-drop-item'
78
+ return typeof children === 'function' ?
79
+ children({className: extraClassName, ...this.events, children: isDragging && <DropPosition rounded={roundedPlaceholder} inheritRadius={inheritRadius}/>}) :
80
+ <Wrapper
81
+ className={className + extraClassName}
82
+ isDragging={isDragging}
83
+ {...props}
84
+ {...this.events}
85
+ >
86
+ {children}
87
+ {
88
+ isDragging &&
89
+ <DropPosition rounded={roundedPlaceholder} inheritRadius={inheritRadius}/>
90
+ }
91
+
92
+ </Wrapper>
93
+ }
94
+ }
95
+
96
+ DraggableItem.a = (props) => <DraggableItem {...props} DragTag='a'/>
97
+
98
+ export const DroppableContainer = (props) =>
99
+ <Consumer>
100
+ {(dragProps) =>
101
+ <DroppableContainerComponent {...props} {...dragProps} />
102
+ }
103
+ </Consumer>
104
+
105
+
106
+ class DroppableContainerComponent extends React.Component {
107
+
108
+ static defaultProps = {
109
+ placeholder: () => <EmptyPlaceholder/>
110
+ }
111
+
112
+ draggables = []
113
+ emptyPlaceholder
114
+
115
+ addProps = (children, eventHandlers, activeIndex, extraProps) =>
116
+ children.map((c, index) =>
117
+ c.type === DraggableItem ? React.cloneElement(c, {
118
+ isDragging: activeIndex === index,
119
+ dragIndex: index,
120
+ droppable: this,
121
+ ref: (el) => this.draggables[index] = el,
122
+ eventHandlers: eventHandlers
123
+ }) : c
124
+ )
125
+
126
+ toPosition = (parentRect, {scrollTop, scrollLeft}) => (el, i) => {
127
+ if (el) {
128
+ const domNode = ReactDOM.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE.findDOMNode(el)
129
+ const {width, height} = domNode.getBoundingClientRect()
130
+ return {
131
+ droppable: this,
132
+ index: i,
133
+ width,
134
+ height,
135
+ y: parentRect.top + domNode.offsetTop + (height / 2) + (scrollTop || 0),
136
+ x: parentRect.left + domNode.offsetLeft + (width / 2) + (scrollLeft || 0)
137
+ }
138
+ }
139
+ }
140
+
141
+ getPosition = (i, scrollXY) => {
142
+ const el = this.draggables[i]
143
+ if (el) {
144
+ const parent = ReactDOM.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE.findDOMNode(this).offsetParent
145
+ const parentRect = parent.getBoundingClientRect()
146
+ return this.toPosition(parentRect, 0)(el, i)
147
+ }
148
+ }
149
+
150
+ getPositions = (scrollXY, dragInfo) => {
151
+ if (this.props.allowDrop && !this.props.allowDrop(_.map(dragInfo, v => v && v.props ? v.props.id : v))) {
152
+ return []
153
+ }
154
+ const parent = ReactDOM.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE.findDOMNode(this).offsetParent
155
+ if (!parent) { return []}
156
+ const parentRect = parent.getBoundingClientRect()
157
+ const toPosition = this.toPosition(parentRect, scrollXY)
158
+ const existing = this.draggables.map(toPosition).filter(_ => _)
159
+ if (existing.length || !this.props.placeholder) return existing
160
+ return [ReactDOM.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE.findDOMNode(this)].map(toPosition).filter(_ => _)
161
+ }
162
+
163
+
164
+ componentDidMount() {
165
+ this.props.addDroppable(this)
166
+ }
167
+
168
+ componentWillUnmount() {
169
+ this.props.removeDroppable(this)
170
+ }
171
+
172
+ repositionDragged = (array, {active, startIndex, startDroppable, activeIndex, activeDroppable}, getActiveElement) => {
173
+ // Repositioning dereferences startDroppable.props further down. Rendering with an
174
+ // activeDroppable but no startDroppable would take the whole tree down, so render
175
+ // the list untouched instead and let the next commit reconcile it.
176
+ if (!startDroppable) { return array }
177
+ if (activeDroppable && (activeDroppable === this || startDroppable === this)) {
178
+ // remove
179
+ const removedOrInserted = _.pullAt(startDroppable === this ? startIndex : [], array)
180
+ if (activeDroppable === this) {
181
+ // insert
182
+ removedOrInserted.splice(activeIndex, 0, getActiveElement())
183
+ }
184
+ return removedOrInserted
185
+ }
186
+ return array
187
+ }
188
+
189
+
190
+ getPlaceholder = (childArray, dragInfo) => childArray.find(_ => _.props.droppable) || dragInfo.startDroppable.props.children[dragInfo.startIndex]
191
+
192
+ getDragged = (childArray, dragInfo) => dragInfo.startDroppable === this ?
193
+ childArray[dragInfo.startIndex] :
194
+ React.cloneElement(this.getPlaceholder(childArray, dragInfo), {key: childArray[0].key + 'si' + dragInfo.startIndex})
195
+
196
+
197
+ childrenToarray = ({children, list, dragInfo}) => {
198
+ const {startDroppable, startIndex} = dragInfo
199
+ if (list) {
200
+ return React.Children.toArray(children(this.repositionDragged(list, dragInfo, () => startDroppable.props.list[startIndex])))
201
+ } else {
202
+ const childArray = React.Children.toArray(children)
203
+ return this.repositionDragged(childArray, dragInfo, () => this.getDragged(childArray, dragInfo))
204
+ }
205
+ }
206
+
207
+ render() {
208
+ const {group, placeholder, dragInfo, animation, timing, eventHandlers} = this.props
209
+ const {active, activeIndex, activeDroppable} = dragInfo
210
+ const childArray = this.childrenToarray(this.props)
211
+ if (!childArray.length && placeholder) {
212
+ return placeholder()
213
+ }
214
+ return <FlipList animation={animation} timing={timing} play={active}>{
215
+ this.addProps(childArray, eventHandlers, active && activeDroppable === this && activeIndex)
216
+ }</FlipList>
217
+ }
218
+ }
219
+
220
+
221
+ export class DraggableContainer extends React.Component {
222
+
223
+ static defaultProps = {
224
+ holdTouchTimer: 500,
225
+ scrollOnDrag: true,
226
+ setDragRef: () => {}
227
+ }
228
+
229
+ props/*: {
230
+ scrollContainer,
231
+ setItemOrder,
232
+ children,
233
+ noResistance,
234
+ holdTouchTimer
235
+ }*/
236
+
237
+ ghost
238
+ // dragEnd animates the ghost out over 300ms, so the tail of a drag is asynchronous
239
+ // and a new drag can start before the previous one has finished. Each drag therefore
240
+ // gets its own session holding everything its completion needs (ghost, dragInfo),
241
+ // instead of reading the shared slots a newer drag may already have taken over.
242
+ // dragToken identifies the drag that currently owns the shared state.
243
+ dragToken = 0
244
+ activeSession = null
245
+ positions = {}
246
+ passedResistance = false
247
+ pressedEnough = false
248
+ holdTouchTimer//: number
249
+
250
+ preventScroll//: boolean
251
+ state = {dragInfo: {}}
252
+
253
+ droppables = {}
254
+ offsetParent//: HTMLElement
255
+ parent//: HTMLElement
256
+ ghostDimensions//: GhostDimensions
257
+ itemsPositions//: Array<{ index: number, y: number, x: number }>
258
+ scrollContainer//: HTMLElement & any
259
+
260
+ lastScrollTop//: number
261
+ lastScrollLeft//: number
262
+
263
+ settedDragRef = false
264
+
265
+ preventDefault = (e) => {
266
+ if (!shouldPreventDrag(e) && this.preventScroll)
267
+ e.preventDefault()
268
+ }
269
+
270
+ getScrollContainer = () => {
271
+ return this.scrollContainer = this.scrollContainer || (this.props.scrollContainer ? this.props.scrollContainer : getScrollParent((ReactDOM.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE.findDOMNode(this))))
272
+ }
273
+
274
+ componentDidMount() {
275
+ this.parent.addEventListener('touchmove', this.preventDefault)
276
+ }
277
+
278
+ componentWillUnmount() {
279
+ this.parent.removeEventListener('touchmove', this.preventDefault)
280
+ }
281
+
282
+ componentDidUpdate(_, prevState) {
283
+ if (!isNaN(this.lastScrollTop) || !isNaN(this.lastScrollLeft)) {
284
+ this.getScrollContainer().scroll(this.lastScrollLeft, this.lastScrollTop)
285
+ this.lastScrollLeft = this.lastScrollTop = undefined
286
+ }
287
+ if (this.ghost && this.ghost.parentNode) {
288
+ this.ghost.parentNode.appendChild(this.ghost)
289
+ }
290
+ if (this.state.dragInfo !== prevState.dragInfo) {
291
+ this.resetPositions()
292
+ }
293
+ }
294
+
295
+
296
+ removeItem = (el) => {
297
+ this.domItems = this.domItems.filter(e => e !== el)
298
+ }
299
+
300
+ setOffsetParent = (e) => {
301
+ this.parent = e
302
+ this.offsetParent = e && (window.getComputedStyle(e).position !== 'static' ? e : e.offsetParent)
303
+ }
304
+
305
+ getConstraintDimensions = () => {
306
+ if (!this.offsetParent)
307
+ return {left: 0, top: 0, bottom: 0, right: 0, width: 0, height: 0}
308
+ return this.offsetParent.getBoundingClientRect()
309
+ }
310
+
311
+
312
+ dragStart = (draggable) => (e, {pageX, pageY, clientX, clientY}) => {
313
+ const currentTarget = e.currentTarget
314
+ if (shouldPreventDrag(e) || this.props.disabled) {
315
+ return
316
+ }
317
+ if (e.type === 'mousedown' && e.button !== 0) {
318
+ return
319
+ }
320
+ e.stopPropagation()
321
+ this.passedResistance = false
322
+ const holdTouchTimer = this.props.holdTouchTimer
323
+ this.pressedEnough = !holdTouchTimer
324
+ if (holdTouchTimer) {
325
+ e.persist()
326
+ this.holdTouchTimer = setTimeout(() => {
327
+ this.pressedEnough = true
328
+ if (window.navigator.vibrate)
329
+ window.navigator.vibrate([65])
330
+ if (!this.state.dragInfo.active) {
331
+ this.dragMove(e, {pageX, pageY, clientX, clientY}, )
332
+ }
333
+ }, holdTouchTimer)
334
+ }
335
+ const {width, height, top, left} = currentTarget.getBoundingClientRect()
336
+ const targetStyle = window.getComputedStyle(currentTarget)
337
+ const {top: parentTop, left: parentLeft} = this.getConstraintDimensions()
338
+ const offsetTop = parentTop + pageY - top + parseInt(targetStyle['margin-top'], 10)
339
+ const offsetLeft = parentLeft + pageX - left + parseInt(targetStyle['margin-left'], 10)
340
+ const {scrollTop, scrollLeft, scrollY, scrollX} = this.getScrollContainer()
341
+ this.positions = {
342
+ originalX: pageX, originalY: pageY,
343
+ offsetTop, offsetLeft, ghostHeight: height, ghostWidth: width,
344
+ ghostMarginTop: parseInt(targetStyle['margin-top'], 10),
345
+ ghostMarginLeft: parseInt(targetStyle['margin-left'], 10),
346
+ scrollLeft: scrollLeft || 0, scrollTop: scrollTop || 0,
347
+ scrollY: scrollY || 0, scrollX: scrollX || 0
348
+ }
349
+
350
+
351
+ const ghost = currentTarget.cloneNode(true)
352
+ ghost.className += ' dragging'
353
+ Object.assign(ghost.style, {
354
+ transition: '',
355
+ position: 'fixed', display: 'none', zIndex: '10', width: width + 'px', height: height + 'px', margin: '0',
356
+ top: (top - clientY) + 'px', left: (left - clientX) + 'px'
357
+ })
358
+
359
+ this.setupMove(ghost, currentTarget.offsetParent, draggable, {
360
+ width,
361
+ height,
362
+ offsetX: pageX - left,
363
+ offsetY: pageY - top
364
+ })
365
+ window.addEventListener('mousemove', this.mouseMove)
366
+ window.addEventListener('touchmove', this.touchMove, {passive: false})
367
+ window.addEventListener('mouseup', this.dragEnd)
368
+ window.addEventListener('touchend', this.dragEnd)
369
+ window.addEventListener('visibilitychange', this.dragEnd)
370
+ window.addEventListener('blur', this.dragEnd, false)
371
+ window.addEventListener('selectstart', this.disableSelect)
372
+ }
373
+
374
+
375
+ setupMove = (el, parent, item, ghostDimensions) => {
376
+ this.ghostDimensions = ghostDimensions
377
+ parent.click() // close popups
378
+ this.ghost = el
379
+ // Open this drag's session and take ownership of the shared state. Any previous
380
+ // session is now superseded: it still holds its own ghost and dragInfo, so it can
381
+ // finish animating and clean itself up without touching anything below.
382
+ this.dragToken++
383
+ this.activeSession = {
384
+ token: this.dragToken,
385
+ ghost: el,
386
+ dragInfo: null,
387
+ ended: false,
388
+ endTimeout: null,
389
+ onTransitionEnd: null
390
+ }
391
+ parent.appendChild(this.ghost)
392
+ disableWillChange(this.ghost)
393
+ this.setState({
394
+ dragInfo: {
395
+ active: false,
396
+ startIndex: item.props.dragIndex,
397
+ activeGroup: item.props.droppable.props.group,
398
+ startDroppable: item.props.droppable
399
+ }
400
+ })
401
+ this.settedDragRef = false
402
+ }
403
+
404
+ getScrollPosition = () => {
405
+ const {scrollY, scrollX, scrollLeft, scrollTop} = this.getScrollContainer()
406
+ const left = (scrollX || 0 + scrollLeft || 0) - this.positions.scrollX - this.positions.scrollLeft
407
+ const top = (scrollY || 0 + scrollTop || 0) - this.positions.scrollY - this.positions.scrollTop
408
+ return {scrollLeft: left, scrollTop: top}
409
+ }
410
+
411
+ resetPositions = (group = this.state.dragInfo.activeGroup) => {
412
+ this.itemsPositions =
413
+ this.droppables[group]
414
+ .reduce((acc, _) => acc.concat(_.getPositions(this.getScrollPosition(), this.state.dragInfo)), [])
415
+ .sort((a, b) => a.y - b.y || a.x - b.x)
416
+ }
417
+
418
+ move = (x, y) => {
419
+ if (!this.settedDragRef && this.props.setDragRef) {
420
+ this.props.setDragRef(this.ghost)
421
+ this.settedDragRef = true
422
+ }
423
+ const {scrollTop, scrollLeft} = this.getScrollPosition()
424
+ this.preventScroll = true
425
+ let item = this.findGhostIndex(x - this.ghostDimensions.offsetX + (scrollLeft || 0), y - this.ghostDimensions.offsetY + (scrollTop || 0), this.ghostDimensions.width, this.ghostDimensions.height)
426
+ if (!item) {
427
+ return
428
+ }
429
+ const dragInfo = this.state.dragInfo
430
+ if (item.index !== dragInfo.activeIndex || item.droppable !== dragInfo.activeDroppable) {
431
+ this.setState({
432
+ dragInfo: _.assign(dragInfo, {
433
+ active: true,
434
+ activePosition: item,
435
+ activeIndex: item.index,
436
+ activeDroppable: item.droppable
437
+ })
438
+ })
439
+ }
440
+ }
441
+
442
+ findGhostIndex = (ghostX1, ghostY1, ghostWidth, ghostHeight) => {
443
+ const ghostY2 = ghostY1 + ghostHeight
444
+ const ghostX2 = ghostX1 + ghostWidth
445
+ const centerInsideGhost = this.itemsPositions.filter(position =>
446
+ position.y >= ghostY1 &&
447
+ position.x >= ghostX1 &&
448
+ position.y <= ghostY2 &&
449
+ position.x <= ghostX2
450
+ )
451
+ const ghostX = ghostX1 + (ghostWidth / 2)
452
+ const ghostY = ghostY1 + (ghostHeight / 2)
453
+ const rectanglesIntersection = (a, b) => Math.max(0, Math.min(a.x2, b.x2) - Math.max(a.x1, b.x1)) * Math.max(0, Math.min(a.y2, b.y2) - Math.max(a.y1, b.y1))
454
+ const closestIndex = centerInsideGhost
455
+ .map((item) => {
456
+ const {x, y, width, height} = item
457
+ const area = width * height
458
+ const intersectionArea = rectanglesIntersection(
459
+ //at least 1px wide
460
+ {
461
+ x1: x - (Math.max(1, width) / 2),
462
+ x2: x + (width / 2),
463
+ y1: y - (Math.max(1, height) / 2),
464
+ y2: y + (height / 2)
465
+ },
466
+ {x1: ghostX1, x2: ghostX2, y1: ghostY1, y2: ghostY2}
467
+ )
468
+ const distance = Math.sqrt(Math.pow(x - ghostX, 2) + Math.pow(y - ghostY, 2))
469
+ return {area: intersectionArea / area, distance}
470
+ }).reduce((iMax, {area, distance}, i, arr) => area > arr[iMax].area || (area === arr[iMax].area && distance < arr[iMax].distance) ? i : iMax, 0)
471
+ return centerInsideGhost[closestIndex]
472
+ }
473
+
474
+ stopMoving = (session) => {
475
+ // Completion runs from mouseup, from a transitionend, or from the 300ms fallback
476
+ // timer, so it can be reached more than once for the same drag.
477
+ if (!session || session.ended) { return }
478
+ session.ended = true
479
+
480
+ const {token, ghost, dragInfo} = session
481
+ const {activeIndex, startIndex, activeGroup, activeDroppable, startDroppable} = dragInfo || {}
482
+ if (session.endTimeout) {
483
+ clearTimeout(session.endTimeout)
484
+ session.endTimeout = null
485
+ }
486
+ if (ghost && session.onTransitionEnd) {
487
+ ghost.removeEventListener('transitionend', session.onTransitionEnd)
488
+ session.onTransitionEnd = null
489
+ }
490
+
491
+ // Everything below acts on this session's own ghost and dragInfo, so it stays
492
+ // correct even once a newer drag has replaced this.ghost and this.state.dragInfo.
493
+ const finish = () => {
494
+ if (ghost?.parentNode) {
495
+ reenableWillChange(ghost)
496
+ ghost.parentNode.removeChild(ghost)
497
+ }
498
+ if ((activeIndex !== startIndex && activeIndex !== undefined) || (activeDroppable !== startDroppable && activeDroppable !== undefined)) {
499
+ const promise = this.props.setItemOrder(startIndex, activeIndex, startDroppable?.props?.id, activeDroppable?.props?.id, activeGroup)
500
+ if (promise && promise.then) {
501
+ promise.then(() => this.clearChangedOrder(token), () => this.clearChangedOrder(token))
502
+ } else {
503
+ this.clearChangedOrder(token)
504
+ }
505
+ } else {
506
+ this.clearChangedOrder(token)
507
+ }
508
+ }
509
+
510
+ if (token !== this.dragToken) {
511
+ // Superseded: a newer drag owns the shared state and the render output. Drop
512
+ // our ghost and commit our reorder, but leave that drag's state alone.
513
+ finish()
514
+ return
515
+ }
516
+
517
+ this.setState({
518
+ saving: {
519
+ list: this.props.list,
520
+ children: this.props.children,
521
+ dragInfo: _.set('active', false, dragInfo || {})
522
+ }
523
+ }, () => {
524
+ this.preventScroll = false
525
+ this.props.setDragRef(null)
526
+ this.settedDragRef = false
527
+ finish()
528
+ })
529
+ }
530
+
531
+ clearChangedOrder = (token) => {
532
+ // Only the drag that still owns the shared state may reset it — a superseded
533
+ // session clearing dragInfo here would wipe the drag currently in progress.
534
+ if (token !== undefined && token !== this.dragToken) { return }
535
+ this.setState({dragInfo: {}, saving: false})
536
+ }
537
+
538
+ dragMove = (e, {pageX, pageY, clientX, clientY}) => {
539
+ const distance = Math.sqrt((this.positions.originalX - pageX) ** 2 + (this.positions.originalY - pageY) ** 2)
540
+ if (e.type === 'mousemove') { // get touchstart and touchmove
541
+ if (!this.passedResistance && !this.props.noResistance && distance < 5) {
542
+ return
543
+ }
544
+ } else {
545
+ if (!this.pressedEnough) {
546
+ if (distance > 5) {
547
+ this.dragEnd()
548
+ }
549
+ return
550
+ }
551
+ e.preventDefault()
552
+ }
553
+ this.passedResistance = true
554
+
555
+ this.processAutoScroll(pageX, pageY, clientX, clientY)
556
+ }
557
+
558
+ completeMove = (pageX, pageY) => {
559
+ const {scrollTop, scrollLeft} = this.getScrollContainer()
560
+ const scrollYDelta = this.positions.scrollTop - (scrollTop || 0)
561
+ const scrollXDelta = this.positions.scrollLeft - (scrollLeft || 0)
562
+ const ghostLeft = pageX - this.positions.offsetLeft
563
+ const ghostTop = pageY - this.positions.offsetTop
564
+ const {height: parentHeight, width: parentWidth} = this.getConstraintDimensions()
565
+ const normalizedLeft = Math.max(Math.min(ghostLeft, parentWidth - this.positions.ghostWidth - this.positions.ghostMarginLeft + scrollXDelta), 0 - this.positions.ghostMarginLeft + scrollXDelta)
566
+ const normalizedTop = Math.max(Math.min(ghostTop, parentHeight - this.positions.ghostHeight - this.positions.ghostMarginTop + scrollYDelta), 0 - this.positions.ghostMarginTop + scrollYDelta)
567
+ const x = (normalizedLeft - ghostLeft) + pageX - window.scrollX
568
+ const y = (normalizedTop - ghostTop) + pageY - window.scrollY
569
+
570
+ if (this.ghost) {
571
+ this.ghost.style.transform = `translate(${x}px,${y}px)` + (this.props.ghostScale ? ' scale(' + this.props.ghostScale + ')' : '')
572
+ this.ghost.style.display = ''
573
+ }
574
+ this.move(pageX - (ghostLeft - normalizedLeft), pageY - (ghostTop - normalizedTop))
575
+ if (!this.state.dragInfo.active) {
576
+ this.setState({
577
+ dragInfo: _.assign(this.state.dragInfo,
578
+ {
579
+ activeIndex: this.state.dragInfo.startIndex,
580
+ activeDroppable: this.state.dragInfo.startDroppable,
581
+ active: true
582
+ })
583
+ }
584
+ )
585
+ }
586
+ }
587
+
588
+ autoScrollAnimation
589
+ autoScrollX
590
+ autoScrollY
591
+
592
+ getFixedParentRect = (node, not) => {
593
+ if (node) {
594
+ if (window.getComputedStyle(node)['position'].toLowerCase() === 'fixed' && not !== node)
595
+ return node.getBoundingClientRect()
596
+ else
597
+ return this.getFixedParentRect((node).offsetParent, not)
598
+ } else {
599
+ return {}
600
+ }
601
+ }
602
+
603
+ calculateDistanceFromBorder = (ghost, position, getElementBeforeBorder, scrollContainer, parentRect, keys) => {
604
+ const fixedUnderMouse = scrollContainer === window ? this.getFixedParentRect(getElementBeforeBorder(+AUTO_SCROLL_BORDER_SIZE), ghost) : {}
605
+ const min = scrollContainer !== window ? scrollContainer.getBoundingClientRect()[keys.min] :
606
+ (fixedUnderMouse)[keys.max] || (this.getFixedParentRect(getElementBeforeBorder(-AUTO_SCROLL_BORDER_SIZE), ghost))[keys.max] || 0
607
+ const max = scrollContainer !== window ? scrollContainer.getBoundingClientRect()[keys.max] :
608
+ (fixedUnderMouse)[keys.min] || (this.getFixedParentRect(getElementBeforeBorder(+AUTO_SCROLL_BORDER_SIZE), ghost))[keys.min] || window['inner' + keys.size]
609
+ const distanceMin = position - AUTO_SCROLL_BORDER_SIZE - min
610
+ const distanceMax = position + AUTO_SCROLL_BORDER_SIZE - max
611
+
612
+ const containerMin = scrollContainer === window ? 0 : Math.max(scrollContainer.getBoundingClientRect()[keys.min], 0)
613
+ const containerMax = scrollContainer === window ? window['inner' + keys.size] : Math.min(scrollContainer.getBoundingClientRect()[keys.max], window['inner' + keys.size])
614
+ const afterCenter = ((position - containerMin) * 2 / (containerMax - containerMin)) > 1
615
+ if (distanceMin < 0 && !afterCenter) return distanceMin
616
+ if (distanceMax > 0) return distanceMax
617
+ return 0
618
+ }
619
+
620
+ processAutoScroll = (pageX, pageY, clientX, clientY) => {
621
+ const scrollContainer = this.getScrollContainer()
622
+ const parentRect = this.getConstraintDimensions()
623
+ this.autoScrollY = this.calculateDistanceFromBorder(this.ghost, clientY, (diff) => document.elementFromPoint(clientX, clientY + diff), scrollContainer, parentRect, VERTICAL_DIMENSIONS)
624
+ // this.autoScrollX = this.calculateDistanceFromBorder(clientX, (diff) => document.elementFromPoint(clientX + diff, clientY), scrollContainer, parentRect, HORIZONTAL_DIMENSIONS)
625
+ this.autoScrollX = 0
626
+ // noinspection JSSuspiciousNameCombination
627
+ const keepY = this.autoScrollX === 0 || (this.autoScrollY !== 0 && Math.abs(this.autoScrollY) < Math.abs(this.autoScrollX))
628
+
629
+ this.autoScrollY = this.props.scrollOnDrag && keepY ? this.autoScrollY : 0
630
+ this.autoScrollX = this.props.scrollOnDrag && !keepY ? this.autoScrollX : 0
631
+
632
+ if (this.autoScrollX || this.autoScrollY) {
633
+
634
+ this.startAutoScroll(pageX, pageY, scrollContainer)
635
+ } else {
636
+ if (this.autoScrollAnimation) {
637
+ this.stopAutoScroll()
638
+ }
639
+ this.completeMove(pageX, pageY)
640
+ }
641
+ }
642
+
643
+ checkLimits = (delta, boundRect, constraintRect, scrollRect, keys) => {
644
+ if (delta > 0) {
645
+ if (constraintRect[keys.max] + AUTO_SCROLL_OVERFLOW_BUFFER < scrollRect[keys.max]) {
646
+ return 0
647
+ }
648
+ }
649
+ if (delta < 0) {
650
+ if (constraintRect[keys.min] - AUTO_SCROLL_OVERFLOW_BUFFER > scrollRect[keys.min]) {
651
+ return 0
652
+ }
653
+ }
654
+ return delta
655
+ }
656
+
657
+ startAutoScroll = (pageX, pageY, scrollContainer) => {
658
+ if (!this.autoScrollAnimation) {
659
+ this.autoScrollAnimation = window.requestAnimationFrame((timestamp) => {
660
+ const ghostRect = (this.ghost).getBoundingClientRect()
661
+ const parentRect = this.getConstraintDimensions()
662
+ const scrollRect = scrollContainer === window ?
663
+ {top: 0, bottom: window.innerHeight, left: 0, right: window.innerWidth} :
664
+ scrollContainer.getBoundingClientRect()
665
+ const deltaScrollX = easeInCubic(this.checkLimits(
666
+ this.autoScrollX < 0 ? Math.max(this.autoScrollX, -AUTO_SCROLL_MAX_VELOCITY) : Math.min(this.autoScrollX, AUTO_SCROLL_MAX_VELOCITY),
667
+ ghostRect,
668
+ parentRect,
669
+ scrollRect,
670
+ HORIZONTAL_DIMENSIONS
671
+ ))
672
+ const deltaScrollY = easeInCubic(this.checkLimits(
673
+ this.autoScrollY < 0 ? Math.max(this.autoScrollY, -AUTO_SCROLL_MAX_VELOCITY) : Math.min(this.autoScrollY, AUTO_SCROLL_MAX_VELOCITY),
674
+ ghostRect,
675
+ parentRect,
676
+ scrollRect,
677
+ VERTICAL_DIMENSIONS
678
+ ))
679
+
680
+ const willScroll = deltaScrollX !== 0 || deltaScrollY !== 0
681
+ if (willScroll) {
682
+
683
+ scrollContainer.scroll(
684
+ (isNaN(scrollContainer.scrollLeft) ? scrollContainer.pageXOffset : scrollContainer.scrollLeft) + deltaScrollX,
685
+ (isNaN(scrollContainer.scrollTop) ? scrollContainer.pageYOffset : scrollContainer.scrollTop) + deltaScrollY
686
+ )
687
+ }
688
+
689
+ this.autoScrollAnimation = 0
690
+ this.completeMove(pageX, pageY)
691
+ if (willScroll) {
692
+ this.startAutoScroll(pageX + (scrollContainer === window ? deltaScrollX : 0), pageY + (scrollContainer === window ? deltaScrollY : 0), scrollContainer)
693
+ }
694
+ }
695
+ )
696
+ } else {
697
+ this.completeMove(pageX, pageY)
698
+ }
699
+ }
700
+
701
+ stopAutoScroll = () => {
702
+ window.cancelAnimationFrame(this.autoScrollAnimation)
703
+ this.autoScrollAnimation = 0
704
+ }
705
+
706
+ dragEnd = () => {
707
+ window.removeEventListener('touchmove', this.touchMove)
708
+ window.removeEventListener('mousemove', this.mouseMove)
709
+ window.removeEventListener('mouseup', this.dragEnd)
710
+ window.removeEventListener('touchend', this.dragEnd)
711
+ window.removeEventListener('visibilitychange', this.dragEnd)
712
+ window.removeEventListener('blur', this.dragEnd)
713
+ window.removeEventListener('selectstart', this.disableSelect)
714
+ this.stopAutoScroll()
715
+ this.props.setDragRef && this.props.setDragRef(null)
716
+ clearTimeout(this.holdTouchTimer)
717
+ // Wired to mouseup, touchend, visibilitychange and blur, so this can fire several
718
+ // times for one drag. Only the first call owns the session and completes it.
719
+ const session = this.activeSession
720
+ if (!session) { return }
721
+ this.activeSession = null
722
+ session.dragInfo = this.state.dragInfo
723
+
724
+ const ghost = session.ghost
725
+ const {activeDroppable, activeIndex} = session.dragInfo
726
+ const finalPosition = activeDroppable && activeDroppable.getPosition(activeIndex, this.getScrollPosition())
727
+ if (activeDroppable && ghost && finalPosition) {
728
+ const {marginTop, marginLeft, left, top} = window.getComputedStyle(ghost)
729
+ const finalLeft = finalPosition.x - (Math.max(1, finalPosition.width) / 2) - parseInt(left) - parseInt(marginLeft)
730
+ const finalTop = finalPosition.y - (Math.max(1, finalPosition.height) / 2) - parseInt(top) - parseInt(marginTop)
731
+ ghost.style.transition = `transform 300ms ease-in-out 0ms, opacity 300ms ease-in-out 0ms`
732
+ const newTransform = `translate(${finalLeft}px,${finalTop}px)`
733
+ if ((ghost.style.transform||"").replace(/\s/g,'') === newTransform) {
734
+ this.stopMoving(session)
735
+ } else {
736
+ ghost.style.transform = newTransform
737
+ session.onTransitionEnd = () => this.stopMoving(session)
738
+ ghost.addEventListener('transitionend', session.onTransitionEnd)
739
+ session.endTimeout = setTimeout(session.onTransitionEnd, 300)
740
+ }
741
+ } else {
742
+ this.stopMoving(session)
743
+ }
744
+ }
745
+
746
+ disableSelect(event) {
747
+ event.preventDefault()
748
+ }
749
+
750
+
751
+ addDroppable = (el) => {
752
+ this.droppables[el.props.group] = (this.droppables[el.props.group] || []).concat(el)
753
+ }
754
+
755
+ remoevDroppable = (el) => {
756
+ this.droppables[el.props.group] = this.droppables[el.props.group].filter(_ => _ !== el)
757
+ }
758
+
759
+ mouseDown = (draggable) => normalizeMouse(this.dragStart(draggable),this.dragEnd)
760
+ mouseMove = normalizeMouse(this.dragMove,this.dragEnd)
761
+ touchStart = (draggable) => normalizeTouch(this.dragStart(draggable), this.dragEnd)
762
+ touchMove = normalizeTouch(this.dragMove, this.dragEnd)
763
+
764
+ providerValue = {}
765
+
766
+ providerEvents = (draggable) => ({
767
+ onMouseDown: this.mouseDown(draggable),
768
+ onTouchStart: this.touchStart(draggable)
769
+ })
770
+
771
+ render() {
772
+ const {setItemOrder, noResistance, animation, timing, disabled, list, ghostScale, setDragRef, scrollOnDrag, holdTouchTimer, children, ...rest} = this.props
773
+ const {saving, dragInfo} = this.state
774
+ this.providerValue = _.assign(
775
+ this.providerValue,
776
+ {
777
+ addDroppable: this.addDroppable,
778
+ removeDroppable: this.remoevDroppable,
779
+ dragInfo: saving ? saving.dragInfo : dragInfo,
780
+ eventHandlers: this.providerEvents
781
+ }
782
+ )
783
+ if (this.scrollContainer) {
784
+ const scrollContainer = this.getScrollContainer()
785
+ this.lastScrollTop = (isNaN(scrollContainer['scrollTop']) ? scrollContainer['pageYOffset'] : scrollContainer['scrollTop']) || undefined
786
+ this.lastScrollLeft = (isNaN(scrollContainer['scrollLeft']) ? scrollContainer['pageXOffset'] : scrollContainer['scrollLeft']) || undefined
787
+ }
788
+ return (
789
+ <Provider value={this.providerValue}>
790
+ <div {...rest} ref={this.setOffsetParent}>
791
+ <DroppableContainer animation={animation} timing={timing} list={saving ? saving.list : list} placeholder={false}>
792
+ {saving ? saving.children : children}
793
+ </DroppableContainer>
794
+ </div>
795
+ </Provider>
796
+ )
797
+ }
798
+ }
799
+
800
+
801
+ // type GhostDimensions = { offsetX: number, offsetY: number, width: number, height: number }