diff --git a/package.json b/package.json
index b157e9836..94f99b974 100644
--- a/package.json
+++ b/package.json
@@ -130,7 +130,7 @@
"dependencies": {
"@babel/runtime": "^7.1.5",
"clsx": "^1.0.4",
- "date-arithmetic": "^4.0.1",
+ "date-arithmetic": "^4.1.0",
"dom-helpers": "^5.1.0",
"invariant": "^2.2.4",
"lodash": "^4.17.11",
diff --git a/src/addons/dragAndDrop/EventContainerWrapper.js b/src/addons/dragAndDrop/EventContainerWrapper.js
index a700b0ec4..a0227146d 100644
--- a/src/addons/dragAndDrop/EventContainerWrapper.js
+++ b/src/addons/dragAndDrop/EventContainerWrapper.js
@@ -1,7 +1,6 @@
import PropTypes from 'prop-types'
import React from 'react'
import * as dates from '../../utils/dates'
-import { findDOMNode } from 'react-dom'
import { DnDContext } from './DnDContext'
import Selection, {
@@ -9,15 +8,9 @@ import Selection, {
getEventNodeFromPoint,
} from '../../Selection'
import TimeGridEvent from '../../TimeGridEvent'
-import { dragAccessors } from './common'
+import { dragAccessors, eventTimes, pointInColumn } from './common'
import NoopWrapper from '../../NoopWrapper'
-const pointInColumn = (bounds, { x, y }) => {
- const { left, right, top } = bounds
- return x < right + 10 && x > left && y > top
-}
-const propTypes = {}
-
class EventContainerWrapper extends React.Component {
static propTypes = {
accessors: PropTypes.object.isRequired,
@@ -33,6 +26,7 @@ class EventContainerWrapper extends React.Component {
constructor(...args) {
super(...args)
this.state = {}
+ this.ref = React.createRef()
}
componentDidMount() {
@@ -65,43 +59,31 @@ class EventContainerWrapper extends React.Component {
})
}
- handleMove = (point, boundaryBox) => {
+ handleMove = (point, bounds) => {
+ if (!pointInColumn(bounds, point)) return this.reset()
const { event } = this.context.draggable.dragAndDropAction
const { accessors, slotMetrics } = this.props
- if (!pointInColumn(boundaryBox, point)) {
- this.reset()
- return
- }
-
- let currentSlot = slotMetrics.closestSlotFromPoint(
+ const newSlot = slotMetrics.closestSlotFromPoint(
{ y: point.y - this.eventOffsetTop, x: point.x },
- boundaryBox
+ bounds
)
- let eventStart = accessors.start(event)
- let eventEnd = accessors.end(event)
- let end = dates.add(
- currentSlot,
- dates.diff(eventStart, eventEnd, 'minutes'),
- 'minutes'
- )
-
- this.update(event, slotMetrics.getRange(currentSlot, end, false, true))
+ const { duration } = eventTimes(event, accessors)
+ let newEnd = dates.add(newSlot, duration, 'milliseconds')
+ this.update(event, slotMetrics.getRange(newSlot, newEnd, false, true))
}
- handleResize(point, boundaryBox) {
- let start, end
+ handleResize(point, bounds) {
const { accessors, slotMetrics } = this.props
const { event, direction } = this.context.draggable.dragAndDropAction
+ const newTime = slotMetrics.closestSlotFromPoint(point, bounds)
- let currentSlot = slotMetrics.closestSlotFromPoint(point, boundaryBox)
+ let { start, end } = eventTimes(event, accessors)
if (direction === 'UP') {
- end = accessors.end(event)
- start = dates.min(currentSlot, slotMetrics.closestSlotFromDate(end, -1))
+ start = dates.min(newTime, slotMetrics.closestSlotFromDate(end, -1))
} else if (direction === 'DOWN') {
- start = accessors.start(event)
- end = dates.max(currentSlot, slotMetrics.closestSlotFromDate(start))
+ end = dates.max(newTime, slotMetrics.closestSlotFromDate(start))
}
this.update(event, slotMetrics.getRange(start, end))
@@ -124,10 +106,11 @@ class EventContainerWrapper extends React.Component {
}
_selectable = () => {
- let node = findDOMNode(this)
+ let wrapper = this.ref.current
+ let node = wrapper.children[0]
let isBeingDragged = false
let selector = (this._selector = new Selection(() =>
- node.closest('.rbc-time-view')
+ wrapper.closest('.rbc-time-view')
))
selector.on('beforeSelect', point => {
@@ -141,6 +124,12 @@ class EventContainerWrapper extends React.Component {
const eventNode = getEventNodeFromPoint(node, point)
if (!eventNode) return false
+ // eventOffsetTop is distance from the top of the event to the initial
+ // mouseDown position. We need this later to compute the new top of the
+ // event during move operations, since the final location is really a
+ // delta from this point. note: if we want to DRY this with WeekWrapper,
+ // probably better just to capture the mouseDown point here and do the
+ // placement computation in handleMove()...
this.eventOffsetTop = point.y - getBoundsForNode(eventNode).top
})
@@ -154,19 +143,14 @@ class EventContainerWrapper extends React.Component {
selector.on('dropFromOutside', point => {
if (!this.context.draggable.onDropFromOutside) return
-
const bounds = getBoundsForNode(node)
-
if (!pointInColumn(bounds, point)) return
-
this.handleDropFromOutside(point, bounds)
})
selector.on('dragOver', point => {
if (!this.context.draggable.dragFromOutsideItem) return
-
const bounds = getBoundsForNode(node)
-
this.handleDropFromOutside(point, bounds)
})
@@ -212,7 +196,7 @@ class EventContainerWrapper extends React.Component {
this._selector = null
}
- render() {
+ renderContent() {
const {
children,
accessors,
@@ -223,7 +207,6 @@ class EventContainerWrapper extends React.Component {
} = this.props
let { event, top, height } = this.state
-
if (!event) return children
const events = children.props.children
@@ -263,8 +246,10 @@ class EventContainerWrapper extends React.Component {
),
})
}
-}
-EventContainerWrapper.propTypes = propTypes
+ render() {
+ return
{this.renderContent()}
+ }
+}
export default EventContainerWrapper
diff --git a/src/addons/dragAndDrop/WeekWrapper.js b/src/addons/dragAndDrop/WeekWrapper.js
index 9d25616c7..0a6041895 100644
--- a/src/addons/dragAndDrop/WeekWrapper.js
+++ b/src/addons/dragAndDrop/WeekWrapper.js
@@ -1,28 +1,16 @@
import PropTypes from 'prop-types'
import React from 'react'
+import EventRow from '../../EventRow'
+import Selection, {
+ getBoundsForNode,
+ getEventNodeFromPoint,
+} from '../../Selection'
import * as dates from '../../utils/dates'
-import { getSlotAtX, pointInBox } from '../../utils/selection'
-import { findDOMNode } from 'react-dom'
-
import { eventSegments } from '../../utils/eventLevels'
-import Selection, { getBoundsForNode } from '../../Selection'
-import EventRow from '../../EventRow'
-import { dragAccessors } from './common'
+import { getSlotAtX, pointInBox } from '../../utils/selection'
+import { dragAccessors, eventTimes } from './common'
import { DnDContext } from './DnDContext'
-const propTypes = {}
-
-const eventTimes = (event, accessors) => {
- let start = accessors.start(event)
- let end = accessors.end(event)
-
- const isZeroDuration =
- dates.eq(start, end, 'minutes') && start.getMinutes() === 0
- // make zero duration midnight events at least one day long
- if (isZeroDuration) end = dates.add(end, 1, 'day')
- return { start, end }
-}
-
class WeekWrapper extends React.Component {
static propTypes = {
isAllDay: PropTypes.bool,
@@ -31,6 +19,7 @@ class WeekWrapper extends React.Component {
getters: PropTypes.object.isRequired,
components: PropTypes.object.isRequired,
resourceId: PropTypes.any,
+ rtl: PropTypes.bool,
}
static contextType = DnDContext
@@ -38,6 +27,7 @@ class WeekWrapper extends React.Component {
constructor(...args) {
super(...args)
this.state = {}
+ this.ref = React.createRef()
}
componentDidMount() {
@@ -71,42 +61,34 @@ class WeekWrapper extends React.Component {
this.setState({ segment })
}
- handleMove = ({ x, y }, node, draggedEvent) => {
+ handleMove = (point, bounds, draggedEvent) => {
+ if (!pointInBox(bounds, point)) return this.reset()
const event = this.context.draggable.dragAndDropAction.event || draggedEvent
- const metrics = this.props.slotMetrics
- const { accessors } = this.props
-
- if (!event) return
-
- let rowBox = getBoundsForNode(node)
-
- if (!pointInBox(rowBox, { x, y })) {
- this.reset()
- return
- }
+ const { accessors, slotMetrics, rtl } = this.props
- // Make sure to maintain the time of the start date while moving it to the new slot
- let start = dates.merge(
- metrics.getDateForSlot(getSlotAtX(rowBox, x, false, metrics.slots)),
- accessors.start(event)
+ const slot = getSlotAtX(
+ bounds,
+ point.x - this.eventOffsetLeft,
+ rtl,
+ slotMetrics.slots
)
- let end = dates.add(
- start,
- dates.diff(accessors.start(event), accessors.end(event), 'minutes'),
- 'minutes'
- )
+ const date = slotMetrics.getDateForSlot(slot)
+ // Adjust the dates, but maintain the times when moving
+ let { start, duration } = eventTimes(event, accessors)
+ start = dates.merge(date, start)
+ const end = dates.add(start, duration, 'milliseconds')
+ // TODO: when dragging a multi-row event, only the first row is animating
this.update(event, start, end)
}
- handleDropFromOutside = (point, rowBox) => {
+ handleDropFromOutside = (point, bounds) => {
if (!this.context.draggable.onDropFromOutside) return
- const { slotMetrics: metrics } = this.props
+ const { slotMetrics, rtl } = this.props
- let start = metrics.getDateForSlot(
- getSlotAtX(rowBox, point.x, false, metrics.slots)
- )
+ const slot = getSlotAtX(bounds, point.x, rtl, slotMetrics.slots)
+ const start = slotMetrics.getDateForSlot(slot)
this.context.draggable.onDropFromOutside({
start,
@@ -115,103 +97,103 @@ class WeekWrapper extends React.Component {
})
}
- handleDragOverFromOutside = ({ x, y }, node) => {
+ handleDragOverFromOutside = (point, node) => {
if (!this.context.draggable.dragFromOutsideItem) return
-
- this.handleMove(
- { x, y },
- node,
- this.context.draggable.dragFromOutsideItem()
- )
+ this.handleMove(point, node, this.context.draggable.dragFromOutsideItem())
}
- handleResize(point, node) {
+ // TODO: when resizing RIGHT, the mouse has to make it all the way to the
+ // very end of the slot before it jumps...
+ handleResize(point, bounds) {
const { event, direction } = this.context.draggable.dragAndDropAction
- const { accessors, slotMetrics: metrics } = this.props
+ const { accessors, slotMetrics, rtl } = this.props
- let { start, end } = eventTimes(event, accessors)
+ let { start, end, allDay } = eventTimes(event, accessors)
let originalStart = start
let originalEnd = end
- let rowBox = getBoundsForNode(node)
- let cursorInRow = pointInBox(rowBox, point)
+ const slot = getSlotAtX(bounds, point.x, rtl, slotMetrics.slots)
+ const date = slotMetrics.getDateForSlot(slot)
+ let cursorInRow = pointInBox(bounds, point)
if (direction === 'RIGHT') {
if (cursorInRow) {
- if (metrics.last < start) return this.reset()
- end = metrics.getDateForSlot(
- getSlotAtX(rowBox, point.x, false, metrics.slots)
- )
+ if (slotMetrics.last < start) return this.reset()
+ end = date
} else if (
- dates.inRange(start, metrics.first, metrics.last) ||
- (rowBox.bottom < point.y && +metrics.first > +start)
+ dates.inRange(start, slotMetrics.first, slotMetrics.last) ||
+ (bounds.bottom < point.y && dates.gt(slotMetrics.first, start))
) {
- end = dates.add(metrics.last, 1, 'milliseconds')
+ end = dates.add(slotMetrics.last, 1, 'milliseconds')
} else {
this.setState({ segment: null })
return
}
- end = dates.merge(end, accessors.end(event))
+ end = dates.merge(end, originalEnd)
if (dates.lt(end, start)) {
end = originalEnd
}
} else if (direction === 'LEFT') {
- // inbetween Row
if (cursorInRow) {
- if (metrics.first > end) return this.reset()
-
- start = metrics.getDateForSlot(
- getSlotAtX(rowBox, point.x, false, metrics.slots)
- )
+ if (slotMetrics.first > end) return this.reset()
+ start = date
} else if (
- dates.inRange(end, metrics.first, metrics.last) ||
- (rowBox.top > point.y && +metrics.last < +end)
+ dates.inRange(end, slotMetrics.first, slotMetrics.last) ||
+ (bounds.top > point.y && dates.lt(slotMetrics.last, end))
) {
- start = dates.add(metrics.first, -1, 'milliseconds')
+ start = dates.add(slotMetrics.first, -1, 'milliseconds')
} else {
this.reset()
return
}
- start = dates.merge(start, accessors.start(event))
+ start = dates.merge(start, originalStart)
if (dates.gt(start, end)) {
start = originalStart
}
}
+ if (allDay) end = dates.add(end, 1, 'day')
this.update(event, start, end)
}
_selectable = () => {
- let node = findDOMNode(this).closest('.rbc-month-row, .rbc-allday-cell')
- let container = node.closest('.rbc-month-view, .rbc-time-view')
+ let wrapper = this.ref.current
+ let node = wrapper.closest('.rbc-month-row, .rbc-allday-cell')
+ let container = wrapper.closest('.rbc-month-view, .rbc-time-view')
let selector = (this._selector = new Selection(() => container))
selector.on('beforeSelect', point => {
const { isAllDay } = this.props
const { action } = this.context.draggable.dragAndDropAction
+ const eventNode = getEventNodeFromPoint(node, point)
+
+ // eventOffsetLeft is distance from the left of the event to the initial
+ // mouseDown position. We need this later to compute the new top of the
+ // event during move operations, since the final location is really a
+ // delta from this point. note: if we want to DRY this with
+ // EventContainerWrapper, probably better just to capture the mouseDown
+ // point here and do the placement computation in handleMove()...
+ this.eventOffsetLeft = point.x - getBoundsForNode(eventNode).left
return (
action === 'move' ||
- (action === 'resize' &&
- (!isAllDay || pointInBox(getBoundsForNode(node), point)))
+ (action === 'resize' && (!isAllDay || pointInBox(eventNode, point)))
)
})
selector.on('selecting', box => {
const bounds = getBoundsForNode(node)
const { dragAndDropAction } = this.context.draggable
-
if (dragAndDropAction.action === 'move') this.handleMove(box, bounds)
if (dragAndDropAction.action === 'resize') this.handleResize(box, bounds)
})
selector.on('selectStart', () => this.context.draggable.onStart())
+
selector.on('select', point => {
const bounds = getBoundsForNode(node)
-
if (!this.state.segment) return
-
if (!pointInBox(bounds, point)) {
this.reset()
} else {
@@ -221,17 +203,13 @@ class WeekWrapper extends React.Component {
selector.on('dropFromOutside', point => {
if (!this.context.draggable.onDropFromOutside) return
-
const bounds = getBoundsForNode(node)
-
if (!pointInBox(bounds, point)) return
-
this.handleDropFromOutside(point, bounds)
})
selector.on('dragOverFromOutside', point => {
if (!this.context.draggable.dragFromOutsideItem) return
-
const bounds = getBoundsForNode(node)
this.handleDragOverFromOutside(point, bounds)
@@ -271,7 +249,7 @@ class WeekWrapper extends React.Component {
let { segment } = this.state
return (
-
+
{children}
{segment && (
@@ -291,6 +269,4 @@ class WeekWrapper extends React.Component {
}
}
-WeekWrapper.propTypes = propTypes
-
export default WeekWrapper
diff --git a/src/addons/dragAndDrop/common.js b/src/addons/dragAndDrop/common.js
index 29f51264f..8e1e53320 100644
--- a/src/addons/dragAndDrop/common.js
+++ b/src/addons/dragAndDrop/common.js
@@ -1,12 +1,13 @@
import { wrapAccessor } from '../../utils/accessors'
import { createFactory } from 'react'
+import * as dates from '../../utils/dates'
export const dragAccessors = {
start: wrapAccessor(e => e.start),
end: wrapAccessor(e => e.end),
}
-export const nest = (...Components) => {
+function nest(...Components) {
const factories = Components.filter(Boolean).map(createFactory)
const Nest = ({ children, ...props }) =>
factories.reduceRight((child, factory) => factory(props, child), children)
@@ -14,12 +15,39 @@ export const nest = (...Components) => {
return Nest
}
-export const mergeComponents = (components = {}, addons) => {
+export function mergeComponents(components = {}, addons) {
const keys = Object.keys(addons)
const result = { ...components }
keys.forEach(key => {
- result[key] = components[key] ? nest(components[key], addons[key]) : addons[key]
+ result[key] = components[key]
+ ? nest(components[key], addons[key])
+ : addons[key]
})
return result
}
+
+export function pointInColumn(bounds, point) {
+ const { left, right, top } = bounds
+ const { x, y } = point
+ return x < right + 10 && x > left && y > top
+}
+
+/**
+ * Get start, end, allDay and duration of an event using the provided accessors.
+ * Fixes up problematic case of malformed allDay events (those missing
+ * allDay=true or allDay events where the end date isn't exclusive)
+ */
+export function eventTimes(event, accessors) {
+ const start = accessors.start(event)
+ let end = accessors.end(event)
+ let allDay = accessors.allDay(event)
+ const duration = dates.diff(start, end, 'milliseconds')
+
+ // make zero duration midnight events at least one day long
+ if (duration === 0 && start.getMinutes() === 0) {
+ end = dates.add(end, 1, 'day')
+ allDay = true
+ }
+ return { start, end, allDay, duration }
+}