Adam Shaw 7 лет назад
Родитель
Сommit
ee49fb346c

+ 7 - 31
src/Calendar.ts

@@ -22,6 +22,7 @@ import { parseDateSpan, DateSpanInput, DateSpan } from './reducers/date-span'
 import reselector from './util/reselector'
 import { assignTo } from './util/object'
 import { RenderForceFlags } from './component/Component'
+import browserContext from './common/browser-context'
 
 
 export default class Calendar {
@@ -915,46 +916,21 @@ export default class Calendar {
     }
 
     let selection = parseDateSpan(selectionInput, this.dateEnv)
-    if (selection) {
+    if (selection) { // throw parse error otherwise?
       this.dispatch({
         type: 'SELECT',
         selection: selection
       })
-      this.triggerSelect(selection, this.view)
-    } // otherwise, throw error?
+      browserContext.reportDateSelection(this, selection)
+    }
   }
 
 
   // public method
   unselect(ev?: UIEvent) {
-    this.dispatch({
-      type: 'UNSELECT'
-    })
-    this.triggerUnselect(this.view, ev)
-  }
-
-
-  // Triggers handlers to 'select'
-  triggerSelect(selection: DateSpan, view: View, ev?: UIEvent) {
-    this.publiclyTrigger('select', [
-      {
-        start: this.dateEnv.toDate(selection.range.start),
-        end: this.dateEnv.toDate(selection.range.end),
-        isAllDay: selection.isAllDay,
-        jsEvent: ev,
-        view
-      }
-    ])
-  }
-
-
-  triggerUnselect(view, ev?: UIEvent) {
-    this.publiclyTrigger('unselect', [
-      {
-        jsEvent: ev,
-        view
-      }
-    ])
+    if (browserContext.dateSelectedCalendar === this) {
+      browserContext.unselectDates()
+    }
   }
 
 

+ 6 - 3
src/agenda/AgendaView.ts

@@ -448,16 +448,19 @@ function splitInteractionState(state: EventInteractionState) {
   let timed = null
 
   if (state) {
-    let eventStoreGroups = splitEventStore(state.eventStore)
+    let affectedEventGroups = splitEventStore(state.affectedEvents)
+    let mutatedEventGroups = splitEventStore(state.mutatedEvents)
 
     allDay = {
-      eventStore: eventStoreGroups.allDay,
+      affectedEvents: affectedEventGroups.allDay,
+      mutatedEvents: mutatedEventGroups.allDay,
       origSeg: state.origSeg,
       willCreateEvent: state.willCreateEvent
     }
 
     timed = {
-      eventStore: eventStoreGroups.timed,
+      affectedEvents: affectedEventGroups.timed,
+      mutatedEvents: mutatedEventGroups.timed,
       origSeg: state.origSeg,
       willCreateEvent: state.willCreateEvent
     }

+ 31 - 13
src/common/browser-context.ts

@@ -7,6 +7,7 @@ import EventHovering from '../interactions/EventHovering'
 import EventDragging from '../interactions/EventDragging'
 import EventResizing from '../interactions/EventResizing'
 import { DateSpan } from '../reducers/date-span'
+import Calendar from '../Calendar'
 
 export class BrowserContext {
 
@@ -14,7 +15,7 @@ export class BrowserContext {
   componentCnt: number = 0
   componentHash = {}
   listenerHash = {}
-  dateSelectedComponent: DateComponent
+  dateSelectedCalendar: Calendar
   eventSelectedComponent: DateComponent
 
   registerComponent(component: DateComponent) {
@@ -82,23 +83,40 @@ export class BrowserContext {
     }
   }
 
-  unselectDates(ev: PointerDragEvent) {
-    if (this.dateSelectedComponent) {
-      this.dateSelectedComponent.getCalendar().unselect(ev.origEvent) // TODO: send pev?
-      this.dateSelectedComponent = null
+  unselectDates(pev?: PointerDragEvent) {
+    let { dateSelectedCalendar } = this
+
+    if (dateSelectedCalendar) {
+
+      dateSelectedCalendar.dispatch({
+        type: 'UNSELECT'
+      })
+
+      dateSelectedCalendar.publiclyTrigger('unselect', [
+        {
+          jsEvent: pev ? pev.origEvent : null,
+          view: dateSelectedCalendar.view
+        }
+      ])
+
+      this.dateSelectedCalendar = null
     }
   }
 
-  reportDateSelection(component: DateComponent, selection: DateSpan, ev: PointerDragEvent) {
-    this.unselectDates(ev)
+  reportDateSelection(calendar: Calendar, selection: DateSpan, pev?: PointerDragEvent) {
+    this.unselectDates(pev)
 
-    component.getCalendar().triggerSelect(
-      selection,
-      component.view,
-      ev.origEvent // TODO: send pev?
-    )
+    calendar.publiclyTrigger('select', [
+      {
+        start: calendar.dateEnv.toDate(selection.range.start),
+        end: calendar.dateEnv.toDate(selection.range.end),
+        isAllDay: selection.isAllDay,
+        jsEvent: pev ? pev.origEvent : null,
+        view: calendar.view
+      }
+    ])
 
-    this.dateSelectedComponent = component
+    this.dateSelectedCalendar = calendar
   }
 
   unselectEvent() {

+ 17 - 33
src/component/DateComponent.ts

@@ -578,17 +578,13 @@ export default abstract class DateComponent extends Component {
 
 
   renderDragState(dragState: EventInteractionState) {
-    if (dragState.origSeg) {
-      this.hideRelatedSegs(dragState.origSeg)
-    }
-    this.renderDrag(dragState.eventStore, dragState.origSeg, dragState.willCreateEvent)
+    this.hideSegsByHash(dragState.affectedEvents.instances)
+    this.renderDrag(dragState.mutatedEvents, dragState.origSeg, dragState.willCreateEvent)
   }
 
 
   unrenderDragState() {
-    if (this.dragState.origSeg) {
-      this.showRelatedSegs(this.dragState.origSeg)
-    }
+    this.showSegsByHash(this.dragState.affectedEvents.instances)
     this.unrenderDrag()
   }
 
@@ -612,17 +608,13 @@ export default abstract class DateComponent extends Component {
 
 
   renderEventResizeState(eventResizeState: EventInteractionState) {
-    if (eventResizeState.origSeg) {
-      this.hideRelatedSegs(eventResizeState.origSeg)
-    }
-    this.renderEventResize(eventResizeState.eventStore, eventResizeState.origSeg)
+    this.hideSegsByHash(eventResizeState.affectedEvents.instances)
+    this.renderEventResize(eventResizeState.mutatedEvents, eventResizeState.origSeg)
   }
 
 
   unrenderEventResizeState() {
-    if (this.eventResizeState.origSeg) {
-      this.showRelatedSegs(this.eventResizeState.origSeg)
-    }
+    this.showSegsByHash(this.eventResizeState.affectedEvents.instances)
     this.unrenderEventResize()
   }
 
@@ -643,33 +635,25 @@ export default abstract class DateComponent extends Component {
   // -----------------------------------------------------------------------------------------------------------------
 
 
-  hideRelatedSegs(targetSeg: Seg) {
-    this.getRelatedSegs(targetSeg).forEach(function(seg) {
-      seg.el.style.visibility = 'hidden'
-    })
-  }
-
-
-  showRelatedSegs(targetSeg: Seg) {
-    this.getRelatedSegs(targetSeg).forEach(function(seg) {
-      seg.el.style.visibility = ''
+  hideSegsByHash(hash) {
+    this.getAllEventSegs().forEach(function(seg) {
+      if (hash[seg.eventRange.eventInstance.instanceId]) {
+        seg.el.style.visibility = 'hidden'
+      }
     })
   }
 
 
-  getRelatedSegs(targetSeg: Seg) {
-    let targetEventDef = targetSeg.eventRange.eventDef
-
-    return this.getAllEventSegs().filter(function(seg: Seg) {
-      let segEventDef = seg.eventRange.eventDef
-
-      return segEventDef.defId === targetEventDef.defId || // include defId as well???
-        segEventDef.groupId && segEventDef.groupId === targetEventDef.groupId
+  showSegsByHash(hash) {
+    this.getAllEventSegs().forEach(function(seg) {
+      if (hash[seg.eventRange.eventInstance.instanceId]) {
+        seg.el.style.visibility = ''
+      }
     })
   }
 
 
-  getAllEventSegs() {
+  getAllEventSegs(): Seg[] {
     if (this.eventRenderer) {
       return this.eventRenderer.getSegs()
     } else {

+ 60 - 55
src/interactions-external/ExternalElementDragging.ts

@@ -9,10 +9,12 @@ import { createDuration } from '../datelib/duration'
 import { assignTo } from '../util/object'
 import { DateSpan } from '../reducers/date-span'
 import Calendar from '../Calendar'
+import { EventInteractionState } from '../reducers/event-interaction'
 
 export default class ExternalElementDragging {
 
   hitDragging: HitDragging
+  receivingCalendar: Calendar
   addableEventStore: EventStore
   explicitEventCreationData: any
   eventCreationData: any
@@ -21,12 +23,9 @@ export default class ExternalElementDragging {
     let hitDragging = this.hitDragging = new HitDragging(dragging, browserContext.componentHash)
     hitDragging.requireInitial = false
     hitDragging.emitter.on('dragstart', this.onDragStart)
-    hitDragging.emitter.on('hitover', this.onHitOver)
-    hitDragging.emitter.on('hitout', this.onHitOut)
+    hitDragging.emitter.on('hitchange', this.onHitChange)
     hitDragging.emitter.on('dragend', this.onDragEnd)
 
-    dragging.setMirrorIsVisible(true)
-
     this.explicitEventCreationData = rawEventCreationData ? processExplicitData(rawEventCreationData) : null
   }
 
@@ -35,70 +34,54 @@ export default class ExternalElementDragging {
     this.eventCreationData = this.explicitEventCreationData || getDraggedElMeta(ev.subjectEl)
   }
 
-  onHitOver = (hit: Hit) => {
-    let calendar = hit.component.getCalendar()
-
-    this.addableEventStore = computeEventStoreForDateSpan(
-      hit.dateSpan,
-      hit.component.getCalendar(),
-      this.eventCreationData
-    )
-
-    calendar.dispatch({
-      type: 'SET_DRAG',
-      dragState: {
-        eventStore: this.addableEventStore,
-        willCreateEvent: Boolean(this.eventCreationData.standardProps)
-      }
-    })
-
+  onHitChange = (hit: Hit | null, isFinal: boolean, ev: PointerDragEvent) => {
     let { dragging } = this.hitDragging
+    let receivingCalendar: Calendar = null
+    let addableEventStore: EventStore = null
+
+    if (hit) {
+      receivingCalendar = hit.component.getCalendar()
+      addableEventStore = computeEventStoreForDateSpan(
+        hit.dateSpan,
+        receivingCalendar,
+        this.eventCreationData
+      )
+    }
 
-    dragging.setMirrorNeedsRevert(false)
+    this.renderDrag(receivingCalendar, {
+      affectedEvents: { defs: {}, instances: {} },
+      mutatedEvents: addableEventStore || { defs: {}, instances: {} }, // TODO: better way to make empty event-store
+      willCreateEvent: Boolean(this.eventCreationData.standardProps)
+    })
 
-    // show mirror if no already-rendered helper element
+    // show mirror if no already-rendered helper element OR if we are shutting down the mirror
     // TODO: wish we could somehow wait for dispatch to guarantee render
     dragging.setMirrorIsVisible(
-      !document.querySelector('.fc-helper')
+      isFinal || !addableEventStore || !document.querySelector('.fc-helper')
     )
-  }
-
-  onHitOut = (hit) => { // TODO: onHitChange?
-
-    // we still want to notify calendar about invalid drag
-    // because we want related events to stay hidden
-    hit.component.getCalendar().dispatch({
-      type: 'SET_DRAG',
-      dragState: {
-        eventStore: { defs: {}, instances: {} } // TODO: better way to make empty event-store
-      }
-    })
-
-    this.addableEventStore = null
 
-    let { dragging } = this.hitDragging
+    if (!isFinal) {
+      dragging.setMirrorNeedsRevert(!addableEventStore)
 
-    dragging.setMirrorIsVisible(true)
-    dragging.setMirrorNeedsRevert(true)
+      this.receivingCalendar = receivingCalendar
+      this.addableEventStore = addableEventStore
+    }
   }
 
   onDragEnd = (pev: PointerDragEvent) => {
-    this.hitDragging.dragging.setMirrorIsVisible(true) // always restore!
+    let { receivingCalendar, addableEventStore } = this
 
-    if (this.addableEventStore) {
+    this.unrenderDrag()
+
+    if (receivingCalendar && addableEventStore) {
       let finalHit = this.hitDragging.finalHit
       let finalView = finalHit.component.view
-      let finalCalendar = finalView.calendar
-
-      finalCalendar.dispatch({
-        type: 'CLEAR_DRAG'
-      })
 
       // TODO: how to let Scheduler extend this?
-      finalCalendar.publiclyTrigger('drop', [
+      receivingCalendar.publiclyTrigger('drop', [
         {
           draggedEl: pev.subjectEl,
-          date: finalCalendar.dateEnv.toDate(finalHit.dateSpan.range.start),
+          date: receivingCalendar.dateEnv.toDate(finalHit.dateSpan.range.start),
           isAllDay: finalHit.dateSpan.isAllDay,
           jsEvent: pev.origEvent,
           view: finalView
@@ -106,23 +89,45 @@ export default class ExternalElementDragging {
       ])
 
       if (this.eventCreationData.standardProps) { // TODO: bad way to test if event creation is good
-        finalCalendar.dispatch({
+        receivingCalendar.dispatch({
           type: 'ADD_EVENTS',
-          eventStore: this.addableEventStore,
+          eventStore: addableEventStore,
           stick: this.eventCreationData.stick // TODO: use this param
         })
 
         // signal an external event landed
-        finalCalendar.publiclyTrigger('eventReceive', [
+        receivingCalendar.publiclyTrigger('eventReceive', [
           {
             draggedEl: pev.subjectEl,
-            event: this.addableEventStore, // TODO: what to put here!?
+            event: addableEventStore, // TODO: what to put here!?
             view: finalView
           }
         ])
       }
+    }
+
+    this.receivingCalendar = null
+    this.addableEventStore = null
+  }
+
+  renderDrag(newReceivingCalendar: Calendar | null, dragState: EventInteractionState) {
+    let prevReceivingCalendar = this.receivingCalendar
+
+    if (prevReceivingCalendar && prevReceivingCalendar !== newReceivingCalendar) {
+      prevReceivingCalendar.dispatch({ type: 'CLEAR_DRAG' })
+    }
+
+    if (newReceivingCalendar) {
+      newReceivingCalendar.dispatch({
+        type: 'SET_DRAG',
+        dragState: dragState
+      })
+    }
+  }
 
-      this.addableEventStore = null
+  unrenderDrag() {
+    if (this.receivingCalendar) {
+      this.receivingCalendar.dispatch({ type: 'CLEAR_DRAG' })
     }
   }
 

+ 20 - 27
src/interactions/DateSelecting.ts

@@ -24,8 +24,7 @@ export default class DateSelecting {
     let hitDragging = this.hitDragging = new HitDragging(this.dragging, component)
     hitDragging.emitter.on('pointerdown', this.onPointerDown)
     hitDragging.emitter.on('dragstart', this.onDragStart)
-    hitDragging.emitter.on('hitover', this.onHitOver)
-    hitDragging.emitter.on('hitout', this.onHitOut)
+    hitDragging.emitter.on('hitchange', this.onHitChange)
   }
 
   destroy() {
@@ -49,31 +48,31 @@ export default class DateSelecting {
     browserContext.unselectDates(ev)
   }
 
-  onHitOver = (overHit: Hit) => { // TODO: do a onHitChange instead?
+  onHitChange = (hit: Hit | null, isFinal: boolean) => {
     let calendar = this.component.getCalendar()
-    let dragSelection = computeSelection(
-      this.hitDragging.initialHit.dateSpan,
-      overHit.dateSpan
-    )
+    let dragSelection: DateSpan = null
 
-    if (dragSelection) {
-      this.dragSelection = dragSelection
+    if (hit) {
+      dragSelection = computeSelection(
+        this.hitDragging.initialHit.dateSpan,
+        hit.dateSpan
+      )
+    }
 
+    if (dragSelection) {
       calendar.dispatch({
         type: 'SELECT',
         selection: dragSelection
       })
+    } else if (!isFinal) { // only unselect if moved away while dragging
+      calendar.dispatch({
+        type: 'UNSELECT'
+      })
     }
-  }
 
-  onHitOut = (hit: DateSpan, ev) => {
-    let calendar = this.component.getCalendar()
-
-    this.dragSelection = null
-
-    calendar.dispatch({
-      type: 'UNSELECT'
-    })
+    if (!isFinal) {
+      this.dragSelection = dragSelection
+    }
   }
 
   onDocumentPointerUp = (ev: PointerDragEvent, wasTouchScroll: boolean, downEl: HTMLElement) => {
@@ -82,20 +81,16 @@ export default class DateSelecting {
     if (this.dragSelection) {
 
       // the selection is already rendered, so just need to report it
-      browserContext.reportDateSelection(component, this.dragSelection, ev)
+      browserContext.reportDateSelection(component.getCalendar(), this.dragSelection, ev)
 
       this.dragSelection = null
 
-    } else if (!wasTouchScroll && component.selection) {
-      // if there was a pointerup that did not result in a selection and was
-      // not merely a touchmove-scroll, then possibly unselect the current selection.
-      // won't do anything if already unselected (OR, leverage selectedCalendar?)
-
+    } else if (!wasTouchScroll && component.selection) { // only unselect if this component has a selection
       let unselectAuto = component.opt('unselectAuto')
       let unselectCancel = component.opt('unselectCancel')
 
       if (unselectAuto && (!unselectAuto || !elementClosest(downEl, unselectCancel))) {
-        component.getCalendar().unselect()
+        browserContext.unselectDates(ev)
       }
     }
   }
@@ -125,5 +120,3 @@ function computeSelection(dateSpan0: DateSpan, dateSpan1: DateSpan): DateSpan {
     isAllDay: dateSpan0.isAllDay
   }
 }
-
-// TODO: isSelectionFootprintAllowed

+ 89 - 62
src/interactions/EventDragging.ts

@@ -6,6 +6,9 @@ import browserContext from '../common/browser-context'
 import { startOfDay } from '../datelib/marker'
 import { elementClosest } from '../util/dom-manip'
 import FeaturefulElementDragging from '../dnd/FeaturefulElementDragging'
+import { EventStore } from '../reducers/event-store'
+import Calendar from '../Calendar'
+import { EventInteractionState } from '../reducers/event-interaction'
 
 export default class EventDragging {
 
@@ -13,7 +16,8 @@ export default class EventDragging {
   dragging: FeaturefulElementDragging
   hitDragging: HitDragging
   draggingSeg: Seg
-  mutation: EventMutation
+  receivingCalendar: Calendar
+  validMutation: EventMutation
 
   constructor(component: DateComponent) {
     this.component = component
@@ -26,8 +30,7 @@ export default class EventDragging {
     hitDragging.useSubjectCenter = true
     hitDragging.emitter.on('pointerdown', this.onPointerDown)
     hitDragging.emitter.on('dragstart', this.onDragStart)
-    hitDragging.emitter.on('hitover', this.onHitOver)
-    hitDragging.emitter.on('hitout', this.onHitOut)
+    hitDragging.emitter.on('hitchange', this.onHitChange)
     hitDragging.emitter.on('dragend', this.onDragEnd)
   }
 
@@ -63,7 +66,6 @@ export default class EventDragging {
   }
 
   onDragStart = (ev: PointerDragEvent) => {
-    browserContext.unselectEvent()
     this.draggingSeg = (ev.subjectEl as any).fcSeg
 
     if (ev.isTouch) {
@@ -74,99 +76,124 @@ export default class EventDragging {
         eventInstanceId
       })
 
-      browserContext.reportEventSelection(this.component)
+      browserContext.reportEventSelection(this.component) // will unselect previous
+    } else {
+      browserContext.unselectEvent()
     }
   }
 
-  onHitOver = (hit) => {
-    let { initialHit } = this.hitDragging
-    let calendar = hit.component.getCalendar()
+  onHitChange = (hit: Hit | null, isFinal: boolean) => {
+    let { initialHit } = this.hitDragging // guaranteed
+    let initialCalendar = initialHit.component.getCalendar()
+    let receivingCalendar: Calendar = null
+    let validMutation: EventMutation = null
+    let relatedEvents: EventStore = null
+    let mutatedRelatedEvents: EventStore = null
 
-    let mutation = computeEventMutation(initialHit, hit)
-    let related = getRelatedEvents(
-      calendar.state.eventStore,
+    relatedEvents = getRelatedEvents( // TODO: compute this only once?
+      initialCalendar.state.eventStore,
       this.draggingSeg.eventRange.eventInstance.instanceId
     )
-    let mutatedRelated = applyMutationToAll(related, mutation, calendar)
 
-    calendar.dispatch({
-      type: 'SET_DRAG',
-      dragState: {
-        eventStore: mutatedRelated,
-        origSeg: this.draggingSeg
-      }
-    })
+    if (hit) {
+      receivingCalendar = hit.component.getCalendar()
 
-    let { dragging } = this
+      if (!isHitsEqual(initialHit, hit)) {
+        validMutation = computeEventMutation(initialHit, hit)
 
-    // render the mirror if no already-rendered helper
-    // TODO: wish we could somehow wait for dispatch to guarantee render
-    dragging.setMirrorIsVisible(
-      !document.querySelector('.fc-helper')
-    )
-
-    let isSame = isHitsEqual(initialHit, hit)
-    dragging.setMirrorNeedsRevert(isSame)
-
-    if (!isSame) {
-      this.mutation = mutation
+        if (validMutation) {
+          mutatedRelatedEvents = applyMutationToAll(relatedEvents, validMutation, receivingCalendar)
+        }
+      }
     }
-  }
 
-  onHitOut = (hit) => { // TODO: onHitChange?
-    this.mutation = null
-
-    // we still want to notify calendar about invalid drag
-    // because we want related events to stay hidden
-    hit.component.getCalendar().dispatch({
-      type: 'SET_DRAG',
-      dragState: {
-        eventStore: { defs: {}, instances: {} }, // TODO: better way to make empty event-store
-        origSeg: this.draggingSeg
-      }
+    this.renderDrag(receivingCalendar, {
+      affectedEvents: relatedEvents,
+      mutatedEvents: mutatedRelatedEvents || relatedEvents,
+      origSeg: this.draggingSeg
     })
 
-    let { dragging } = this
+    if (!isFinal) {
+      this.dragging.setMirrorNeedsRevert(!validMutation)
+
+      // render the mirror if no already-rendered helper
+      // TODO: wish we could somehow wait for dispatch to guarantee render
+      this.dragging.setMirrorIsVisible(
+        !hit || !document.querySelector('.fc-helper')
+      )
 
-    dragging.setMirrorIsVisible(true)
-    dragging.setMirrorNeedsRevert(true)
+      this.receivingCalendar = receivingCalendar
+      this.validMutation = validMutation
+    }
   }
 
   onDocumentPointerUp = (ev, wasTouchScroll) => {
     if (
-      !this.mutation &&
       !wasTouchScroll &&
+      !this.draggingSeg && // was never dragging
       // was the previously event-selected component?
       browserContext.eventSelectedComponent === this.component
     ) {
-      this.component.getCalendar().dispatch({
-        type: 'CLEAR_SELECTED_EVENT'
-      })
+      browserContext.unselectEvent()
     }
   }
 
   onDragEnd = () => {
-    let { initialHit } = this.hitDragging
-    let initialCalendar = initialHit.component.getCalendar()
+    this.unrenderDrag()
 
-    // TODO: what about across calendars?
-
-    initialCalendar.dispatch({
-      type: 'CLEAR_DRAG'
-    })
-
-    if (this.mutation) {
-      initialCalendar.dispatch({
+    if (this.validMutation) {
+      this.receivingCalendar.dispatch({
         type: 'MUTATE_EVENTS',
-        mutation: this.mutation,
+        mutation: this.validMutation,
         instanceId: this.draggingSeg.eventRange.eventInstance.instanceId
       })
     }
 
-    this.mutation = null
+    this.receivingCalendar = null
+    this.validMutation = null
     this.draggingSeg = null
   }
 
+  renderDrag(newReceivingCalendar: Calendar | null, dragState: EventInteractionState) {
+    let initialCalendar = this.hitDragging.initialHit.component.getCalendar()
+    let prevReceivingCalendar = this.receivingCalendar
+
+    if (prevReceivingCalendar && prevReceivingCalendar !== newReceivingCalendar) {
+      if (prevReceivingCalendar === initialCalendar) {
+        prevReceivingCalendar.dispatch({
+          type: 'SET_DRAG',
+          dragState: {
+            affectedEvents: dragState.affectedEvents,
+            mutatedEvents: { defs: {}, instances: {} }, // TODO: util
+            origSeg: dragState.origSeg
+          }
+        })
+      } else {
+        prevReceivingCalendar.dispatch({ type: 'CLEAR_DRAG' })
+      }
+    }
+
+    if (newReceivingCalendar) {
+      newReceivingCalendar.dispatch({
+        type: 'SET_DRAG',
+        dragState
+      })
+    }
+  }
+
+  unrenderDrag() {
+    let initialCalendar = this.hitDragging.initialHit.component.getCalendar()
+    let { receivingCalendar } = this
+
+    if (receivingCalendar) {
+      receivingCalendar.dispatch({ type: 'CLEAR_DRAG' })
+    }
+
+    if (initialCalendar !== receivingCalendar) {
+      initialCalendar.dispatch({ type: 'CLEAR_DRAG' })
+    }
+  }
+
 }
 
 function computeEventMutation(hit0: Hit, hit1: Hit): EventMutation {

+ 28 - 35
src/interactions/EventResizing.ts

@@ -24,8 +24,7 @@ export default class EventDragging {
     let hitDragging = this.hitDragging = new HitDragging(this.dragging, component)
     hitDragging.emitter.on('pointerdown', this.onPointerDown)
     hitDragging.emitter.on('dragstart', this.onDragStart)
-    hitDragging.emitter.on('hitover', this.onHitOver)
-    hitDragging.emitter.on('hitout', this.onHitOut)
+    hitDragging.emitter.on('hitchange', this.onHitChange)
     hitDragging.emitter.on('dragend', this.onDragEnd)
   }
 
@@ -48,67 +47,61 @@ export default class EventDragging {
     this.draggingSeg = this.querySeg(ev)
   }
 
-  onHitOver = (hit, ev: PointerDragEvent) => {
+  onHitChange = (hit: Hit | null, isFinal: boolean, ev: PointerDragEvent) => {
     let calendar = this.component.getCalendar()
     let { initialHit } = this.hitDragging
     let eventInstance = this.draggingSeg.eventRange.eventInstance
+    let mutation = null
+
+    if (hit) {
+      mutation = computeMutation(
+        initialHit,
+        hit,
+        (ev.subjectEl as HTMLElement).classList.contains('.fc-start-resizer'),
+        eventInstance.range
+      )
+    }
 
-    let mutation = computeMutation(
-      initialHit,
-      hit,
-      (ev.subjectEl as HTMLElement).classList.contains('.fc-start-resizer'),
-      eventInstance.range
-    )
-
-    if (!mutation) {
-      calendar.dispatch({
-        type: 'CLEAR_EVENT_RESIZE'
-      })
-    } else {
+    if (mutation) {
       let related = getRelatedEvents(calendar.state.eventStore, eventInstance.instanceId)
       let mutatedRelated = applyMutationToAll(related, mutation, calendar)
 
       calendar.dispatch({
         type: 'SET_EVENT_RESIZE',
         eventResizeState: {
-          eventStore: mutatedRelated,
-          origSeg: this.draggingSeg,
-          isTouch: ev.isTouch
+          affectedEvents: related,
+          mutatedEvents: mutatedRelated,
+          origSeg: this.draggingSeg
         }
       })
-
-      if (!isHitsEqual(initialHit, hit)) {
-        this.mutation = mutation
-      }
+    } else {
+      calendar.dispatch({
+        type: 'CLEAR_EVENT_RESIZE'
+      })
     }
-  }
-
-  onHitOut = (hit: Hit, ev) => {
-    let calendar = this.component.getCalendar()
 
-    calendar.dispatch({
-      type: 'CLEAR_EVENT_RESIZE'
-    })
+    if (mutation && isHitsEqual(initialHit, hit)) {
+      mutation = null
+    }
 
-    this.mutation = null
+    if (!isFinal) {
+      this.mutation = mutation
+    }
   }
 
   onDragEnd = (ev) => {
     let calendar = this.component.getCalendar()
 
-    calendar.dispatch({
-      type: 'CLEAR_EVENT_RESIZE'
-    })
-
     if (this.mutation) {
       calendar.dispatch({
         type: 'MUTATE_EVENTS',
         mutation: this.mutation,
         instanceId: this.draggingSeg.eventRange.eventInstance.instanceId
       })
+
+      this.mutation = null
     }
 
-    this.mutation = null
     this.draggingSeg = null
   }
 

+ 13 - 17
src/interactions/HitDragging.ts

@@ -4,7 +4,7 @@ import ElementDragging from '../dnd/ElementDragging'
 import DateComponent, { DateComponentHash } from '../component/DateComponent'
 import { DateSpan, isDateSpansEqual } from '../reducers/date-span'
 import { computeRect } from '../util/dom-geom'
-import { constrainPoint, intersectRects, getRectCenter, diffPoints, Rect } from '../util/geom'
+import { constrainPoint, intersectRects, getRectCenter, diffPoints, Rect, Point } from '../util/geom'
 
 export interface Hit {
   component: DateComponent
@@ -14,12 +14,12 @@ export interface Hit {
 }
 
 /*
-fires (none will be fired if no initial hit):
+emits:
 - pointerdown
 - dragstart
-- hitover - always fired in beginning
-- hitout - only fired when pointer moves away. not fired at drag end
+- hitchange
 - pointerup
+- (hitchange - again, to null, if ended over a hit)
 - dragend
 */
 export default class HitDragging {
@@ -30,13 +30,13 @@ export default class HitDragging {
 
   // options that can be set by caller
   useSubjectCenter: boolean = false
-  requireInitial: boolean = true
+  requireInitial: boolean = true // if doesn't start out on a hit, won't emit any events
 
   // internal state
   initialHit: Hit
   movingHit: Hit
   finalHit: Hit // won't ever be populated if shouldIgnoreMove
-  coordAdjust: any
+  coordAdjust: Point
 
   constructor(dragging: ElementDragging, droppable: DateComponent | DateComponentHash) {
 
@@ -118,6 +118,10 @@ export default class HitDragging {
   }
 
   handleDragEnd = (ev: PointerDragEvent) => {
+    if (this.movingHit) {
+      this.emitter.trigger('hitchange', null, true, ev)
+    }
+
     this.finalHit = this.movingHit
     this.movingHit = null
     this.emitter.trigger('dragend', ev)
@@ -129,17 +133,9 @@ export default class HitDragging {
       ev.pageY + this.coordAdjust.top
     )
 
-    if (!this.movingHit || !isHitsEqual(this.movingHit, hit)) {
-
-      if (this.movingHit) {
-        this.emitter.trigger('hitout', this.movingHit, ev)
-        this.movingHit = null
-      }
-
-      if (hit) {
-        this.movingHit = hit
-        this.emitter.trigger('hitover', hit, ev)
-      }
+    if (!isHitsEqual(this.movingHit, hit)) {
+      this.movingHit = hit
+      this.emitter.trigger('hitchange', hit, false, ev)
     }
   }
 

+ 2 - 1
src/reducers/event-interaction.ts

@@ -2,7 +2,8 @@ import { EventStore } from './event-store'
 import { Seg } from '../component/DateComponent'
 
 export interface EventInteractionState {
-  eventStore: EventStore
+  affectedEvents: EventStore
+  mutatedEvents: EventStore
   origSeg?: Seg
   willCreateEvent?: boolean // doesn't apply to resize :(
 }