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

+ 141 - 0
demos/external-dragging-builtin.html

@@ -0,0 +1,141 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset='utf-8' />
+<link href='../node_modules/dragula/dist/dragula.css' rel='stylesheet' />
+<link href='../dist/fullcalendar.css' rel='stylesheet' />
+<link href='../dist/fullcalendar.print.css' rel='stylesheet' media='print' />
+<script src='../dist/fullcalendar.js'></script>
+<script>
+
+  document.addEventListener('DOMContentLoaded', function() {
+
+    /* initialize the external events
+    -----------------------------------------------------------------*/
+
+    var containerEl = document.getElementById('external-events-list');
+    var eventEls = Array.prototype.slice.call(
+      containerEl.querySelectorAll('.fc-event')
+    );
+
+    eventEls.forEach(function(eventEl) {
+      new FullCalendar.ExternalDraggableEvent(
+        eventEl,
+        {}
+      )
+
+      // eventEl.setAttribute('data-event', JSON.stringify({
+      //   title: eventEl.innerText.trim(),
+      //   stick: true
+      // }));
+    });
+
+    // FullCalendar.dragula({
+    //   containers: [ containerEl ],
+    //   copy: true
+    // });
+
+    /* initialize the calendar
+    -----------------------------------------------------------------*/
+
+    var calendarEl = document.getElementById('calendar');
+    var calendar = new FullCalendar.Calendar(calendarEl, {
+      header: {
+        left: 'prev,next today',
+        center: 'title',
+        right: 'month,agendaWeek,agendaDay'
+      },
+      editable: true,
+      droppable: true, // this allows things to be dropped onto the calendar
+      drop: function() {
+        // is the "remove after drop" checkbox checked?
+        if (document.getElementById('drop-remove').checked) {
+          // if so, remove the element from the "Draggable Events" list
+          this.parentNode.removeChild(this);
+        }
+      }
+    });
+    calendar.render();
+
+  });
+
+</script>
+<style>
+
+  body {
+    margin-top: 40px;
+    text-align: center;
+    font-size: 14px;
+    font-family: "Lucida Grande",Helvetica,Arial,Verdana,sans-serif;
+  }
+
+  #wrap {
+    width: 1100px;
+    margin: 0 auto;
+  }
+
+  #external-events {
+    float: left;
+    width: 150px;
+    padding: 0 10px;
+    border: 1px solid #ccc;
+    background: #eee;
+    text-align: left;
+  }
+
+  #external-events h4 {
+    font-size: 16px;
+    margin-top: 0;
+    padding-top: 1em;
+  }
+
+  #external-events .fc-event {
+    margin: 10px 0;
+    cursor: pointer;
+  }
+
+  #external-events p {
+    margin: 1.5em 0;
+    font-size: 11px;
+    color: #666;
+  }
+
+  #external-events p input {
+    margin: 0;
+    vertical-align: middle;
+  }
+
+  #calendar {
+    float: right;
+    width: 900px;
+  }
+
+</style>
+</head>
+<body>
+  <div id='wrap'>
+
+    <div id='external-events'>
+      <h4>Draggable Events</h4>
+
+      <div id='external-events-list'>
+        <div class='fc-event'>My Event 1</div>
+        <div class='fc-event'>My Event 2</div>
+        <div class='fc-event'>My Event 3</div>
+        <div class='fc-event'>My Event 4</div>
+        <div class='fc-event'>My Event 5</div>
+      </div>
+
+      <p>
+        <input type='checkbox' id='drop-remove' />
+        <label for='drop-remove'>remove after drop</label>
+      </p>
+    </div>
+
+    <div id='calendar'></div>
+
+    <div style='clear:both'></div>
+
+  </div>
+</body>
+</html>

+ 6 - 2
demos/external-dragging-dragula.html

@@ -7,7 +7,7 @@
 <link href='../dist/fullcalendar.print.css' rel='stylesheet' media='print' />
 <script src='../node_modules/dragula/dist/dragula.js'></script>
 <script src='../dist/fullcalendar.js'></script>
-<script src='../dist/dragula.js'></script>
+<script src='../dist/generic-dragging.js'></script>
 <script>
 
   document.addEventListener('DOMContentLoaded', function() {
@@ -27,11 +27,15 @@
       }));
     });
 
-    FullCalendar.dragula({
+    var drake = dragula({
       containers: [ containerEl ],
       copy: true
     });
 
+    FullCalendarGenericDragging.enable({
+      mirrorSelector: '.gu-mirror'
+    })
+
     /* initialize the calendar
     -----------------------------------------------------------------*/
 

+ 7 - 3
demos/external-dragging-jquery-ui.html

@@ -7,21 +7,25 @@
 <script src='../node_modules/jquery/dist/jquery.js'></script>
 <script src='../node_modules/components-jqueryui/jquery-ui.js'></script>
 <script src='../dist/fullcalendar.js'></script>
-<script src='../dist/jquery-ui-draggable.js'></script>
+<script src='../dist/generic-dragging.js'></script>
 <script>
 
   document.addEventListener('DOMContentLoaded', function() {
 
+    FullCalendarGenericDragging.enable({
+      mirrorSelector: '.ui-draggable-dragging'
+    })
+
     /* initialize the external events
     -----------------------------------------------------------------*/
 
     $('#external-events .fc-event').each(function() {
 
       // store data so the calendar knows to render an event upon drop
-      $(this).data('event', {
+      $(this).attr('data-event', JSON.stringify({
         title: $.trim($(this).text()), // use the element's text as the event title
         stick: true // maintain when user navigates (see docs on the renderEvent method)
-      });
+      }));
 
       // make the event draggable using jQuery UI
       $(this).draggable({

+ 0 - 48
plugins/dragula/main.ts

@@ -1,48 +0,0 @@
-import * as dragula from 'dragula'
-import * as FullCalendar from 'fullcalendar'
-
-
-let visibleCalendars = []
-
-FullCalendar.Calendar.on('initialRender', function(calendar) {
-  visibleCalendars.push(calendar)
-
-  calendar.one('destroy', function() {
-    FullCalendar.removeExact(visibleCalendars, calendar)
-  })
-})
-
-
-let recentEvent
-
-[
-  'mousedown',
-  'touchstart',
-  'pointerdown'
-].forEach(function(eventName) {
-  document.addEventListener(eventName, function(ev) {
-    recentEvent = ev
-  })
-})
-
-
-function constructDragula(...args) {
-  let drake = dragula.apply(window, args)
-
-  drake.on('drag', function(draggingEl) { // dragging started
-    for (let calendar of visibleCalendars) {
-      calendar.handlExternalDragStart(
-        recentEvent,
-        draggingEl,
-        false // have FullCalendar watch for mouse/touch events
-          // because dragula doesn't expose a 'move' event
-      )
-    }
-  })
-
-  return drake
-}
-
-
-(FullCalendar as any).dragula = constructDragula
-export default constructDragula

+ 83 - 0
plugins/generic-dragging/DumbDragListener.ts

@@ -0,0 +1,83 @@
+import {
+  PointerDragListener,
+  PointerDragEvent,
+  IntentfulDragListener,
+  EmitterMixin
+} from 'fullcalendar'
+
+/* needs to fire events:
+- pointerdown
+- dragstart
+- dragmove
+- pointerup
+- dragend
+*/
+export default class DumbDragListener implements IntentfulDragListener {
+
+  isDragging: boolean
+  emitter: EmitterMixin
+  options: any
+  pointerListener: PointerDragListener
+
+  constructor(options) {
+    this.options = options
+    this.emitter = new EmitterMixin()
+
+    let pointerListener = this.pointerListener = new PointerDragListener(document as any)
+    pointerListener.selector = options.itemSelector || '[data-event]' // TODO: better
+    pointerListener.on('pointerdown', this.handlePointerDown)
+    pointerListener.on('pointermove', this.handlePointerMove)
+    pointerListener.on('pointerup', this.handlePointerUp)
+  }
+
+  destroy() {
+    this.pointerListener.destroy()
+  }
+
+  on(name, func) {
+    this.emitter.on(name, func)
+  }
+
+  handlePointerDown = (ev: PointerDragEvent) => {
+    this.isDragging = true
+    this.emitter.trigger('pointerdown', ev)
+    this.emitter.trigger('dragstart', ev)
+  }
+
+  handlePointerMove = (ev: PointerDragEvent) => {
+    this.emitter.trigger('dragmove', ev)
+  }
+
+  handlePointerUp = (ev: PointerDragEvent) => {
+    this.isDragging = false
+    this.emitter.trigger('pointerup', ev)
+    this.emitter.trigger('dragend', ev)
+  }
+
+  setMirrorNeedsRevert() {
+    // doesn't support revert animation
+  }
+
+  enableMirror() {
+    let selector = this.options.mirrorSelector
+    let mirrorEl = selector ? document.querySelector(selector) as HTMLElement : null
+
+    if (mirrorEl) {
+      mirrorEl.style.visibility = ''
+    }
+  }
+
+  disableMirror() {
+    let selector = this.options.mirrorSelector
+    let mirrorEl = selector ? document.querySelector(selector) as HTMLElement : null
+
+    if (mirrorEl) {
+      mirrorEl.style.visibility = 'hidden'
+    }
+  }
+
+  setIgnoreMove(bool: boolean) {
+    // no optimization
+  }
+
+}

+ 22 - 0
plugins/generic-dragging/main.ts

@@ -0,0 +1,22 @@
+import { ExternalDragging } from 'fullcalendar'
+import DumbDragListener from './DumbDragListener'
+
+let externalDragging
+
+window['FullCalendarGenericDragging'] = {
+
+  enable(options) {
+    let dragListener = new DumbDragListener(options || {})
+    externalDragging = new ExternalDragging(dragListener)
+  },
+
+  disable() {
+    if (externalDragging) {
+      externalDragging.destroy()
+      externalDragging = null
+    }
+  }
+
+}
+
+

+ 0 - 50
plugins/jquery-ui-draggable/main.ts

@@ -1,50 +0,0 @@
-import * as $ from 'jquery'
-import { Calendar, ExternalDropping } from 'fullcalendar'
-
-let $document = $(document)
-
-Calendar.on('initialRender', function(calendar) {
-
-  const handleDragStart = function(ev, ui) {
-
-    const handleDragMove = (ev, ui) => {
-      calendar.handleExternalDragMove(ev)
-    }
-
-    const handleDragStop = (ev, ui) => {
-      calendar.handleExternalDragStop(ev)
-      $document
-        .off('drag', handleDragMove)
-        .off('dragstop', handleDragStop)
-    }
-
-    $document
-      .on('drag', handleDragMove)
-      .on('dragstop', handleDragStop)
-
-    calendar.handlExternalDragStart(
-      ev.originalEvent,
-      ((ui && ui.item) ? ui.item[0] : null) || ev.target,
-      ev.name === 'dragstart' // don't watch mouse/touch movements if doing jqui drag (not sort)
-    )
-  }
-
-  $document.on('dragstart sortstart', handleDragStart)
-
-  calendar.one('destroy', function(calendar) {
-    $document.off('dragstart sortstart', handleDragStart)
-  })
-})
-
-
-const origGetEmbeddedElData = ExternalDropping.getEmbeddedElData
-
-ExternalDropping.getEmbeddedElData = function(el, name, shouldParseJson = false) {
-  let val = $(el).data(name) // will automatically parse JSON
-
-  if (val != null) {
-    return val
-  }
-
-  return origGetEmbeddedElData.apply(ExternalDropping, arguments)
-}

+ 0 - 2
src/Calendar.ts

@@ -119,8 +119,6 @@ export default class Calendar {
       this.el.classList.add('fc')
       this._render()
       this.isRendered = true
-      this.trigger('initialRender')
-      Calendar.trigger('initialRender', this)
     } else if (this.elementVisible()) {
       // mainly for the public API
       this.calcSize()

+ 1 - 1
src/dnd/DragMirror.ts

@@ -1,4 +1,4 @@
-import IntentfulDragListener from '../dnd/IntentfulDragListener'
+import { IntentfulDragListener } from '../dnd/IntentfulDragListener'
 import { PointerDragEvent } from '../dnd/PointerDragListener'
 import { removeElement, applyStyle } from '../util/dom-manip'
 import { computeRect } from '../util/dom-geom'

+ 31 - 26
src/dnd/HitDragListener.ts

@@ -1,6 +1,6 @@
 import EmitterMixin from '../common/EmitterMixin'
 import { PointerDragEvent } from './PointerDragListener'
-import IntentfulDragListener from './IntentfulDragListener'
+import { IntentfulDragListener } from './IntentfulDragListener'
 import DateComponent, { DateComponentHash } from '../component/DateComponent'
 import { Selection } from '../reducers/selection'
 import { computeRect } from '../util/dom-geom'
@@ -21,40 +21,39 @@ fires (none will be fired if no initial hit):
 */
 export default class HitDragListener {
 
-  component: DateComponent
-  droppableComponentHash: DateComponentHash
+  droppableHash: DateComponentHash
   dragListener: IntentfulDragListener
   emitter: EmitterMixin
   initialHit: Hit
   movingHit: Hit
   finalHit: Hit // won't ever be populated if ignoreMove
   coordAdjust: any
+  dieIfNoInitial: boolean = true
+  isIgnoringMove: boolean = false
 
   // options
   subjectCenter: boolean = false
 
-  constructor(component: DateComponent, droppableComponentHash?: DateComponentHash) {
-    this.component = component
-    this.droppableComponentHash = droppableComponentHash
+  constructor(dragListener: IntentfulDragListener, droppable: DateComponent | DateComponentHash) {
 
-    if (droppableComponentHash) {
-      this.droppableComponentHash = droppableComponentHash
+    if (droppable instanceof DateComponent) {
+      this.droppableHash = { [droppable.uid]: droppable }
     } else {
-      this.droppableComponentHash = { [component.uid]: component }
+      this.droppableHash = droppable
     }
 
-    let dragListener = this.dragListener = new IntentfulDragListener(component.el)
     dragListener.on('pointerdown', this.onPointerDown)
     dragListener.on('dragstart', this.onDragStart)
     dragListener.on('dragmove', this.onDragMove)
     dragListener.on('pointerup', this.onPointerUp)
     dragListener.on('dragend', this.onDragEnd)
 
+    this.dragListener = dragListener
     this.emitter = new EmitterMixin()
   }
 
   destroy() {
-    this.dragListener.destroy()
+    this.dragListener.destroy() // should not be responsible for destroying!
   }
 
   on(name, handler) {
@@ -69,13 +68,15 @@ export default class HitDragListener {
     this.prepareComponents()
     this.processFirstCoord(ev)
 
-    let { pointerListener } = this.dragListener
+    let { dragListener } = this
 
-    if (this.initialHit) {
-      pointerListener.ignoreMove = false
+    if (this.initialHit || !this.dieIfNoInitial) {
+      this.isIgnoringMove = false
+      dragListener.setIgnoreMove(false)
       this.emitter.trigger('pointerdown', ev)
     } else {
-      pointerListener.ignoreMove = true
+      this.isIgnoringMove = true
+      dragListener.setIgnoreMove(true)
     }
   }
 
@@ -103,23 +104,27 @@ export default class HitDragListener {
       }
 
       this.coordAdjust = diffPoints(adjustedPoint, origPoint)
+    } else {
+      this.coordAdjust = { left: 0, top: 0 }
     }
   }
 
   onDragStart = (ev: PointerDragEvent) => {
-    this.emitter.trigger('dragstart', ev)
-    this.handleMove(ev)
+    if (!this.isIgnoringMove) {
+      this.emitter.trigger('dragstart', ev)
+      this.handleMove(ev)
+    }
   }
 
   onDragMove = (ev: PointerDragEvent) => {
-    this.emitter.trigger('dragmove', ev)
-    this.handleMove(ev)
+    if (!this.isIgnoringMove) {
+      this.emitter.trigger('dragmove', ev)
+      this.handleMove(ev)
+    }
   }
 
   onPointerUp = (ev: PointerDragEvent) => {
-    let { pointerListener } = this.dragListener
-
-    if (!pointerListener.ignoreMove) { // cancelled in onPointerDown?
+    if (!this.isIgnoringMove) { // cancelled in onPointerDown?
       this.emitter.trigger('pointerup', ev)
     }
   }
@@ -151,15 +156,15 @@ export default class HitDragListener {
   }
 
   prepareComponents() {
-    for (let id in this.droppableComponentHash) {
-      let component = this.droppableComponentHash[id]
+    for (let id in this.droppableHash) {
+      let component = this.droppableHash[id]
       component.buildCoordCaches()
     }
   }
 
   queryHit(x, y): Hit {
-    for (let id in this.droppableComponentHash) {
-      let component = this.droppableComponentHash[id]
+    for (let id in this.droppableHash) {
+      let component = this.droppableHash[id]
       let hit = component.queryHit(x, y) as Hit
 
       if (hit) {

+ 35 - 1
src/dnd/IntentfulDragListener.ts

@@ -3,6 +3,23 @@ import { default as PointerDragListener, PointerDragEvent } from './PointerDragL
 import { preventSelection, allowSelection, preventContextMenu, allowContextMenu } from '../util/misc'
 import DragMirror from './DragMirror'
 
+/* needs events:
+- pointerdown
+- dragstart
+- dragmove
+- pointerup
+- dragend
+*/
+export interface IntentfulDragListener {
+  isDragging: boolean
+  on(evName: string, callback: (ev: PointerDragEvent) => void)
+  setMirrorNeedsRevert(bool: boolean)
+  enableMirror()
+  disableMirror()
+  setIgnoreMove(bool: boolean)
+  destroy()
+}
+
 /*
 fires:
 - pointerdown
@@ -12,7 +29,7 @@ fires:
 - pointerup (always happens before dragend!)
 - dragend (happens after any revert animation)
 */
-export default class IntentfulDragListener {
+export class IntentfulDragListenerImpl implements IntentfulDragListener {
 
   pointerListener: PointerDragListener
   emitter: EmitterMixin
@@ -171,4 +188,21 @@ export default class IntentfulDragListener {
     this.isDragging = false
   }
 
+
+  enableMirror() {
+    this.dragMirror.enable()
+  }
+
+  disableMirror() {
+    this.dragMirror.disable()
+  }
+
+  setMirrorNeedsRevert(bool: boolean) {
+    this.dragMirror.needsRevert = bool
+  }
+
+  setIgnoreMove(bool: boolean) {
+    this.pointerListener.ignoreMove = bool
+  }
+
 }

+ 34 - 32
src/dnd/PointerDragListener.ts

@@ -89,7 +89,7 @@ export default class PointerDragListener {
       isPrimaryMouseButton(ev) &&
       this.maybeStart(ev)
     ) {
-      this.emitter.trigger('pointerdown', this.createMouseEvent(ev))
+      this.emitter.trigger('pointerdown', createMouseEvent(ev, this.subjectEl))
 
       if (!this.ignoreMove) {
         document.addEventListener('mousemove', this.onMouseMove)
@@ -100,14 +100,14 @@ export default class PointerDragListener {
   }
 
   onMouseMove = (ev: MouseEvent) => {
-    this.emitter.trigger('pointermove', this.createMouseEvent(ev))
+    this.emitter.trigger('pointermove', createMouseEvent(ev, this.subjectEl))
   }
 
   onMouseUp = (ev: MouseEvent) => {
     document.removeEventListener('mousemove', this.onMouseMove)
     document.removeEventListener('mouseup', this.onMouseUp)
 
-    this.emitter.trigger('pointerup', this.createMouseEvent(ev))
+    this.emitter.trigger('pointerup', createMouseEvent(ev, this.subjectEl))
 
     this.cleanup()
   }
@@ -116,7 +116,7 @@ export default class PointerDragListener {
     if (this.maybeStart(ev)) {
       this.isDraggingTouch = true
 
-      this.emitter.trigger('pointerdown', this.createTouchEvent(ev))
+      this.emitter.trigger('pointerdown', createTouchEvent(ev, this.subjectEl))
 
       // unlike mouse, need to attach to target, not document
       // https://stackoverflow.com/a/45760014
@@ -141,7 +141,7 @@ export default class PointerDragListener {
   }
 
   onTouchMove = (ev: TouchEvent) => {
-    this.emitter.trigger('pointermove', this.createTouchEvent(ev))
+    this.emitter.trigger('pointermove', createTouchEvent(ev, this.subjectEl))
   }
 
   onTouchEnd = (ev: TouchEvent) => {
@@ -153,7 +153,7 @@ export default class PointerDragListener {
       target.removeEventListener('touchcancel', this.onTouchEnd)
       window.removeEventListener('scroll', this.onTouchScroll)
 
-      this.emitter.trigger('pointerup', this.createTouchEvent(ev))
+      this.emitter.trigger('pointerup', createTouchEvent(ev, this.subjectEl))
 
       this.cleanup()
       this.isDraggingTouch = false
@@ -171,40 +171,42 @@ export default class PointerDragListener {
     }
   }
 
-  createMouseEvent(ev): PointerDragEvent {
-    return {
-      origEvent: ev,
-      isTouch: false,
-      el: this.subjectEl,
-      pageX: ev.pageX,
-      pageY: ev.pageY
-    }
+}
+
+function createMouseEvent(ev, el: HTMLElement): PointerDragEvent {
+  return {
+    origEvent: ev,
+    isTouch: false,
+    el: el,
+    pageX: ev.pageX,
+    pageY: ev.pageY
   }
+}
 
-  createTouchEvent(ev): PointerDragEvent {
-    let pev = {
-      origEvent: ev,
-      isTouch: true,
-      el: this.subjectEl
-    } as PointerDragEvent
+function createTouchEvent(ev, el: HTMLElement): PointerDragEvent {
+  let pev = {
+    origEvent: ev,
+    isTouch: true,
+    el: el
+  } as PointerDragEvent
 
-    let touches = ev.touches
+  let touches = ev.touches
 
-    // if touch coords available, prefer,
-    // because FF would give bad ev.pageX ev.pageY
-    if (touches && touches.length) {
-      pev.pageX = touches[0].pageX
-      pev.pageY = touches[0].pageY
-    } else {
-      pev.pageX = ev.pageX
-      pev.pageY = ev.pageY
-    }
-
-    return pev
+  // if touch coords available, prefer,
+  // because FF would give bad ev.pageX ev.pageY
+  if (touches && touches.length) {
+    pev.pageX = touches[0].pageX
+    pev.pageY = touches[0].pageY
+  } else {
+    pev.pageX = ev.pageX
+    pev.pageY = ev.pageY
   }
 
+  return pev
 }
 
+
+
 let ignoreMouseDepth = 0
 
 function startIgnoringMouse() { // can be made non-class function

+ 5 - 2
src/exports.ts

@@ -109,5 +109,8 @@ export { parse as parseMarker } from './datelib/parsing'
 export { registerSourceType } from './reducers/event-sources'
 export { refineProps } from './reducers/utils'
 
-export { default as PointerDragListener } from './dnd/PointerDragListener'
-export { default as IntentfulDragListener } from './dnd/IntentfulDragListener'
+export { default as PointerDragListener, PointerDragEvent } from './dnd/PointerDragListener'
+export { IntentfulDragListener } from './dnd/IntentfulDragListener'
+
+export { default as ExternalDraggableEvent } from './interactions/ExternalDraggableEvent'
+export { default as ExternalDragging } from './interactions/ExternalDragging'

+ 6 - 3
src/interactions/DateClicking.ts

@@ -1,22 +1,25 @@
 import DateComponent from '../component/DateComponent'
+import { IntentfulDragListenerImpl } from '../dnd/IntentfulDragListener'
 import HitDragListener, { isHitsEqual } from '../dnd/HitDragListener'
 import { PointerDragEvent } from '../dnd/PointerDragListener'
 
 export default class DateClicking {
 
   component: DateComponent
+  dragListener: IntentfulDragListenerImpl
   hitListener: HitDragListener
 
   constructor(component: DateComponent) {
     this.component = component
-    this.hitListener = new HitDragListener(component)
+    this.dragListener = new IntentfulDragListenerImpl(component.el)
+    this.hitListener = new HitDragListener(this.dragListener, component)
     this.hitListener.on('pointerdown', this.onPointerDown)
     this.hitListener.on('dragend', this.onDragEnd)
   }
 
   onPointerDown = (ev: PointerDragEvent) => {
     let { component } = this
-    let { pointerListener } = this.hitListener.dragListener
+    let { pointerListener } = this.dragListener
 
     // do this in pointerdown (not dragend) because DOM might be mutated by the time dragend is fired
     pointerListener.ignoreMove = !component.isValidDateInteraction(pointerListener.downEl)
@@ -24,7 +27,7 @@ export default class DateClicking {
 
   onDragEnd = (ev: PointerDragEvent) => {
     let { component } = this
-    let { pointerListener } = this.hitListener.dragListener
+    let { pointerListener } = this.dragListener
 
     if (
       !pointerListener.ignoreMove && // not ignored in onPointerDown

+ 7 - 4
src/interactions/DateSelecting.ts

@@ -5,12 +5,14 @@ import HitDragListener, { Hit } from '../dnd/HitDragListener'
 import { Selection } from '../reducers/selection'
 import UnzonedRange from '../models/UnzonedRange'
 import { PointerDragEvent } from '../dnd/PointerDragListener'
+import { IntentfulDragListenerImpl } from '../dnd/IntentfulDragListener'
 import { GlobalContext } from '../common/GlobalContext'
 
 export default class DateSelecting {
 
   component: DateComponent
   globalContext: GlobalContext
+  dragListener: IntentfulDragListenerImpl
   hitListener: HitDragListener
   dragSelection: Selection
 
@@ -18,8 +20,10 @@ export default class DateSelecting {
     this.component = component
     this.globalContext = globalContext
 
-    let hitListener = this.hitListener = new HitDragListener(component)
-    hitListener.dragListener.touchScrollAllowed = false
+    this.dragListener = new IntentfulDragListenerImpl(component.el)
+    this.dragListener.touchScrollAllowed = false
+
+    let hitListener = this.hitListener = new HitDragListener(this.dragListener, component)
     hitListener.on('pointerdown', this.onPointerDown)
     hitListener.on('dragstart', this.onDragStart)
     hitListener.on('hitover', this.onHitOver)
@@ -31,8 +35,7 @@ export default class DateSelecting {
   }
 
   onPointerDown = (ev: PointerDragEvent) => {
-    let { component } = this
-    let { dragListener } = this.hitListener
+    let { component, dragListener } = this
     let isValid = component.opt('selectable') &&
       component.isValidDateInteraction(ev.origEvent.target as HTMLElement)
 

+ 12 - 11
src/interactions/EventDragging.ts

@@ -5,11 +5,13 @@ import { EventMutation, diffDates, getRelatedEvents, applyMutationToAll } from '
 import { GlobalContext } from '../common/GlobalContext'
 import { startOfDay } from '../datelib/marker'
 import { elementClosest } from '../util/dom-manip'
+import { IntentfulDragListenerImpl } from '../dnd/IntentfulDragListener'
 
 export default class EventDragging {
 
   component: DateComponent
-  globalContext: GlobalContext
+  globalContext: GlobalContext // need this as a member?
+  dragListener: IntentfulDragListenerImpl
   hitListener: HitDragListener
   draggingSeg: Seg
   mutation: EventMutation
@@ -18,12 +20,11 @@ export default class EventDragging {
     this.component = component
     this.globalContext = globalContext
 
-    let hitListener = this.hitListener = new HitDragListener(
-      component,
-      globalContext.componentHash
-    )
-    hitListener.dragListener.pointerListener.selector = '.fc-draggable'
-    hitListener.dragListener.touchScrollAllowed = false
+    this.dragListener = new IntentfulDragListenerImpl(component.el)
+    this.dragListener.pointerListener.selector = '.fc-draggable'
+    this.dragListener.touchScrollAllowed = false
+
+    let hitListener = this.hitListener = new HitDragListener(this.dragListener, globalContext.componentHash)
     hitListener.subjectCenter = true
     hitListener.on('pointerdown', this.onPointerDown)
     hitListener.on('dragstart', this.onDragStart)
@@ -37,7 +38,7 @@ export default class EventDragging {
   }
 
   onPointerDown = (ev: PointerDragEvent) => {
-    let { dragListener } = this.hitListener
+    let { dragListener } = this
 
     dragListener.delay = this.computeDragDelay(ev)
 
@@ -46,7 +47,7 @@ export default class EventDragging {
 
     let origTarget = ev.origEvent.target as HTMLElement
 
-    this.hitListener.dragListener.pointerListener.ignoreMove =
+    dragListener.pointerListener.ignoreMove =
       !this.component.isValidSegInteraction(origTarget) ||
       elementClosest(origTarget, '.fc-resizer')
   }
@@ -110,7 +111,7 @@ export default class EventDragging {
       isTouch: ev.isTouch
     })
 
-    let { dragMirror } = this.hitListener.dragListener
+    let { dragMirror } = this.dragListener
 
     // TODO wish we could somehow wait for dispatch to guarantee render
     if (!document.querySelector('.fc-helper')) {
@@ -139,7 +140,7 @@ export default class EventDragging {
       isTouch: ev.isTouch
     })
 
-    let { dragMirror } = this.hitListener.dragListener
+    let { dragMirror } = this.dragListener
 
     dragMirror.enable()
     dragMirror.needsRevert = true

+ 8 - 4
src/interactions/EventResizing.ts

@@ -3,10 +3,12 @@ import HitDragListener, { isHitsEqual, Hit } from '../dnd/HitDragListener'
 import { EventMutation, diffDates, getRelatedEvents, applyMutationToAll } from '../reducers/event-mutation'
 import { elementClosest } from '../util/dom-manip'
 import UnzonedRange from '../models/UnzonedRange'
+import { IntentfulDragListenerImpl } from '../dnd/IntentfulDragListener'
 
 export default class EventDragging {
 
   component: DateComponent
+  dragListener: IntentfulDragListenerImpl
   hitListener: HitDragListener
   draggingSeg: Seg
   mutation: EventMutation
@@ -14,9 +16,11 @@ export default class EventDragging {
   constructor(component: DateComponent) {
     this.component = component
 
-    let hitListener = this.hitListener = new HitDragListener(component)
-    hitListener.dragListener.pointerListener.selector = '.fc-resizer'
-    hitListener.dragListener.touchScrollAllowed = false
+    this.dragListener = new IntentfulDragListenerImpl(component.el)
+    this.dragListener.pointerListener.selector = '.fc-resizer'
+    this.dragListener.touchScrollAllowed = false
+
+    let hitListener = this.hitListener = new HitDragListener(this.dragListener, component)
     hitListener.on('pointerdown', this.onPointerDown)
     hitListener.on('dragstart', this.onDragStart)
     hitListener.on('hitover', this.onHitOver)
@@ -33,7 +37,7 @@ export default class EventDragging {
     let eventInstanceId = seg.eventRange.eventInstance.instanceId
 
     // if touch, need to be working with a selected event
-    this.hitListener.dragListener.pointerListener.ignoreMove =
+    this.dragListener.pointerListener.ignoreMove =
       !this.component.isValidSegInteraction(ev.origEvent.target) ||
       (ev.isTouch && this.component.selectedEventInstanceId !== eventInstanceId)
   }

+ 21 - 0
src/interactions/ExternalDraggableEvent.ts

@@ -0,0 +1,21 @@
+import { IntentfulDragListenerImpl } from '../dnd/IntentfulDragListener'
+import ExternalDragging from './ExternalDragging'
+
+export interface ExternalDraggableEventSettings {
+
+}
+
+export default class ExternalDraggableEvent {
+
+  externalDragging: ExternalDragging
+
+  constructor(el: HTMLElement, settings: ExternalDraggableEventSettings) {
+    let dragListener = new IntentfulDragListenerImpl(el)
+    this.externalDragging = new ExternalDragging(dragListener)
+  }
+
+  destroy() {
+    this.externalDragging.destroy()
+  }
+
+}

+ 111 - 0
src/interactions/ExternalDragging.ts

@@ -0,0 +1,111 @@
+import { IntentfulDragListener } from '../dnd/IntentfulDragListener'
+import HitDragListener, { Hit } from '../dnd/HitDragListener'
+import globalContext from '../common/GlobalContext'
+import { PointerDragEvent } from '../dnd/PointerDragListener'
+import { EventStore, parseDef, createInstance } from '../reducers/event-store'
+import UnzonedRange from '../models/UnzonedRange';
+
+export default class ExternalDragging {
+
+  hitListener: HitDragListener
+  addableEventStore: EventStore
+
+  constructor(dragListener: IntentfulDragListener) {
+    let hitListener = this.hitListener = new HitDragListener(dragListener, globalContext.componentHash)
+    hitListener.dieIfNoInitial = false
+    hitListener.on('dragstart', this.onDragStart)
+    hitListener.on('hitover', this.onHitOver)
+    hitListener.on('hitout', this.onHitOut)
+    hitListener.on('dragend', this.onDragEnd)
+
+    dragListener.enableMirror()
+  }
+
+  destroy() {
+    this.hitListener.destroy() // should not be responsible for destroying something not belonged!
+  }
+
+  onDragStart = (ev: PointerDragEvent) => {
+
+    // TODO: nicer accessors in GlobalContext for this?
+    if (globalContext.eventSelectedComponent) {
+      let selectedCalendar = globalContext.eventSelectedComponent.getCalendar()
+
+      if (selectedCalendar) {
+        selectedCalendar.dispatch({
+          type: 'CLEAR_SELECTED_EVENT'
+        })
+        globalContext.eventSelectedComponent = null
+      }
+    }
+  }
+
+  onHitOver = (hit, ev) => {
+    let calendar = hit.component.getCalendar()
+
+    calendar.dispatch({
+      type: 'SET_DRAG',
+      displacement: (this.addableEventStore = computeEventStoreForHit(hit)),
+      origSeg: null,
+      isTouch: ev.isTouch
+    })
+
+    let { dragListener } = this.hitListener
+
+    dragListener.setMirrorNeedsRevert(false)
+
+    // TODO wish we could somehow wait for dispatch to guarantee render
+    if (!document.querySelector('.fc-helper')) {
+      dragListener.enableMirror()
+    } else {
+      dragListener.disableMirror()
+    }
+  }
+
+  onHitOut = (hit, ev) => { // 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',
+      displacement: { defs: {}, instances: {} }, // TODO: better way to make empty event-store
+      origSeg: null,
+      isTouch: ev.isTouch
+    })
+
+    this.addableEventStore = null
+
+    let { dragListener } = this.hitListener
+
+    dragListener.enableMirror()
+    dragListener.setMirrorNeedsRevert(true)
+  }
+
+  onDragEnd = () => {
+    if (this.addableEventStore) {
+      let finalComponent = this.hitListener.finalHit.component
+
+      finalComponent.getCalendar().dispatch({
+        type: 'CLEAR_DRAG'
+      })
+
+      // TODO: report mutation to dispatcher
+      console.log('addable', this.addableEventStore)
+    }
+  }
+
+}
+
+function computeEventStoreForHit(hit: Hit): EventStore {
+  let calendar = hit.component.getCalendar()
+  let def = parseDef({ title: 'test event' }, null, hit.isAllDay, false)
+  let instance = createInstance(def.defId, new UnzonedRange(
+    hit.range.start,
+    calendar.getDefaultEventEnd(hit.isAllDay, hit.range.start)
+  ))
+
+  return {
+    defs: { [def.defId]: def },
+    instances: { [instance.instanceId]: instance }
+  }
+}

+ 1 - 2
webpack.config.js

@@ -14,8 +14,7 @@ const MODULES = {
   'dist/fullcalendar.css': './src/main.scss',
   'dist/fullcalendar.print.css': './src/common/print.scss',
   'dist/gcal': './plugins/gcal/main.ts',
-  'dist/jquery-ui-draggable': './plugins/jquery-ui-draggable/main.ts',
-  'dist/dragula': './plugins/dragula/main.ts',
+  'dist/generic-dragging': './plugins/generic-dragging/main.ts',
   'tmp/automated-tests': './tests/automated/index'
 }