Преглед изворни кода

new way of unfocusing things. fixes bugs

Adam Shaw пре 7 година
родитељ
комит
80c877c952

+ 72 - 11
src/Calendar.ts

@@ -1,4 +1,4 @@
-import { createElement, removeElement, applyStyle, prependToElement } from './util/dom-manip'
+import { createElement, removeElement, applyStyle, prependToElement, elementClosest } from './util/dom-manip'
 import { computeHeightAndMargins } from './util/dom-geom'
 import { listenBySelector } from './util/dom-event'
 import { capitaliseFirstLetter, debounce } from './util/misc'
@@ -16,11 +16,10 @@ import { DateMarker, startOfDay } from './datelib/marker'
 import { createFormatter } from './datelib/formatting'
 import { Duration, createDuration } from './datelib/duration'
 import reduce from './reducers/main'
-import { parseDateSpan, DateSpanInput, DateSpan } from './structs/date-span'
+import { parseDateSpan, DateSpanInput, DateSpan, buildDateSpanApi } from './structs/date-span'
 import reselector from './util/reselector'
 import { assignTo } from './util/object'
 import { RenderForceFlags } from './component/Component'
-import browserContext from './common/browser-context'
 import { DateRangeInput, rangeContainsMarker } from './datelib/date-range'
 import { DateProfile } from './DateProfileGenerator'
 import { EventSourceInput, parseEventSource, EventSourceHash } from './structs/event-source'
@@ -66,6 +65,9 @@ export default class Calendar {
   elDirClassName: string
   contentEl: HTMLElement
 
+  documentPointer: PointerDragging // for unfocusing
+  isRecentPointerDateSelect = false // wish we could use a selector to detect date selection, but uses hit system
+
   suggestedViewHeight: number
   ignoreUpdateViewSize: number = 0
   removeNavLinkListener: any
@@ -322,6 +324,11 @@ export default class Calendar {
 
 
   bindGlobalHandlers() {
+    let documentPointer = this.documentPointer = new PointerDragging(document)
+    documentPointer.shouldIgnoreMove = true
+    documentPointer.shouldWatchScroll = false
+    documentPointer.emitter.on('pointerup', this.onDocumentPointerUp)
+
     if (this.opt('handleWindowResize')) {
       window.addEventListener('resize',
         this.windowResizeProxy = debounce( // prevents rapid calls
@@ -333,6 +340,8 @@ export default class Calendar {
   }
 
   unbindGlobalHandlers() {
+    this.documentPointer.destroy()
+
     if (this.windowResizeProxy) {
       window.removeEventListener('resize', this.windowResizeProxy)
       this.windowResizeProxy = null
@@ -986,7 +995,7 @@ export default class Calendar {
   }
 
 
-  // DateSpan / DayClick
+  // Date Selection / Event Selection / DayClick
   // -----------------------------------------------------------------------------------------------------------------
 
 
@@ -1018,23 +1027,43 @@ export default class Calendar {
     )
 
     if (selection) { // throw parse error otherwise?
-      this.dispatch({
-        type: 'SELECT_DATES',
-        selection: selection
-      })
-      browserContext.reportDateSelection(this, selection)
+      this.dispatch({ type: 'SELECT_DATES', selection })
+      this.triggerDateSelect(selection)
     }
   }
 
 
   // public method
   unselect(ev?: UIEvent) {
-    if (browserContext.dateSelectedCalendar === this) {
-      browserContext.unselectDates()
+    this.dispatch({ type: 'UNSELECT_DATES' })
+    this.triggerDateUnselect()
+  }
+
+
+  triggerDateSelect(selection: DateSpan, pev?: PointerDragEvent) {
+    let arg = buildDateSpanApi(selection, this.dateEnv)
+
+    arg.jsEvent = pev ? pev.origEvent : null
+    arg.view = this.view
+
+    this.publiclyTrigger('select', [ arg ])
+
+    if (pev) {
+      this.isRecentPointerDateSelect = true
     }
   }
 
 
+  triggerDateUnselect(pev?: PointerDragEvent) {
+    this.publiclyTrigger('unselect', [
+      {
+        jsEvent: pev ? pev.origEvent : null,
+        view: this.view
+      }
+    ])
+  }
+
+
   // TODO: receive pev?
   triggerDayClick(dateSpan: DateSpan, dayEl: HTMLElement, view: View, ev: UIEvent) {
     this.publiclyTrigger('dateClick', [
@@ -1050,6 +1079,38 @@ export default class Calendar {
   }
 
 
+  // for unfocusing selections
+  onDocumentPointerUp = (pev: PointerDragEvent) => {
+    let { state, view, documentPointer } = this
+
+    // touch-scrolling should never unfocus any type of selection
+    if (!documentPointer.wasTouchScroll) {
+
+      if (
+        state.dateSelection && // an existing date selection?
+        !this.isRecentPointerDateSelect // a new pointer-initiated date selection since last onDocumentPointerUp?
+      ) {
+        let unselectAuto = view.opt('unselectAuto')
+        let unselectCancel = view.opt('unselectCancel')
+
+        if (unselectAuto && (!unselectAuto || !elementClosest(documentPointer.downEl, unselectCancel))) {
+          this.unselect(pev.origEvent)
+        }
+      }
+
+      if (
+        state.eventSelection && // an existing event selected?
+        !elementClosest(documentPointer.downEl, EventDragging.SELECTOR) // interaction DIDN'T start on an event
+      ) {
+        this.dispatch({ type: 'UNSELECT_EVENT' })
+      }
+
+    }
+
+    this.isRecentPointerDateSelect = false
+  }
+
+
   // Date Utils
   // -----------------------------------------------------------------------------------------------------------------
 

+ 0 - 101
src/common/browser-context.ts

@@ -1,55 +1,26 @@
 import DateComponent from '../component/DateComponent'
-import PointerDragging, { PointerDragEvent } from '../dnd/PointerDragging'
 import DateClicking from '../interactions/DateClicking'
 import DateSelecting from '../interactions/DateSelecting'
 import EventClicking from '../interactions/EventClicking'
 import EventHovering from '../interactions/EventHovering'
 import EventDragging from '../interactions/EventDragging'
 import EventResizing from '../interactions/EventResizing'
-import { DateSpan, buildDateSpanApi } from '../structs/date-span'
-import Calendar from '../Calendar'
 
 export class BrowserContext {
 
-  pointer: PointerDragging
-  componentCnt: number = 0
   componentHash = {}
   listenerHash = {}
-  dateSelectedCalendar: Calendar
-  eventSelectedComponent: DateComponent
 
   registerComponent(component: DateComponent) {
     this.componentHash[component.uid] = component
-
-    if (!(this.componentCnt++)) {
-      this.bind()
-    }
-
     this.bindComponent(component)
   }
 
   unregisterComponent(component: DateComponent) {
     delete this.componentHash[component.uid]
-
-    if (!(--this.componentCnt)) {
-      this.unbind()
-    }
-
     this.unbindComponent(component)
   }
 
-  bind() {
-    let pointer = this.pointer = new PointerDragging(document)
-    pointer.shouldIgnoreMove = true
-    pointer.shouldWatchScroll = false
-    pointer.emitter.on('pointerup', this.onPointerUp)
-  }
-
-  unbind() {
-    this.pointer.destroy()
-    this.pointer = null
-  }
-
   bindComponent(component: DateComponent) {
     this.listenerHash[component.uid] = {
       dateClicking: new DateClicking(component),
@@ -74,78 +45,6 @@ export class BrowserContext {
     delete this.listenerHash[component.uid]
   }
 
-  onPointerUp = (ev) => {
-    let { listenerHash, pointer } = this
-
-    // trickiness about sending pointer-up to these components:
-    // pointer might be null, but if so, all components were destroyed and the following loops won't execute.
-    // also, a onDocumentPointerUp call might cause the component to be destroyed, so break into separate loops
-    // and freshly access listenerHash every time.
-
-    for (let id in listenerHash) {
-      listenerHash[id].dateSelecting.onDocumentPointerUp(ev, pointer.wasTouchScroll, pointer.downEl)
-    }
-
-    for (let id in listenerHash) {
-      listenerHash[id].eventDragging.onDocumentPointerUp(ev, pointer.wasTouchScroll, pointer.downEl)
-    }
-  }
-
-  unselectDates(pev?: PointerDragEvent) {
-    let { dateSelectedCalendar } = this
-
-    if (dateSelectedCalendar) {
-
-      dateSelectedCalendar.dispatch({
-        type: 'UNSELECT_DATES'
-      })
-
-      this.dateSelectedCalendar = null // in case publicTrigger wants to reselect
-
-      dateSelectedCalendar.publiclyTrigger('unselect', [
-        {
-          jsEvent: pev ? pev.origEvent : null,
-          view: dateSelectedCalendar.view
-        }
-      ])
-    }
-  }
-
-  reportDateSelection(calendar: Calendar, selection: DateSpan, pev?: PointerDragEvent) {
-
-    if (this.dateSelectedCalendar && this.dateSelectedCalendar !== calendar) {
-      this.unselectDates(pev)
-    }
-
-    this.dateSelectedCalendar = calendar // in case publicTrigger wants to unselect
-
-    let arg = buildDateSpanApi(selection, calendar.dateEnv)
-    arg.jsEvent = pev ? pev.origEvent : null
-    arg.view = calendar.view
-
-    calendar.publiclyTrigger('select', [ arg ])
-  }
-
-  unselectEvent() {
-    if (this.eventSelectedComponent) {
-      this.eventSelectedComponent.getCalendar().dispatch({
-        type: 'UNSELECT_EVENT'
-      })
-      this.eventSelectedComponent = null
-    }
-  }
-
-  reportEventSelection(component: DateComponent) {
-    if (
-      this.eventSelectedComponent &&
-      this.eventSelectedComponent.getCalendar() !== component.getCalendar()
-    ) {
-      this.unselectEvent()
-    }
-
-    this.eventSelectedComponent = component
-  }
-
 }
 
 export default new BrowserContext()

+ 0 - 2
src/interactions-external/ExternalElementDragging.ts

@@ -41,8 +41,6 @@ export default class ExternalElementDragging {
   }
 
   handleDragStart = (ev: PointerDragEvent) => {
-    browserContext.unselectEvent() // unselect any existing events
-
     this.dragMeta = this.buildDragMeta(ev.subjectEl as HTMLElement)
   }
 

+ 7 - 18
src/interactions/DateSelecting.ts

@@ -1,11 +1,9 @@
 import { compareNumbers, enableCursor, disableCursor } from '../util/misc'
-import { elementClosest } from '../util/dom-manip'
 import DateComponent from '../component/DateComponent'
 import HitDragging, { Hit } from './HitDragging'
 import { DateSpan } from '../structs/date-span'
 import { PointerDragEvent } from '../dnd/PointerDragging'
 import FeaturefulElementDragging from '../dnd/FeaturefulElementDragging'
-import browserContext from '../common/browser-context'
 
 /*
 Tracks when the user selects a portion of time of a component,
@@ -30,6 +28,7 @@ export default class DateSelecting {
     hitDragging.emitter.on('pointerdown', this.handlePointerDown)
     hitDragging.emitter.on('dragstart', this.handleDragStart)
     hitDragging.emitter.on('hitupdate', this.handleHitUpdate)
+    hitDragging.emitter.on('pointerup', this.handlePointerUp)
   }
 
   destroy() {
@@ -49,7 +48,9 @@ export default class DateSelecting {
   }
 
   handleDragStart = (ev: PointerDragEvent) => {
-    browserContext.unselectDates(ev) // clear selection from all other calendars/components
+    // unselect previous (just trigger the handlers)
+    // will render previous selection in handleHitUpdate
+    this.component.getCalendar().triggerDateUnselect()
   }
 
   handleHitUpdate = (hit: Hit | null, isFinal: boolean) => {
@@ -86,25 +87,13 @@ export default class DateSelecting {
     }
   }
 
-  onDocumentPointerUp = (ev: PointerDragEvent, wasTouchScroll: boolean, downEl: HTMLElement) => {
-    let { component } = this
-
+  handlePointerUp = (pev: PointerDragEvent) => {
     if (this.dragSelection) {
 
-      // the selection is already rendered, so just need to report it
-      browserContext.reportDateSelection(component.getCalendar(), this.dragSelection, ev)
+      // selection is already rendered, so just need to report selection
+      this.component.getCalendar().triggerDateSelect(this.dragSelection, pev)
 
       this.dragSelection = null
-
-    // only unselect if this component has a selection.
-    // otherwise, we might be clearing another component's new selection in the same calendar.
-    } else if (!wasTouchScroll && component.dateSelection) {
-      let unselectAuto = component.opt('unselectAuto')
-      let unselectCancel = component.opt('unselectCancel')
-
-      if (unselectAuto && (!unselectAuto || !elementClosest(downEl, unselectCancel))) {
-        browserContext.unselectDates(ev)
-      }
     }
   }
 

+ 8 - 21
src/interactions/EventDragging.ts

@@ -16,6 +16,8 @@ import EventApi from '../api/EventApi'
 
 export default class EventDragging { // TODO: rename to EventSelectingAndDragging
 
+  static SELECTOR = '.fc-draggable, .fc-resizable' // TODO: test this in IE11
+
   component: DateComponent
   dragging: FeaturefulElementDragging
   hitDragging: HitDragging
@@ -33,7 +35,7 @@ export default class EventDragging { // TODO: rename to EventSelectingAndDraggin
     this.component = component
 
     let dragging = this.dragging = new FeaturefulElementDragging(component.el)
-    dragging.pointer.selector = '.fc-draggable, .fc-resizable' // TODO: test this in IE11
+    dragging.pointer.selector = EventDragging.SELECTOR
     dragging.touchScrollAllowed = false
     dragging.autoScroller.isEnabled = component.opt('dragScroll')
 
@@ -42,6 +44,7 @@ export default class EventDragging { // TODO: rename to EventSelectingAndDraggin
     hitDragging.emitter.on('pointerdown', this.handlePointerDown)
     hitDragging.emitter.on('dragstart', this.handleDragStart)
     hitDragging.emitter.on('hitupdate', this.handleHitUpdate)
+    hitDragging.emitter.on('pointerup', this.handlePointerUp)
     hitDragging.emitter.on('dragend', this.handleDragEnd)
   }
 
@@ -92,21 +95,13 @@ export default class EventDragging { // TODO: rename to EventSelectingAndDraggin
     let eventInstanceId = eventRange.instance.instanceId
 
     if (ev.isTouch) {
-
       // need to select a different event?
       if (eventInstanceId !== this.component.eventSelection) {
-
-        initialCalendar.dispatch({
-          type: 'SELECT_EVENT',
-          eventInstanceId: eventInstanceId
-        })
-
-        browserContext.reportEventSelection(this.component) // will unselect previous
+        initialCalendar.dispatch({ type: 'SELECT_EVENT', eventInstanceId })
       }
-
-    // if now using mouse, but was previous touch interaction, clear selected event
     } else {
-      browserContext.unselectEvent()
+      // if now using mouse, but was previous touch interaction, clear selected event
+      initialCalendar.dispatch({ type: 'UNSELECT_EVENT' })
     }
 
     if (this.isDragging) {
@@ -189,15 +184,7 @@ export default class EventDragging { // TODO: rename to EventSelectingAndDraggin
     }
   }
 
-  onDocumentPointerUp = (ev: PointerDragEvent, wasTouchScroll: boolean) => {
-    if (
-      !wasTouchScroll &&
-      !this.subjectSeg && // no events were selected/dragged during this interaction
-      browserContext.eventSelectedComponent === this.component // this component owned the previous event selection
-    ) {
-      browserContext.unselectEvent()
-    }
-
+  handlePointerUp = () => {
     if (!this.isDragging) {
       this.cleanup() // because handleDragEnd won't fire
     }

+ 3 - 4
src/reducers/main.ts

@@ -7,7 +7,6 @@ import { EventInteractionUiState } from '../interactions/event-interaction-state
 import { CalendarState, Action } from './types'
 import { EventSourceHash } from '../structs/event-source'
 import { computeEventDefUis } from '../component/event-rendering'
-import browserContext from '../common/browser-context'
 
 export default function(state: CalendarState, action: Action, calendar: Calendar): CalendarState {
   calendar.publiclyTrigger(action.type, action) // for testing hooks
@@ -21,7 +20,7 @@ export default function(state: CalendarState, action: Action, calendar: Calendar
     eventStore: reduceEventStore(state.eventStore, action, eventSources, dateProfile, calendar),
     eventUis: state.eventUis, // TODO: should really be internal state
     businessHours: state.businessHours,
-    dateSelection: reduceDateSelection(state.dateSelection, action),
+    dateSelection: reduceDateSelection(state.dateSelection, action, calendar),
     eventSelection: reduceSelectedEvent(state.eventSelection, action),
     eventDrag: reduceEventDrag(state.eventDrag, action, eventSources, calendar),
     eventResize: reduceEventResize(state.eventResize, action, eventSources, calendar),
@@ -41,7 +40,7 @@ function reduceDateProfile(currentDateProfile: DateProfile | null, action: Actio
   }
 }
 
-function reduceDateSelection(currentSelection: DateSpan | null, action: Action) {
+function reduceDateSelection(currentSelection: DateSpan | null, action: Action, calendar: Calendar) {
   switch (action.type) {
 
     case 'SELECT_DATES':
@@ -51,7 +50,7 @@ function reduceDateSelection(currentSelection: DateSpan | null, action: Action)
       // clear selection if dates are changing.
       // we need to notify the global context that his happened (strange place to do this).
       if (currentSelection) {
-        browserContext.unselectDates()
+        calendar.unselect()
       } // will fall thru...
 
     case 'UNSELECT_DATES':