diff --git a/examples/App.js b/examples/App.js
index 5295c2c75..8e33532c6 100644
--- a/examples/App.js
+++ b/examples/App.js
@@ -25,6 +25,7 @@ import Resource from './demos/resource'
import DndResource from './demos/dndresource'
import Timeslots from './demos/timeslots'
import Dnd from './demos/dnd'
+import DndOutsideSource from './demos/dndOutsideSource'
import Dropdown from 'react-bootstrap/lib/Dropdown'
import MenuItem from 'react-bootstrap/lib/MenuItem'
@@ -43,6 +44,7 @@ const EXAMPLES = {
customView: 'Custom Calendar Views',
resource: 'Resource Scheduling',
dnd: 'Addon: Drag and drop',
+ dndOutsideSource: 'Addon: Drag and drop (from outside calendar)',
}
const DEFAULT_EXAMPLE = 'basic'
@@ -78,6 +80,7 @@ class Example extends React.Component {
timeslots: Timeslots,
dnd: Dnd,
dndresource: DndResource,
+ dndOutsideSource: DndOutsideSource,
}[selected]
return (
diff --git a/examples/demos/dndOutsideSource.js b/examples/demos/dndOutsideSource.js
new file mode 100644
index 000000000..296aa13ef
--- /dev/null
+++ b/examples/demos/dndOutsideSource.js
@@ -0,0 +1,174 @@
+import React from 'react'
+import events from '../events'
+import BigCalendar from 'react-big-calendar'
+import withDragAndDrop from 'react-big-calendar/lib/addons/dragAndDrop'
+import Layout from 'react-tackle-box/Layout'
+import Card from '../Card'
+
+import 'react-big-calendar/lib/addons/dragAndDrop/styles.less'
+
+const DragAndDropCalendar = withDragAndDrop(BigCalendar)
+
+const formatName = (name, count) => `${name} ID ${count}`
+
+class Dnd extends React.Component {
+ constructor(props) {
+ super(props)
+ this.state = {
+ events: events,
+ draggedEvent: null,
+ counters: {
+ item1: 0,
+ item2: 0,
+ },
+ }
+ }
+
+ handleDragStart = name => {
+ this.setState({ draggedEvent: name })
+ }
+
+ customOnDragOver = event => {
+ // check for undroppable is specific to this example
+ // and not part of API. This just demonstrates that
+ // onDragOver can optionally be passed to conditionally
+ // allow draggable items to be dropped on cal, based on
+ // whether event.preventDefault is called
+ if (this.state.draggedEvent !== 'undroppable') {
+ console.log('preventDefault')
+ event.preventDefault()
+ }
+ }
+
+ onDropFromOutside = ({ start, end, allDay }) => {
+ const { draggedEvent, counters } = this.state
+ const event = {
+ title: formatName(draggedEvent, counters[draggedEvent]),
+ start,
+ end,
+ isAllDay: allDay,
+ }
+ const updatedCounters = {
+ ...counters,
+ [draggedEvent]: counters[draggedEvent] + 1,
+ }
+ this.setState({ draggedEvent: null, counters: updatedCounters })
+ this.newEvent(event)
+ }
+
+ moveEvent({ event, start, end, isAllDay: droppedOnAllDaySlot }) {
+ const { events } = this.state
+
+ const idx = events.indexOf(event)
+ let allDay = event.allDay
+
+ if (!event.allDay && droppedOnAllDaySlot) {
+ allDay = true
+ } else if (event.allDay && !droppedOnAllDaySlot) {
+ allDay = false
+ }
+
+ const updatedEvent = { ...event, start, end, allDay }
+
+ const nextEvents = [...events]
+ nextEvents.splice(idx, 1, updatedEvent)
+
+ this.setState({
+ events: nextEvents,
+ })
+
+ // alert(`${event.title} was dropped onto ${updatedEvent.start}`)
+ }
+
+ resizeEvent = ({ event, start, end }) => {
+ const { events } = this.state
+
+ const nextEvents = events.map(existingEvent => {
+ return existingEvent.id == event.id
+ ? { ...existingEvent, start, end }
+ : existingEvent
+ })
+
+ this.setState({
+ events: nextEvents,
+ })
+
+ //alert(`${event.title} was resized to ${start}-${end}`)
+ }
+
+ newEvent(event) {
+ let idList = this.state.events.map(a => a.id)
+ let newId = Math.max(...idList) + 1
+ let hour = {
+ id: newId,
+ title: event.title,
+ allDay: event.isAllDay,
+ start: event.start,
+ end: event.end,
+ }
+ this.setState({
+ events: this.state.events.concat([hour]),
+ })
+ }
+
+ render() {
+ return (
+
+
+ Outside Drag Sources
+ {Object.entries(this.state.counters).map(([name, count]) => (
+ this.handleDragStart(name)}
+ >
+ {formatName(name, count)}
+
+ ))}
+ this.handleDragStart('undroppable')}
+ >
+ Draggable but not for calendar.
+
+
+
+
+ )
+ }
+}
+
+export default Dnd
diff --git a/src/Selection.js b/src/Selection.js
index 4f7569938..f33c1feec 100644
--- a/src/Selection.js
+++ b/src/Selection.js
@@ -54,6 +54,7 @@ class Selection {
this._handleMoveEvent = this._handleMoveEvent.bind(this)
this._handleTerminatingEvent = this._handleTerminatingEvent.bind(this)
this._keyListener = this._keyListener.bind(this)
+ this._dropFromOutsideListener = this._dropFromOutsideListener.bind(this)
// Fixes an iOS 10 bug where scrolling could not be prevented on the window.
// https://github.com/metafizzy/flickity/issues/457#issuecomment-254501356
@@ -64,6 +65,10 @@ class Selection {
)
this._onKeyDownListener = addEventListener('keydown', this._keyListener)
this._onKeyUpListener = addEventListener('keyup', this._keyListener)
+ this._onDropFromOutsideListener = addEventListener(
+ 'drop',
+ this._dropFromOutsideListener
+ )
this._addInitialEventListener()
}
@@ -187,6 +192,19 @@ class Selection {
}
}
+ _dropFromOutsideListener(e) {
+ const { pageX, pageY, clientX, clientY } = getEventCoordinates(e)
+
+ this.emit('dropFromOutside', {
+ x: pageX,
+ y: pageY,
+ clientX: clientX,
+ clientY: clientY,
+ })
+
+ e.preventDefault()
+ }
+
_handleInitialEvent(e) {
const { clientX, clientY, pageX, pageY } = getEventCoordinates(e)
let node = this.container(),
diff --git a/src/addons/dragAndDrop/EventContainerWrapper.js b/src/addons/dragAndDrop/EventContainerWrapper.js
index d2cfec621..642d36d5e 100644
--- a/src/addons/dragAndDrop/EventContainerWrapper.js
+++ b/src/addons/dragAndDrop/EventContainerWrapper.js
@@ -31,6 +31,7 @@ class EventContainerWrapper extends React.Component {
draggable: PropTypes.shape({
onStart: PropTypes.func,
onEnd: PropTypes.func,
+ onDropFromOutside: PropTypes.func,
onBeginAction: PropTypes.func,
dragAndDropAction: PropTypes.object,
}),
@@ -113,6 +114,21 @@ class EventContainerWrapper extends React.Component {
this.update(event, slotMetrics.getRange(start, end))
}
+ handleDropFromOutside = (point, boundaryBox) => {
+ const { slotMetrics } = this.props
+
+ let start = slotMetrics.closestSlotFromPoint(
+ { y: point.y, x: point.x },
+ boundaryBox
+ )
+
+ this.context.draggable.onDropFromOutside({
+ start,
+ end: slotMetrics.nextSlot(start),
+ allDay: false,
+ })
+ }
+
_selectable = () => {
let node = findDOMNode(this)
let selector = (this._selector = new Selection(() =>
@@ -141,6 +157,16 @@ class EventContainerWrapper extends React.Component {
if (dragAndDropAction.action === 'resize') this.handleResize(box, bounds)
})
+ 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('selectStart', () => this.context.draggable.onStart())
selector.on('select', point => {
diff --git a/src/addons/dragAndDrop/WeekWrapper.js b/src/addons/dragAndDrop/WeekWrapper.js
index 8870204fa..db19203e7 100644
--- a/src/addons/dragAndDrop/WeekWrapper.js
+++ b/src/addons/dragAndDrop/WeekWrapper.js
@@ -37,6 +37,7 @@ class WeekWrapper extends React.Component {
onStart: PropTypes.func,
onEnd: PropTypes.func,
dragAndDropAction: PropTypes.object,
+ onDropFromOutside: PropTypes.func,
onBeginAction: PropTypes.func,
}),
}
@@ -106,6 +107,21 @@ class WeekWrapper extends React.Component {
this.update(event, start, end)
}
+ handleDropFromOutside = (point, rowBox) => {
+ if (!this.context.draggable.onDropFromOutside) return
+ const { slotMetrics: metrics } = this.props
+
+ let start = metrics.getDateForSlot(
+ getSlotAtX(rowBox, point.x, false, metrics.slots)
+ )
+
+ this.context.draggable.onDropFromOutside({
+ start,
+ end: dates.add(start, 1, 'day'),
+ allDay: false,
+ })
+ }
+
handleResize(point, node) {
const { event, direction } = this.context.draggable.dragAndDropAction
const { accessors, slotMetrics: metrics } = this.props
@@ -193,6 +209,17 @@ class WeekWrapper extends React.Component {
if (!this.state.segment || !pointInBox(bounds, point)) return
this.handleInteractionEnd()
})
+
+ 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('click', () => this.context.draggable.onEnd(null))
}
diff --git a/src/addons/dragAndDrop/withDragAndDrop.js b/src/addons/dragAndDrop/withDragAndDrop.js
index a192f6110..7472ba3cc 100644
--- a/src/addons/dragAndDrop/withDragAndDrop.js
+++ b/src/addons/dragAndDrop/withDragAndDrop.js
@@ -23,8 +23,8 @@ import { mergeComponents } from './common'
*
* Set `resizable` to true in your calendar if you want events to be resizable.
*
- * The HOC adds `onEventDrop`, `onEventResize`, `onDragStart` callback properties if the events are
- * moved or resized. They are called with these signatures:
+ * The HOC adds `onEventDrop`, `onEventResize`, and `onDragStart` callback properties if the events are
+ * moved or resized. These callbacks are called with these signatures:
*
* ```js
* function onEventDrop({ event, start, end, allDay }) {...}
@@ -46,6 +46,23 @@ import { mergeComponents } from './common'
* If you care about these corner cases, you can examine the `allDay` param suppled
* in the callback to determine how the user dropped or resized the event.
*
+ * Additionally, this HOC adds the callback props `onDropFromOutside` and `onDragOver`.
+ * By default, the calendar will not respond to outside draggable items being dropped
+ * onto it. However, if `onDropFromOutside` callback is passed, then when draggable
+ * DOM elements are dropped on the calendar, the callback will fire, receiving an
+ * object with start and end times, and an allDay boolean.
+ *
+ * If `onDropFromOutside` is passed, but `onDragOver` is not, any draggable event will be
+ * droppable onto the calendar by default. On the other hand, if an `onDragOver` callback
+ * *is* passed, then it can discriminate as to whether a draggable item is droppable on the
+ * calendar. To designate a draggable item as droppable, call `event.preventDefault`
+ * inside `onDragOver`. If `event.preventDefault` is not called in the `onDragOver`
+ * callback, then the draggable item will not be droppable on the calendar.
+ *
+ * * ```js
+ * function onDropFromOutside({ start, end, allDay }) {...}
+ * function onDragOver(DragEvent: event) {...}
+ * ```
* @param {*} Calendar
* @param {*} backend
*/
@@ -55,6 +72,7 @@ export default function withDragAndDrop(Calendar) {
onEventDrop: PropTypes.func,
onEventResize: PropTypes.func,
onDragStart: PropTypes.func,
+ onDragOver: PropTypes.func,
draggableAccessor: accessor,
resizableAccessor: accessor,
@@ -82,6 +100,7 @@ export default function withDragAndDrop(Calendar) {
onStart: PropTypes.func,
onEnd: PropTypes.func,
onBeginAction: PropTypes.func,
+ onDropFromOutside: PropTypes.fun,
draggableAccessor: accessor,
resizableAccessor: accessor,
dragAndDropAction: PropTypes.object,
@@ -108,6 +127,7 @@ export default function withDragAndDrop(Calendar) {
onStart: this.handleInteractionStart,
onEnd: this.handleInteractionEnd,
onBeginAction: this.handleBeginAction,
+ onDropFromOutside: this.props.onDropFromOutside,
draggableAccessor: this.props.draggableAccessor,
resizableAccessor: this.props.resizableAccessor,
dragAndDropAction: this.state,
@@ -115,6 +135,10 @@ export default function withDragAndDrop(Calendar) {
}
}
+ defaultOnDragOver = event => {
+ event.preventDefault()
+ }
+
handleBeginAction = (event, action, direction) => {
const { onDragStart } = this.props
this.setState({ event, action, direction })
@@ -147,20 +171,33 @@ export default function withDragAndDrop(Calendar) {
}
render() {
- const { selectable, ...props } = this.props
+ const { selectable, elementProps, ...props } = this.props
const { interacting } = this.state
delete props.onEventDrop
delete props.onEventResize
props.selectable = selectable ? 'ignoreEvents' : false
+ const elementPropsWithDropFromOutside = this.props.onDropFromOutside
+ ? {
+ ...elementProps,
+ onDragOver: this.props.onDragOver || this.defaultOnDragOver,
+ }
+ : elementProps
+
props.className = cn(
props.className,
'rbc-addons-dnd',
!!interacting && 'rbc-addons-dnd-is-dragging'
)
- return
+ return (
+
+ )
}
}