Skip to content

Commit 611a0ce

Browse files
authored
Merge pull request #1819 from ehahn9/master
fix: switch DnD to modern context API (was legacy)
2 parents 02bbeb1 + 4574ab7 commit 611a0ce

File tree

10 files changed

+4459
-2057
lines changed

10 files changed

+4459
-2057
lines changed

examples/demos/dnd.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ class Dnd extends React.Component {
5555

5656
const nextEvents = events.map(existingEvent => {
5757
return existingEvent.id == event.id
58-
? { ...existingEvent, start, end }
58+
? { ...existingEvent, start, end, allDay }
5959
: existingEvent
6060
})
6161

@@ -82,7 +82,7 @@ class Dnd extends React.Component {
8282
//alert(`${event.title} was resized to ${start}-${end}`)
8383
}
8484

85-
newEvent(event) {
85+
newEvent(_event) {
8686
// let idList = this.state.events.map(a => a.id)
8787
// let newId = Math.max(...idList) + 1
8888
// let hour = {

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@
7676
"@4c/rollout": "^1.4.0",
7777
"@babel/cli": "^7.1.0",
7878
"@babel/core": "^7.1.5",
79-
"@storybook/addon-actions": "^5.0.11",
80-
"@storybook/react": "^5.0.11",
79+
"@storybook/addon-actions": "^6.1.15",
80+
"@storybook/react": "^6.1.15",
8181
"autoprefixer": "^9.5.1",
8282
"babel-core": "^7.0.0-bridge.0",
8383
"babel-eslint": "^10.0.1",
@@ -130,7 +130,7 @@
130130
"dependencies": {
131131
"@babel/runtime": "^7.1.5",
132132
"clsx": "^1.0.4",
133-
"date-arithmetic": "^4.0.1",
133+
"date-arithmetic": "^4.1.0",
134134
"dom-helpers": "^5.1.0",
135135
"invariant": "^2.2.4",
136136
"lodash": "^4.17.11",

src/addons/dragAndDrop/DnDContext.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import React from 'react'
2+
3+
export const DnDContext = React.createContext()

src/addons/dragAndDrop/EventContainerWrapper.js

Lines changed: 30 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,16 @@
11
import PropTypes from 'prop-types'
22
import React from 'react'
33
import * as dates from '../../utils/dates'
4-
import { findDOMNode } from 'react-dom'
4+
import { DnDContext } from './DnDContext'
55

66
import Selection, {
77
getBoundsForNode,
88
getEventNodeFromPoint,
99
} from '../../Selection'
1010
import TimeGridEvent from '../../TimeGridEvent'
11-
import { dragAccessors } from './common'
11+
import { dragAccessors, eventTimes, pointInColumn } from './common'
1212
import NoopWrapper from '../../NoopWrapper'
1313

14-
const pointInColumn = (bounds, { x, y }) => {
15-
const { left, right, top } = bounds
16-
return x < right + 10 && x > left && y > top
17-
}
18-
const propTypes = {}
19-
2014
class EventContainerWrapper extends React.Component {
2115
static propTypes = {
2216
accessors: PropTypes.object.isRequired,
@@ -27,20 +21,12 @@ class EventContainerWrapper extends React.Component {
2721
resource: PropTypes.any,
2822
}
2923

30-
static contextTypes = {
31-
draggable: PropTypes.shape({
32-
onStart: PropTypes.func,
33-
onEnd: PropTypes.func,
34-
onDropFromOutside: PropTypes.func,
35-
onBeginAction: PropTypes.func,
36-
dragAndDropAction: PropTypes.object,
37-
dragFromOutsideItem: PropTypes.func,
38-
}),
39-
}
24+
static contextType = DnDContext
4025

4126
constructor(...args) {
4227
super(...args)
4328
this.state = {}
29+
this.ref = React.createRef()
4430
}
4531

4632
componentDidMount() {
@@ -73,43 +59,31 @@ class EventContainerWrapper extends React.Component {
7359
})
7460
}
7561

76-
handleMove = (point, boundaryBox) => {
62+
handleMove = (point, bounds) => {
63+
if (!pointInColumn(bounds, point)) return this.reset()
7764
const { event } = this.context.draggable.dragAndDropAction
7865
const { accessors, slotMetrics } = this.props
7966

80-
if (!pointInColumn(boundaryBox, point)) {
81-
this.reset()
82-
return
83-
}
84-
85-
let currentSlot = slotMetrics.closestSlotFromPoint(
67+
const newSlot = slotMetrics.closestSlotFromPoint(
8668
{ y: point.y - this.eventOffsetTop, x: point.x },
87-
boundaryBox
69+
bounds
8870
)
8971

90-
let eventStart = accessors.start(event)
91-
let eventEnd = accessors.end(event)
92-
let end = dates.add(
93-
currentSlot,
94-
dates.diff(eventStart, eventEnd, 'minutes'),
95-
'minutes'
96-
)
97-
98-
this.update(event, slotMetrics.getRange(currentSlot, end, false, true))
72+
const { duration } = eventTimes(event, accessors)
73+
let newEnd = dates.add(newSlot, duration, 'milliseconds')
74+
this.update(event, slotMetrics.getRange(newSlot, newEnd, false, true))
9975
}
10076

101-
handleResize(point, boundaryBox) {
102-
let start, end
77+
handleResize(point, bounds) {
10378
const { accessors, slotMetrics } = this.props
10479
const { event, direction } = this.context.draggable.dragAndDropAction
80+
const newTime = slotMetrics.closestSlotFromPoint(point, bounds)
10581

106-
let currentSlot = slotMetrics.closestSlotFromPoint(point, boundaryBox)
82+
let { start, end } = eventTimes(event, accessors)
10783
if (direction === 'UP') {
108-
end = accessors.end(event)
109-
start = dates.min(currentSlot, slotMetrics.closestSlotFromDate(end, -1))
84+
start = dates.min(newTime, slotMetrics.closestSlotFromDate(end, -1))
11085
} else if (direction === 'DOWN') {
111-
start = accessors.start(event)
112-
end = dates.max(currentSlot, slotMetrics.closestSlotFromDate(start))
86+
end = dates.max(newTime, slotMetrics.closestSlotFromDate(start))
11387
}
11488

11589
this.update(event, slotMetrics.getRange(start, end))
@@ -132,10 +106,11 @@ class EventContainerWrapper extends React.Component {
132106
}
133107

134108
_selectable = () => {
135-
let node = findDOMNode(this)
109+
let wrapper = this.ref.current
110+
let node = wrapper.children[0]
136111
let isBeingDragged = false
137112
let selector = (this._selector = new Selection(() =>
138-
node.closest('.rbc-time-view')
113+
wrapper.closest('.rbc-time-view')
139114
))
140115

141116
selector.on('beforeSelect', point => {
@@ -149,6 +124,12 @@ class EventContainerWrapper extends React.Component {
149124
const eventNode = getEventNodeFromPoint(node, point)
150125
if (!eventNode) return false
151126

127+
// eventOffsetTop is distance from the top of the event to the initial
128+
// mouseDown position. We need this later to compute the new top of the
129+
// event during move operations, since the final location is really a
130+
// delta from this point. note: if we want to DRY this with WeekWrapper,
131+
// probably better just to capture the mouseDown point here and do the
132+
// placement computation in handleMove()...
152133
this.eventOffsetTop = point.y - getBoundsForNode(eventNode).top
153134
})
154135

@@ -162,19 +143,14 @@ class EventContainerWrapper extends React.Component {
162143

163144
selector.on('dropFromOutside', point => {
164145
if (!this.context.draggable.onDropFromOutside) return
165-
166146
const bounds = getBoundsForNode(node)
167-
168147
if (!pointInColumn(bounds, point)) return
169-
170148
this.handleDropFromOutside(point, bounds)
171149
})
172150

173151
selector.on('dragOver', point => {
174152
if (!this.context.draggable.dragFromOutsideItem) return
175-
176153
const bounds = getBoundsForNode(node)
177-
178154
this.handleDropFromOutside(point, bounds)
179155
})
180156

@@ -220,7 +196,7 @@ class EventContainerWrapper extends React.Component {
220196
this._selector = null
221197
}
222198

223-
render() {
199+
renderContent() {
224200
const {
225201
children,
226202
accessors,
@@ -231,7 +207,6 @@ class EventContainerWrapper extends React.Component {
231207
} = this.props
232208

233209
let { event, top, height } = this.state
234-
235210
if (!event) return children
236211

237212
const events = children.props.children
@@ -271,8 +246,10 @@ class EventContainerWrapper extends React.Component {
271246
),
272247
})
273248
}
274-
}
275249

276-
EventContainerWrapper.propTypes = propTypes
250+
render() {
251+
return <div ref={this.ref}>{this.renderContent()}</div>
252+
}
253+
}
277254

278255
export default EventContainerWrapper

src/addons/dragAndDrop/EventWrapper.js

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,11 @@
11
import PropTypes from 'prop-types'
22
import React from 'react'
33
import clsx from 'clsx'
4-
import { accessor } from '../../utils/propTypes'
54
import { accessor as get } from '../../utils/accessors'
5+
import { DnDContext } from './DnDContext'
66

77
class EventWrapper extends React.Component {
8-
static contextTypes = {
9-
draggable: PropTypes.shape({
10-
onStart: PropTypes.func,
11-
onEnd: PropTypes.func,
12-
onBeginAction: PropTypes.func,
13-
draggableAccessor: accessor,
14-
resizableAccessor: accessor,
15-
dragAndDropAction: PropTypes.object,
16-
}),
17-
}
8+
static contextType = DnDContext
189

1910
static propTypes = {
2011
type: PropTypes.oneOf(['date', 'time']),
@@ -32,28 +23,28 @@ class EventWrapper extends React.Component {
3223

3324
handleResizeUp = e => {
3425
if (e.button !== 0) return
35-
e.stopPropagation()
3626
this.context.draggable.onBeginAction(this.props.event, 'resize', 'UP')
3727
}
3828
handleResizeDown = e => {
3929
if (e.button !== 0) return
40-
e.stopPropagation()
4130
this.context.draggable.onBeginAction(this.props.event, 'resize', 'DOWN')
4231
}
4332
handleResizeLeft = e => {
4433
if (e.button !== 0) return
45-
e.stopPropagation()
4634
this.context.draggable.onBeginAction(this.props.event, 'resize', 'LEFT')
4735
}
4836
handleResizeRight = e => {
4937
if (e.button !== 0) return
50-
e.stopPropagation()
5138
this.context.draggable.onBeginAction(this.props.event, 'resize', 'RIGHT')
5239
}
5340
handleStartDragging = e => {
54-
if (e.button === 0) {
41+
if (e.button !== 0) return
42+
// hack: because of the way the anchors are arranged in the DOM, resize
43+
// anchor events will bubble up to the move anchor listener. Don't start
44+
// move operations when we're on a resize anchor.
45+
const isResizeHandle = e.target.className.includes('rbc-addons-dnd-resize')
46+
if (!isResizeHandle)
5547
this.context.draggable.onBeginAction(this.props.event, 'move')
56-
}
5748
}
5849

5950
renderAnchor(direction) {

src/addons/dragAndDrop/README.md

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,66 @@
11
### Drag and Drop
22

3+
Creates a higher-order component (HOC) supporting drag & drop for moving and/or resizing of events:
4+
35
```js
46
import { Calendar } from 'react-big-calendar'
57
import withDragAndDrop from 'react-big-calendar/lib/addons/dragAndDrop'
6-
78
import 'react-big-calendar/lib/addons/dragAndDrop/styles.css'
89

9-
const DraggableCalendar = withDragAndDrop(Calendar)
10+
const DnDCalendar = withDragAndDrop(Calendar)
1011

1112
/* ... */
1213

1314
return (
14-
<DraggableCalendar
15+
<DnDCalendar
1516
localizer={myLocalizer}
1617
events={events}
1718
draggableAccessor={event => true}
1819
/>
1920
)
2021
```
22+
23+
Set `resizable` to false in your calendar if you don't want events to be resizable.
24+
`resizable` is set to true by default.
25+
26+
The HOC adds `onEventDrop`, `onEventResize`, and `onDragStart` callback properties if the events are
27+
moved or resized. These callbacks are called with these signatures:
28+
29+
```js
30+
function onEventDrop({ event, start, end, allDay }) {...}
31+
function onEventResize(type, { event, start, end, allDay }) {...} // type is always 'drop'
32+
function onDragStart({ event, action, direction }) {...}
33+
```
34+
35+
Moving and resizing of events has some subtlety which one should be aware of:
36+
37+
- In some situations, non-allDay events are displayed in "row" format where they
38+
are rendered horizontally. This is the case for ALL events in a month view. It
39+
is also occurs with multi-day events in a day or week view (unless `showMultiDayTimes`
40+
is set).
41+
42+
- When dropping or resizing non-allDay events into a the header area or when
43+
resizing them horizontally because they are displayed in row format, their
44+
times are preserved, only their date is changed.
45+
46+
- If you care about these corner cases, you can examine the `allDay` param suppled
47+
in the callback to determine how the user dropped or resized the event.
48+
49+
Additionally, this HOC adds the callback props `onDropFromOutside` and `onDragOver`:
50+
51+
- By default, the calendar will not respond to outside draggable items being dropped
52+
onto it. However, if `onDropFromOutside` callback is passed, then when draggable
53+
DOM elements are dropped on the calendar, the callback will fire, receiving an
54+
object with start and end times, and an allDay boolean.
55+
56+
- If `onDropFromOutside` is passed, but `onDragOver` is not, any draggable event will be
57+
droppable onto the calendar by default. On the other hand, if an `onDragOver` callback
58+
_is_ passed, then it can discriminate as to whether a draggable item is droppable on the
59+
calendar. To designate a draggable item as droppable, call `event.preventDefault`
60+
inside `onDragOver`. If `event.preventDefault` is not called in the `onDragOver`
61+
callback, then the draggable item will not be droppable on the calendar.
62+
63+
```js
64+
function onDropFromOutside({ start, end, allDay }) {...}
65+
function onDragOver(DragEvent: event) {...}
66+
```

0 commit comments

Comments
 (0)