فهرست منبع

use context more. smarter use of options

Adam Shaw 5 سال پیش
والد
کامیت
827284ac82

+ 1 - 1
packages-premium

@@ -1 +1 @@
-Subproject commit d96810c9170913cc15f4500ae7a507855e36ec92
+Subproject commit 77fae276578dadf5505d950ac416b12221c2f1b3

+ 13 - 44
packages/core/src/Calendar.tsx

@@ -23,12 +23,12 @@ import { EventHovering } from './interactions/EventHovering'
 import { render, h, createRef, flushToDom } from './vdom'
 import { TaskRunner, DelayedRunner } from './util/runner'
 import { ViewApi } from './ViewApi'
-import { removeExact } from './util/array'
 import { guid } from './util/misc'
 import { CssDimValue } from './scrollgrid/util'
 import { applyStyleProp } from './util/dom-manip'
 import { CalendarStateReducer } from './reducers/CalendarStateReducer'
 import { getNow } from './reducers/current-date'
+import { ReducerContext } from './reducers/ReducerContext'
 
 
 export interface DateClickApi extends DatePointApi {
@@ -46,13 +46,11 @@ export type DatePointTransform = (dateSpan: DateSpan, calendar: Calendar) => any
 export type DateSpanTransform = (dateSpan: DateSpan, calendar: Calendar) => any
 
 export type CalendarInteraction = { destroy() }
-export type CalendarInteractionClass = { new(calendar: Calendar): CalendarInteraction }
+export type CalendarInteractionClass = { new(context: ReducerContext): CalendarInteraction }
 
 export type OptionChangeHandler = (propValue: any, calendar: Calendar) => void
 export type OptionChangeHandlerMap = { [propName: string]: OptionChangeHandler }
 
-export type ResizeHandler = (force: boolean) => void
-
 
 export class Calendar {
 
@@ -66,14 +64,12 @@ export class Calendar {
   el: HTMLElement
   currentClassNames: string[] = []
   componentRef = createRef<CalendarComponent>()
-  view: ViewApi // public API
 
   // interaction
   calendarInteractions: CalendarInteraction[]
   interactionsStore: { [componentUid: string]: Interaction[] } = {}
 
-  private resizeHandlers: ResizeHandler[] = [] // TODO: use emitter somehow?
-
+  get view() { return this.state.viewApi } // for public API
   get component() { return this.componentRef.current } // used to get view-specific business hours :(
 
 
@@ -93,7 +89,6 @@ export class Calendar {
       }
     )
 
-    this.emitter.trigger('_init') // for tests
     this.dispatch({
       type: 'INIT',
       optionOverrides
@@ -102,7 +97,7 @@ export class Calendar {
     // must go after INIT
     this.calendarInteractions = this.state.pluginHooks.calendarInteractions
       .map((calendarInteractionClass) => {
-        return new calendarInteractionClass(this)
+        return new calendarInteractionClass(this.state)
       })
   }
 
@@ -138,7 +133,7 @@ export class Calendar {
   // -----------------------------------------------------------------------------------------------------------------
 
 
-  dispatch(action: Action) {
+  dispatch = (action: Action) => {
     this.actionRunner.request(action)
 
     // actions we know we want to render immediately. TODO: another param in dispatch instead?
@@ -264,17 +259,7 @@ export class Calendar {
 
 
   getOption(name: string) { // getter, used externally
-    return this.state.options[name]
-  }
-
-
-  opt(name: string) { // getter, used internally
-    return this.state.options[name]
-  }
-
-
-  viewOpt(name: string) { // getter, used internally
-    return this.state.viewSpecs[this.state.viewType].options[name]
+    return this.state.calendarOptions[name]
   }
 
 
@@ -512,12 +497,12 @@ export class Calendar {
 
   // `settings` is for formatter AND isEndExclusive
   formatRange(d0: DateInput, d1: DateInput, settings) {
-    let { dateEnv } = this.state
+    let { dateEnv, options } = this.state
 
     return dateEnv.formatRange(
       dateEnv.createMarker(d0),
       dateEnv.createMarker(d1),
-      createFormatter(settings, this.opt('defaultRangeSeparator')),
+      createFormatter(settings, options.defaultRangeSeparator),
       settings
     )
   }
@@ -535,7 +520,7 @@ export class Calendar {
 
 
   updateSize() { // public
-    this.triggerResizeHandlers(true)
+    this.emitter.trigger('_resize', true)
     flushToDom()
   }
 
@@ -545,7 +530,7 @@ export class Calendar {
 
 
   resizeRunner = new DelayedRunner(() => {
-    this.triggerResizeHandlers(true) // should window resizes be considered "forced" ?
+    this.emitter.trigger('_resize', true) // should window resizes be considered "forced" ?
     this.emitter.trigger('windowResize')
   })
 
@@ -562,23 +547,6 @@ export class Calendar {
   }
 
 
-  addResizeHandler = (handler: ResizeHandler) => {
-    this.resizeHandlers.push(handler)
-  }
-
-
-  removeResizeHandler = (handler: ResizeHandler) => {
-    removeExact(this.resizeHandlers, handler)
-  }
-
-
-  triggerResizeHandlers(forced: boolean) {
-    for (let handler of this.resizeHandlers) {
-      handler(forced)
-    }
-  }
-
-
   // Component Registration
   // -----------------------------------------------------------------------------------------------------------------
 
@@ -663,7 +631,7 @@ export class Calendar {
     const arg = {
       ...this.buildDateSpanApi(selection),
       jsEvent: pev ? pev.origEvent as MouseEvent : null, // Is this always a mouse event? See #4655
-      view: this.view
+      view: this.state.viewApi
     }
 
     this.emitter.trigger('select', arg)
@@ -673,7 +641,7 @@ export class Calendar {
   triggerDateUnselect(pev?: PointerDragEvent) {
     this.emitter.trigger('unselect', {
       jsEvent: pev ? pev.origEvent : null,
-      view: this.view
+      view: this.state.viewApi
     })
   }
 
@@ -722,6 +690,7 @@ export class Calendar {
 
 
   // Returns a DateMarker for the current date, as defined by the client's computer or from the `now` option
+  // PRIVATE use only. doesn't zone the date.
   getNow(): DateMarker {
     return getNow(this.state.options, this.state.dateEnv)
   }

+ 13 - 67
packages/core/src/CalendarComponent.tsx

@@ -3,11 +3,11 @@ import { ViewSpec } from './structs/view-spec'
 import { ViewProps } from './View'
 import { Toolbar } from './Toolbar'
 import { DateProfileGenerator, DateProfile } from './DateProfileGenerator'
-import { rangeContainsMarker, DateRange } from './datelib/date-range'
+import { rangeContainsMarker } from './datelib/date-range'
 import { EventUiHash } from './component/event-ui'
 import { parseBusinessHours } from './structs/business-hours'
 import { memoize } from './util/memoize'
-import { DateMarker, diffWholeDays } from './datelib/marker'
+import { DateMarker } from './datelib/marker'
 import { CalendarState } from './reducers/types'
 import { ViewPropsTransformerClass } from './plugin-system'
 import { __assign } from 'tslib'
@@ -19,8 +19,6 @@ import { CssDimValue } from './scrollgrid/util'
 import { Theme } from './theme/Theme'
 import { getCanVGrowWithinCell } from './util/table-styling'
 import { ViewComponent } from './structs/view-config'
-import { createFormatter } from './datelib/formatting'
-import { DateEnv } from './datelib/env'
 import { Calendar } from './Calendar'
 import { Emitter } from './common/Emitter'
 
@@ -45,7 +43,6 @@ export class CalendarComponent extends Component<CalendarComponentProps, Calenda
 
   context: never
 
-  private computeTitle = memoize(computeTitle)
   private buildViewContext = memoize(buildViewContext)
   private parseBusinessHours = memoize((input) => parseBusinessHours(input, this.props))
   private buildViewPropTransformers = memoize(buildViewPropTransformers)
@@ -68,8 +65,7 @@ export class CalendarComponent extends Component<CalendarComponentProps, Calenda
   renders INSIDE of an outer div
   */
   render(props: CalendarComponentProps, state: CalendarComponentState) {
-    let { toolbarConfig, theme, dateEnv, options, calendar } = props
-    let viewTitle = this.computeTitle(props.dateProfile, dateEnv, options)
+    let { toolbarConfig, theme, options, calendar } = props
 
     let toolbarProps = this.buildToolbarProps(
       props.viewSpec,
@@ -77,7 +73,7 @@ export class CalendarComponent extends Component<CalendarComponentProps, Calenda
       props.dateProfileGenerator,
       props.currentDate,
       calendar.getNow(), // TODO: use NowTimer????
-      viewTitle
+      props.viewTitle
     )
 
     let calendarHeight: string | number = ''
@@ -106,7 +102,9 @@ export class CalendarComponent extends Component<CalendarComponentProps, Calenda
 
     let viewContext = this.buildViewContext(
       props.viewSpec,
-      viewTitle,
+      props.viewApi,
+      props.options,
+      props.computedOptions,
       props.dateProfile,
       props.dateProfileGenerator,
       props.dateEnv,
@@ -188,7 +186,7 @@ export class CalendarComponent extends Component<CalendarComponentProps, Calenda
 
 
   _handleNavLinkClick(ev: UIEvent, anchorEl: HTMLElement) {
-    let { dateEnv, calendar } = this.props
+    let { dateEnv, options, calendar } = this.props
 
     let navLinkOptions: any = anchorEl.getAttribute('data-navlink')
     navLinkOptions = navLinkOptions ? JSON.parse(navLinkOptions) : {}
@@ -197,7 +195,7 @@ export class CalendarComponent extends Component<CalendarComponentProps, Calenda
     let viewType = navLinkOptions.type
 
     // property like "navLinkDayClick". might be a string or a function
-    let customAction = calendar.viewOpt('navLink' + capitaliseFirstLetter(viewType) + 'Click')
+    let customAction = options['navLink' + capitaliseFirstLetter(viewType) + 'Click']
 
     if (typeof customAction === 'function') {
       customAction(dateEnv.toDate(dateMarker), ev)
@@ -213,10 +211,10 @@ export class CalendarComponent extends Component<CalendarComponentProps, Calenda
 
 
   buildAppendContent() {
-    let { pluginHooks, calendar } = this.props
+    let { props } = this
 
-    return pluginHooks.viewContainerAppends.map(
-      (buildAppendContent) => buildAppendContent(calendar)
+    return props.pluginHooks.viewContainerAppends.map(
+      (buildAppendContent) => buildAppendContent(props)
     )
   }
 
@@ -226,7 +224,7 @@ export class CalendarComponent extends Component<CalendarComponentProps, Calenda
     let { viewSpec } = props
 
     let viewProps: ViewProps = {
-      businessHours: this.parseBusinessHours(viewSpec.options.businessHours),
+      businessHours: this.parseBusinessHours(options.businessHours),
       eventStore: props.eventStore,
       eventUiBases: props.eventUiBases,
       dateSelection: props.dateSelection,
@@ -328,55 +326,3 @@ function buildViewPropTransformers(theClasses: ViewPropsTransformerClass[]) {
     return new theClass()
   })
 }
-
-
-// Title and Date Formatting
-// -----------------------------------------------------------------------------------------------------------------
-
-
-// Computes what the title at the top of the calendar should be for this view
-function computeTitle(dateProfile, dateEnv: DateEnv, viewOptions) {
-  let range: DateRange
-
-  // for views that span a large unit of time, show the proper interval, ignoring stray days before and after
-  if (/^(year|month)$/.test(dateProfile.currentRangeUnit)) {
-    range = dateProfile.currentRange
-  } else { // for day units or smaller, use the actual day range
-    range = dateProfile.activeRange
-  }
-
-  return dateEnv.formatRange(
-    range.start,
-    range.end,
-    createFormatter(
-      viewOptions.titleFormat || computeTitleFormat(dateProfile),
-      viewOptions.titleRangeSeparator
-    ),
-    { isEndExclusive: dateProfile.isRangeAllDay }
-  )
-}
-
-
-// Generates the format string that should be used to generate the title for the current date range.
-// Attempts to compute the most appropriate format if not explicitly specified with `titleFormat`.
-function computeTitleFormat(dateProfile) {
-  let currentRangeUnit = dateProfile.currentRangeUnit
-
-  if (currentRangeUnit === 'year') {
-    return { year: 'numeric' }
-  } else if (currentRangeUnit === 'month') {
-    return { year: 'numeric', month: 'long' } // like "September 2014"
-  } else {
-    let days = diffWholeDays(
-      dateProfile.currentRange.start,
-      dateProfile.currentRange.end
-    )
-    if (days !== null && days > 1) {
-      // multi-day range. shorter, like "Sep 9 - 10 2014"
-      return { year: 'numeric', month: 'short', day: 'numeric' }
-    } else {
-      // one day. longer, like "September 9 2014"
-      return { year: 'numeric', month: 'long', day: 'numeric' }
-    }
-  }
-}

+ 8 - 10
packages/core/src/DateProfileGenerator.ts

@@ -4,6 +4,7 @@ import { DateRange, OpenDateRange, constrainMarkerToRange, intersectRanges, rang
 import { ViewSpec } from './structs/view-spec'
 import { DateEnv } from './datelib/env'
 import { computeVisibleDayRange } from './util/misc'
+import { getNow } from './reducers/current-date'
 
 
 export interface DateProfile {
@@ -24,21 +25,18 @@ export class DateProfileGenerator {
 
   slotMinTime: Duration
   slotMaxTime: Duration
-  options: any
+  nowDate: DateMarker
   isHiddenDayHash: boolean[]
 
 
   constructor(
     protected viewSpec: ViewSpec,
-    protected dateEnv: DateEnv,
-    protected nowDate: DateMarker
+    protected options: any,
+    protected dateEnv: DateEnv
   ) {
-    this.viewSpec = viewSpec
-
-    this.options = viewSpec.options
-    this.slotMinTime = createDuration(viewSpec.options.slotMinTime)
-    this.slotMaxTime = createDuration(viewSpec.options.slotMaxTime)
-
+    this.slotMinTime = createDuration(options.slotMinTime)
+    this.slotMaxTime = createDuration(options.slotMaxTime)
+    this.nowDate = getNow(options, dateEnv)
     this.initHiddenDays()
   }
 
@@ -200,7 +198,7 @@ export class DateProfileGenerator {
     let start = range.start
     let end = range.end
 
-    if (this.viewSpec.options.usesMinMaxTime) {
+    if (this.viewSpec.optionDefaults.usesMinMaxTime) {
 
       // expand active range if slotMinTime is negative (why not when positive?)
       if (asRoughDays(slotMinTime) < 0) {

+ 0 - 35
packages/core/src/OptionsManager.ts

@@ -1,35 +0,0 @@
-import { firstDefined } from './util/misc'
-import { globalDefaults, mergeOptions } from './options'
-import { organizeRawLocales, buildLocale } from './datelib/locale'
-import { __assign } from 'tslib'
-
-
-export function compileOptions(overrides, dynamicOverrides, viewDefaults?, viewOverrides?) {
-  let locales = firstDefined( // explicit locale option given?
-    dynamicOverrides.locales,
-    overrides.locales,
-    globalDefaults.locales
-  )
-
-  let locale = firstDefined( // explicit locales option given?
-    dynamicOverrides.locale,
-    overrides.locale,
-    globalDefaults.locale
-  )
-
-  let availableLocaleData = organizeRawLocales(locales)
-  let localeDefaults = buildLocale(locale || availableLocaleData.defaultCode, availableLocaleData.map).options
-
-  return {
-    availableLocaleData,
-    localeDefaults,
-    options: mergeOptions([ // merge defaults and overrides. lowest to highest precedence
-      globalDefaults, // global defaults
-      viewDefaults || {},
-      localeDefaults,
-      overrides,
-      viewOverrides || {},
-      dynamicOverrides
-    ])
-  }
-}

+ 8 - 5
packages/core/src/ScrollResponder.ts

@@ -1,5 +1,5 @@
 import { Duration, createDuration } from './datelib/duration'
-import { Calendar } from './Calendar'
+import { ReducerContext } from './reducers/ReducerContext'
 import { __assign } from 'tslib'
 
 
@@ -16,14 +16,17 @@ export class ScrollResponder {
   queuedRequest: ScrollRequest
 
 
-  constructor(public calendar: Calendar, public execFunc: ScrollRequestHandler) {
-    calendar.on('_scrollRequest', this.handleScrollRequest)
+  constructor(
+    public execFunc: ScrollRequestHandler,
+    private context: ReducerContext
+  ) {
+    context.emitter.on('_scrollRequest', this.handleScrollRequest)
     this.fireInitialScroll()
   }
 
 
   detach() {
-    this.calendar.off('_scrollRequest', this.handleScrollRequest)
+    this.context.emitter.off('_scrollRequest', this.handleScrollRequest)
   }
 
 
@@ -38,7 +41,7 @@ export class ScrollResponder {
 
   private fireInitialScroll() {
     this.handleScrollRequest({
-      time: createDuration(this.calendar.viewOpt('scrollTime'))
+      time: createDuration(this.context.options.scrollTime)
     })
   }
 

+ 23 - 0
packages/core/src/ViewApi.ts

@@ -1,3 +1,5 @@
+import { DateEnv } from './datelib/env';
+import { DateProfile } from './DateProfileGenerator';
 
 export interface ViewApi {
   type: string
@@ -7,3 +9,24 @@ export interface ViewApi {
   currentStart: Date
   currentEnd: Date
 }
+
+export class ViewApi {
+
+  constructor(
+    public type: string,
+    dateProfile: DateProfile,
+    public title: string,
+    private options: any,
+    dateEnv: DateEnv
+  ) {
+    this.activeStart = dateEnv.toDate(dateProfile.activeRange.start)
+    this.activeEnd = dateEnv.toDate(dateProfile.activeRange.end)
+    this.currentStart = dateEnv.toDate(dateProfile.currentRange.start)
+    this.currentEnd = dateEnv.toDate(dateProfile.currentRange.end)
+  }
+
+  getOption(name: string) {
+    return this.options[name]
+  }
+
+}

+ 2 - 2
packages/core/src/api/EventApi.ts

@@ -179,7 +179,7 @@ export class EventApi {
     let maintainDuration = options.maintainDuration
 
     if (maintainDuration == null) {
-      maintainDuration = this._calendar.opt('allDayMaintainDuration')
+      maintainDuration = this._calendar.state.options.allDayMaintainDuration
     }
 
     if (this._def.allDay !== allDay) {
@@ -192,7 +192,7 @@ export class EventApi {
   formatRange(formatInput: FormatterInput) {
     let { dateEnv } = this._calendar.state
     let instance = this._instance
-    let formatter = createFormatter(formatInput, this._calendar.opt('defaultRangeSeparator'))
+    let formatter = createFormatter(formatInput, this._calendar.state.options.defaultRangeSeparator)
 
     if (this._def.hasEnd) {
       return dateEnv.formatRange(instance.range.start, instance.range.end, formatter, {

+ 25 - 31
packages/core/src/component/ComponentContext.ts

@@ -1,5 +1,4 @@
 import { Calendar } from '../Calendar'
-import { ResizeHandler } from '../Calendar'
 import { ViewApi } from '../ViewApi'
 import { Theme } from '../theme/Theme'
 import { DateEnv } from '../datelib/env'
@@ -8,19 +7,19 @@ import { createContext } from '../vdom'
 import { ScrollResponder, ScrollRequestHandler } from '../ScrollResponder'
 import { DateProfile, DateProfileGenerator } from '../DateProfileGenerator'
 import { ViewSpec } from '../structs/view-spec'
-import { ReducerContext, buildComputedOptions } from '../reducers/ReducerContext'
+import { ReducerContext } from '../reducers/ReducerContext'
 import { Action } from '../reducers/types'
 import { Emitter } from '../common/Emitter'
 
-
 export const ComponentContextType = createContext<ComponentContext>({} as any) // for Components
+export type ResizeHandler = (force: boolean) => void
 
 // TODO: rename file
 // TODO: rename to ViewContext
 
 export interface ComponentContext extends ReducerContext {
-  isRtl: boolean
   theme: Theme
+  isRtl: boolean
   dateProfileGenerator: DateProfileGenerator
   dateProfile: DateProfile
   viewSpec: ViewSpec
@@ -30,10 +29,11 @@ export interface ComponentContext extends ReducerContext {
   createScrollResponder: (execFunc: ScrollRequestHandler) => ScrollResponder
 }
 
-
 export function buildViewContext(
   viewSpec: ViewSpec,
-  viewTitle: string,
+  viewApi: ViewApi,
+  options: any,
+  computedOptions: any,
   dateProfile: DateProfile,
   dateProfileGenerator: DateProfileGenerator,
   dateEnv: DateEnv,
@@ -43,38 +43,32 @@ export function buildViewContext(
   emitter: Emitter,
   calendar: Calendar
 ): ComponentContext {
-  let { options } = viewSpec
+  let reducerContext: ReducerContext = {
+    dateEnv,
+    options,
+    computedOptions,
+    pluginHooks,
+    emitter,
+    dispatch,
+    calendar
+  }
 
   return {
+    ...reducerContext,
     viewSpec,
-    viewApi: buildViewApi(viewSpec, viewTitle, dateProfile, dateEnv),
+    viewApi,
     dateProfile,
     dateProfileGenerator,
-    dateEnv,
-    isRtl: options.direction === 'rtl',
     theme,
-    options,
-    computedOptions: buildComputedOptions(options),
-    pluginHooks,
-    dispatch,
-    emitter,
-    calendar,
-    addResizeHandler: calendar.addResizeHandler,
-    removeResizeHandler: calendar.removeResizeHandler,
+    isRtl: options.direction === 'rtl',
+    addResizeHandler(handler: ResizeHandler) {
+      emitter.on('_resize', handler)
+    },
+    removeResizeHandler(handler: ResizeHandler) {
+      emitter.off('_resize', handler)
+    },
     createScrollResponder(execFunc: ScrollRequestHandler) {
-      return new ScrollResponder(calendar, execFunc)
+      return new ScrollResponder(execFunc, reducerContext)
     }
   }
 }
-
-
-function buildViewApi(viewSpec: ViewSpec, viewTitle: string, dateProfile: DateProfile, dateEnv: DateEnv) {
-  return {
-    type: viewSpec.type,
-    title: viewTitle,
-    activeStart: dateEnv.toDate(dateProfile.activeRange.start),
-    activeEnd: dateEnv.toDate(dateProfile.activeRange.end),
-    currentStart: dateEnv.toDate(dateProfile.currentRange.start),
-    currentEnd: dateEnv.toDate(dateProfile.currentRange.end)
-  }
-}

+ 1 - 1
packages/core/src/event-sources/func-event-source.ts

@@ -28,7 +28,7 @@ let eventSourceDef: EventSourceDef = {
   },
 
   fetch(arg, success, failure) {
-    let dateEnv = arg.calendar.state.dateEnv
+    let dateEnv = arg.context.dateEnv
     let func = arg.eventSource.meta as EventSourceFunc
 
     unpromisify(

+ 7 - 7
packages/core/src/event-sources/json-feed-event-source.ts

@@ -1,5 +1,5 @@
 import { requestJson } from '../util/requestJson'
-import { Calendar } from '../Calendar'
+import { ReducerContext } from '../reducers/ReducerContext'
 import { EventSourceDef } from '../structs/event-source'
 import { DateRange } from '../datelib/date-range'
 import { __assign } from 'tslib'
@@ -35,7 +35,7 @@ let eventSourceDef: EventSourceDef = {
 
   fetch(arg, success, failure) {
     let meta: JsonFeedMeta = arg.eventSource.meta
-    let requestParams = buildRequestParams(meta, arg.range, arg.calendar)
+    let requestParams = buildRequestParams(meta, arg.range, arg.context)
 
     requestJson(
       meta.method, meta.url, requestParams,
@@ -54,8 +54,8 @@ export const jsonFeedEventSourcePlugin = createPlugin({
   eventSourceDefs: [ eventSourceDef ]
 })
 
-function buildRequestParams(meta: JsonFeedMeta, range: DateRange, calendar: Calendar) {
-  const dateEnv = calendar.state.dateEnv
+function buildRequestParams(meta: JsonFeedMeta, range: DateRange, context: ReducerContext) {
+  let { dateEnv, options } = context
   let startParam
   let endParam
   let timeZoneParam
@@ -64,17 +64,17 @@ function buildRequestParams(meta: JsonFeedMeta, range: DateRange, calendar: Cale
 
   startParam = meta.startParam
   if (startParam == null) {
-    startParam = calendar.opt('startParam')
+    startParam = options.startParam
   }
 
   endParam = meta.endParam
   if (endParam == null) {
-    endParam = calendar.opt('endParam')
+    endParam = options.endParam
   }
 
   timeZoneParam = meta.timeZoneParam
   if (timeZoneParam == null) {
-    timeZoneParam = calendar.opt('timeZoneParam')
+    timeZoneParam = options.timeZoneParam
   }
 
   // retrieve any outbound GET/POST data from the options

+ 3 - 2
packages/core/src/plugin-system.ts

@@ -1,11 +1,12 @@
 import { reducerFunc } from './reducers/types'
 import { eventDefParserFunc } from './structs/event'
 import { eventDefMutationApplier } from './structs/event-mutation'
-import { Calendar, DatePointTransform, DateSpanTransform, CalendarInteractionClass, OptionChangeHandlerMap } from './Calendar'
+import { DatePointTransform, DateSpanTransform, CalendarInteractionClass, OptionChangeHandlerMap } from './Calendar'
 import { ViewConfigInputHash } from './structs/view-config'
 import { ViewSpec } from './structs/view-spec'
 import { ViewProps } from './View'
 import { CalendarComponentProps } from './CalendarComponent'
+import { ReducerContext } from './reducers/ReducerContext'
 import { isPropsValidTester } from './validation'
 import { eventDragMutationMassager, eventIsDraggableTransformer, EventDropTransformers } from './interactions/event-dragging'
 import { dateSelectionJoinTransformer } from './interactions/date-selecting'
@@ -98,7 +99,7 @@ export interface ViewPropsTransformer {
   transform(viewProps: ViewProps, viewSpec: ViewSpec, calendarProps: CalendarComponentProps, allOptions: any): any
 }
 
-export type ViewContainerAppend = (calendar: Calendar) => ComponentChildren
+export type ViewContainerAppend = (context: ReducerContext) => ComponentChildren
 
 
 export function createPlugin(input: PluginDefInput): PluginDef {

+ 155 - 62
packages/core/src/reducers/CalendarStateReducer.ts

@@ -1,17 +1,16 @@
-import { buildLocale, RawLocaleInfo } from '../datelib/locale'
-import { memoize } from '../util/memoize'
+import { buildLocale, RawLocaleInfo, organizeRawLocales } from '../datelib/locale'
+import { memoize, memoizeObjArg } from '../util/memoize'
 import { Action, CalendarState } from './types'
 import { PluginHooks, buildPluginHooks } from '../plugin-system'
 import { DateEnv } from '../datelib/env'
-import { compileOptions } from '../OptionsManager'
 import { Calendar } from '../Calendar'
 import { StandardTheme } from '../theme/StandardTheme'
 import { EventSourceHash } from '../structs/event-source'
-import { buildViewSpecs, ViewSpecHash, ViewSpec } from '../structs/view-spec'
+import { buildViewSpecs, ViewSpec } from '../structs/view-spec'
 import { mapHash, isPropsEqual } from '../util/object'
-import { DateProfileGenerator } from '../DateProfileGenerator'
+import { DateProfileGenerator, DateProfile } from '../DateProfileGenerator'
 import { reduceViewType } from './view-type'
-import { reduceCurrentDate, getInitialDate, getNow } from './current-date'
+import { reduceCurrentDate, getInitialDate } from './current-date'
 import { reduceDateProfile } from './date-profile'
 import { reduceEventSources } from './eventSources'
 import { reduceEventStore } from './eventStore'
@@ -24,21 +23,32 @@ import { ReducerContext, buildComputedOptions } from './ReducerContext'
 import { processScopedUiProps, EventUiHash, EventUi } from '../component/event-ui'
 import { EventDefHash } from '../structs/event'
 import { parseToolbars } from '../toolbar-parse'
+import { firstDefined } from '../util/misc'
+import { globalDefaults, mergeOptions } from '../options'
+import { diffWholeDays } from '../datelib/marker'
+import { createFormatter } from '../datelib/formatting'
+import { DateRange } from '../datelib/date-range'
+import { ViewApi } from '../ViewApi'
 
 
 export class CalendarStateReducer {
 
-  private compileOptions = memoize(compileOptions)
   private buildPluginHooks = memoize(buildPluginHooks)
   private buildDateEnv = memoize(buildDateEnv)
   private buildTheme = memoize(buildTheme)
   private buildViewSpecs = memoize(buildViewSpecs)
-  private buildDateProfileGenerator = memoize(buildDateProfileGenerators)
+  private buildDateProfileGenerator = memoize(buildDateProfileGenerator)
   private buildComputedOptions = memoize(buildComputedOptions)
-  private buildViewUiProps = memoize(buildViewUiProps)
+  private buildViewUiProps = memoizeObjArg(buildViewUiProps)
   private buildEventUiBySource = memoize(buildEventUiBySource, isPropsEqual)
   private buildEventUiBases = memoize(buildEventUiBases)
   private parseToolbars = memoize(parseToolbars)
+  private organizeRawLocales = memoize(organizeRawLocales)
+  private buildCalendarOptions = memoize(mergeOptionSets)
+  private buildViewOptions = memoize(mergeOptionSets)
+  private computeTitle = memoize(computeTitle)
+  private buildViewApi = memoize(buildViewApi)
+  private buildLocale = memoize(buildLocale)
 
 
   reduce(state: CalendarState, action: Action, emitter: Emitter, calendar: Calendar): CalendarState {
@@ -69,40 +79,75 @@ export class CalendarStateReducer {
         break
     }
 
-    let { options, availableLocaleData } = this.compileOptions(optionOverrides, dynamicOptionOverrides)
-    emitter.setOptions(options)
+    let locales = firstDefined( // explicit locale option given?
+      dynamicOptionOverrides.locales,
+      optionOverrides.locales,
+      globalDefaults.locales
+    )
+
+    let locale = firstDefined( // explicit locales option given?
+      dynamicOptionOverrides.locale,
+      optionOverrides.locale,
+      globalDefaults.locale
+    )
+
+    let availableLocaleData = this.organizeRawLocales(locales)
+    let localeDefaults = this.buildLocale(locale || availableLocaleData.defaultCode, availableLocaleData.map).options
+
+    let calendarOptions = this.buildCalendarOptions( // NOTE: use viewOptions mostly instead
+      globalDefaults, // global defaults
+      localeDefaults,
+      optionOverrides,
+      dynamicOptionOverrides
+    )
+
+    let pluginHooks = this.buildPluginHooks(calendarOptions.plugins)
 
-    let pluginHooks = this.buildPluginHooks(options.plugins)
-    let viewSpecs = this.buildViewSpecs(pluginHooks.views, optionOverrides, dynamicOptionOverrides)
     let prevDateEnv = state ? state.dateEnv : null
     let dateEnv = this.buildDateEnv(
-      options.timeZone,
-      options.locale,
-      options.weekNumberCalculation,
-      options.firstDay,
-      options.weekText,
+      calendarOptions.timeZone,
+      calendarOptions.locale,
+      calendarOptions.weekNumberCalculation,
+      calendarOptions.firstDay,
+      calendarOptions.weekText,
       pluginHooks,
       availableLocaleData
     )
-    let dateProfileGenerators = this.buildDateProfileGenerator(viewSpecs, dateEnv)
-    let theme = this.buildTheme(options, pluginHooks)
+    let theme = this.buildTheme(calendarOptions, pluginHooks)
+
+    let viewSpecs = this.buildViewSpecs(pluginHooks.views, optionOverrides, dynamicOptionOverrides, localeDefaults)
+    let viewType = state.viewType || calendarOptions.initialView || pluginHooks.initialView // weird how we do INIT
+    viewType = reduceViewType(viewType, action, viewSpecs)
+    let viewSpec = viewSpecs[viewType]
+
+    let viewOptions = this.buildViewOptions( // merge defaults and overrides. lowest to highest precedence
+      globalDefaults, // global defaults
+      viewSpec.optionDefaults,
+      localeDefaults,
+      optionOverrides,
+      viewSpec.optionOverrides,
+      dynamicOptionOverrides
+    )
+
+    emitter.setOptions(viewOptions)
+
+    if (action.type === 'INIT') {
+      emitter.trigger('_init') // for tests. needs to happen after emitter.setOptions
+    }
 
     let dispatch = state.dispatch || calendar.dispatch.bind(calendar) // will reuse past functions! TODO: memoize? TODO: calendar should bind?
     let reducerContext: ReducerContext = {
       dateEnv,
-      options,
-      computedOptions: this.buildComputedOptions(options),
+      options: viewOptions,
+      computedOptions: this.buildComputedOptions(viewOptions),
       pluginHooks,
       emitter,
       dispatch,
       calendar
     }
 
-    let viewType = state.viewType || options.initialView || pluginHooks.initialView // weird how we do INIT
-    viewType = reduceViewType(viewType, action, pluginHooks.views)
-
-    let currentDate = state.currentDate || getInitialDate(options, dateEnv) // weird how we do INIT
-    let dateProfileGenerator = dateProfileGenerators[viewType]
+    let currentDate = state.currentDate || getInitialDate(viewOptions, dateEnv) // weird how we do INIT
+    let dateProfileGenerator = this.buildDateProfileGenerator(viewSpec, viewOptions, dateEnv)
     let dateProfile = reduceDateProfile(state.dateProfile, action, currentDate, dateProfileGenerator)
     currentDate = reduceCurrentDate(currentDate, action, dateProfile)
 
@@ -111,18 +156,11 @@ export class CalendarStateReducer {
     let eventStore = reduceEventStore(state.eventStore, action, eventSources, dateProfile, prevDateEnv, reducerContext)
 
     let renderableEventStore =
-      (eventSourceLoadingLevel && !options.progressiveEventRendering) ?
+      (eventSourceLoadingLevel && !viewOptions.progressiveEventRendering) ?
         (state.renderableEventStore || eventStore) : // try from previous state
         eventStore
 
-    let { eventUiSingleBase, selectionConfig } = this.buildViewUiProps(
-      viewSpecs[viewType],
-      dateEnv,
-      pluginHooks,
-      emitter,
-      dispatch,
-      calendar
-    )
+    let { eventUiSingleBase, selectionConfig } = this.buildViewUiProps(reducerContext)
     let eventUiBySource = this.buildEventUiBySource(eventSources)
     let eventUiBases = this.buildEventUiBases(renderableEventStore.defs, eventUiSingleBase, eventUiBySource)
 
@@ -135,9 +173,13 @@ export class CalendarStateReducer {
       emitter.trigger('loading', false)
     }
 
+    let viewTitle = this.computeTitle(dateProfile, viewOptions, dateEnv)
+    let viewApi = this.buildViewApi(viewSpec.type, dateProfile, viewTitle, viewOptions, dateEnv)
+
     let nextState: CalendarState = {
       ...(state as object), // preserve previous state from plugin reducers. tho remove type to make sure all data is provided right now
       ...reducerContext,
+      calendarOptions,
       optionOverrides,
       dynamicOptionOverrides,
       availableRawLocales: availableLocaleData.map,
@@ -158,7 +200,10 @@ export class CalendarStateReducer {
       eventSelection: reduceSelectedEvent(state.eventSelection, action),
       eventDrag: reduceEventDrag(state.eventDrag, action),
       eventResize: reduceEventResize(state.eventResize, action),
-      toolbarConfig: this.parseToolbars(options, optionOverrides, theme, viewSpecs, calendar)
+      toolbarConfig: this.parseToolbars(viewOptions, optionOverrides, theme, viewSpecs, calendar),
+      viewSpec,
+      viewTitle,
+      viewApi
     }
 
     for (let reducerFunc of pluginHooks.reducers) {
@@ -214,37 +259,22 @@ function buildTheme(rawOptions, pluginHooks: PluginHooks) {
 }
 
 
-function buildDateProfileGenerators(viewSpecs: ViewSpecHash, dateEnv: DateEnv) {
-  return mapHash(viewSpecs, (viewSpec) => {
-    let DateProfileGeneratorClass = viewSpec.options.dateProfileGeneratorClass || DateProfileGenerator
+function buildDateProfileGenerator(viewSpec: ViewSpec, viewOptions: any, dateEnv: DateEnv) {
+  let DateProfileGeneratorClass = viewSpec.optionDefaults.dateProfileGeneratorClass || DateProfileGenerator
 
-    return new DateProfileGeneratorClass(viewSpec, dateEnv, getNow(viewSpec.options, dateEnv))
-  })
+  return new DateProfileGeneratorClass(viewSpec, viewOptions, dateEnv)
 }
 
 
-function buildViewUiProps(
-  viewSpec: ViewSpec,
-  dateEnv: DateEnv,
-  pluginHooks: PluginHooks,
-  emitter: Emitter,
-  dispatch: (action: Action) => void,
-  calendar: Calendar
-) {
-  let { options } = viewSpec
-  let reducerContext: ReducerContext = {
-    dateEnv,
-    options,
-    computedOptions: buildComputedOptions(options), // bad, REPEAT work
-    pluginHooks,
-    emitter,
-    dispatch,
-    calendar
-  }
+function mergeOptionSets(...optionSets: any[]) {
+  return mergeOptions(optionSets)
+}
+
 
+function buildViewUiProps(reducerContext: ReducerContext) {
   return {
-    eventUiSingleBase: processScopedUiProps('event', options, reducerContext),
-    selectionConfig: processScopedUiProps('select', options, reducerContext)
+    eventUiSingleBase: processScopedUiProps('event', reducerContext.options, reducerContext),
+    selectionConfig: processScopedUiProps('select', reducerContext.options, reducerContext)
   }
 }
 
@@ -269,3 +299,66 @@ function buildEventUiBases(eventDefs: EventDefHash, eventUiSingleBase: EventUi,
 
   return eventUiBases
 }
+
+
+function buildViewApi(
+  type: string,
+  dateProfile: DateProfile,
+  title: string,
+  options: any,
+  dateEnv: DateEnv
+) {
+  return new ViewApi(type, dateProfile, title, options, dateEnv)
+}
+
+
+// Title and Date Formatting
+// -----------------------------------------------------------------------------------------------------------------
+
+
+// Computes what the title at the top of the calendar should be for this view
+function computeTitle(dateProfile, viewOptions, dateEnv: DateEnv) {
+  let range: DateRange
+
+  // for views that span a large unit of time, show the proper interval, ignoring stray days before and after
+  if (/^(year|month)$/.test(dateProfile.currentRangeUnit)) {
+    range = dateProfile.currentRange
+  } else { // for day units or smaller, use the actual day range
+    range = dateProfile.activeRange
+  }
+
+  return dateEnv.formatRange(
+    range.start,
+    range.end,
+    createFormatter(
+      viewOptions.titleFormat || computeTitleFormat(dateProfile),
+      viewOptions.titleRangeSeparator
+    ),
+    { isEndExclusive: dateProfile.isRangeAllDay }
+  )
+}
+
+
+// Generates the format string that should be used to generate the title for the current date range.
+// Attempts to compute the most appropriate format if not explicitly specified with `titleFormat`.
+function computeTitleFormat(dateProfile) {
+  let currentRangeUnit = dateProfile.currentRangeUnit
+
+  if (currentRangeUnit === 'year') {
+    return { year: 'numeric' }
+  } else if (currentRangeUnit === 'month') {
+    return { year: 'numeric', month: 'long' } // like "September 2014"
+  } else {
+    let days = diffWholeDays(
+      dateProfile.currentRange.start,
+      dateProfile.currentRange.end
+    )
+    if (days !== null && days > 1) {
+      // multi-day range. shorter, like "Sep 9 - 10 2014"
+      return { year: 'numeric', month: 'short', day: 'numeric' }
+    } else {
+      // one day. longer, like "September 9 2014"
+      return { year: 'numeric', month: 'long', day: 'numeric' }
+    }
+  }
+}

+ 1 - 1
packages/core/src/reducers/ReducerContext.ts

@@ -13,8 +13,8 @@ export interface ReducerContext {
   computedOptions: ComputedOptions
   pluginHooks: PluginHooks
   emitter: Emitter
-  calendar: Calendar
   dispatch(action: Action): void
+  calendar: Calendar
 }
 
 export interface ComputedOptions {

+ 2 - 2
packages/core/src/reducers/eventSources.ts

@@ -148,8 +148,8 @@ function fetchSource(eventSource: EventSource, fetchRange: DateRange, context: R
   sourceDef.fetch(
     {
       eventSource,
-      calendar: context.calendar,
-      range: fetchRange
+      range: fetchRange,
+      context
     },
     function(res) {
       let { rawEvents } = res

+ 6 - 1
packages/core/src/reducers/types.ts

@@ -9,11 +9,13 @@ import { DateSpan } from '../structs/date-span'
 import { DateMarker } from '../datelib/marker'
 import { RawLocaleMap } from '../datelib/locale'
 import { Theme } from '../theme/Theme'
-import { ViewSpecHash } from '../structs/view-spec'
+import { ViewSpecHash, ViewSpec } from '../structs/view-spec'
 import { ReducerContext } from './ReducerContext'
 import { EventUiHash, EventUi } from '../component/event-ui'
+import { ViewApi } from '../ViewApi'
 
 export interface CalendarState extends ReducerContext {
+  calendarOptions: any // NOTE: use `options` instead. view-specific
   eventSources: EventSourceHash
   eventSourceLoadingLevel: number
   eventUiBases: EventUiHash
@@ -35,6 +37,9 @@ export interface CalendarState extends ReducerContext {
   viewSpecs: ViewSpecHash
   toolbarConfig
   selectionConfig: EventUi
+  viewSpec: ViewSpec
+  viewTitle: string
+  viewApi: ViewApi
 }
 
 export type reducerFunc = (state: CalendarState, action: Action, context: ReducerContext) => CalendarState

+ 1 - 2
packages/core/src/structs/event-source.ts

@@ -1,6 +1,5 @@
 import { refineProps, guid } from '../util/misc'
 import { EventInput } from './event'
-import { Calendar } from '../Calendar'
 import { DateRange } from '../datelib/date-range'
 import { EventSourceFunc } from '../event-sources/func-event-source'
 import { EventUi, processUnscopedUiProps } from '../component/event-ui'
@@ -87,8 +86,8 @@ export type EventSourceHash = { [sourceId: string]: EventSource }
 export type EventSourceFetcher = (
   arg: {
     eventSource: EventSource
-    calendar: Calendar
     range: DateRange
+    context: ReducerContext
   },
   success: (res: { rawEvents: EventInput[], xhr?: XMLHttpRequest }) => void,
   failure: (error: EventSourceError) => void

+ 7 - 13
packages/core/src/structs/view-spec.ts

@@ -1,6 +1,5 @@
 import { ViewDef, compileViewDefs } from './view-def'
 import { Duration, createDuration, greatestDurationDenominator, getWeeksFromInput } from '../datelib/duration'
-import { compileOptions } from '../OptionsManager'
 import { mapHash } from '../util/object'
 import { globalDefaults } from '../options'
 import { ViewConfigInputHash, parseViewConfigs, ViewConfigHash, ViewComponentType } from './view-config'
@@ -19,7 +18,8 @@ export interface ViewSpec {
   duration: Duration
   durationUnit: string
   singleUnit: string
-  options: any
+  optionDefaults: any
+  optionOverrides: any
   buttonTextOverride: string
   buttonTextDefault: string
 }
@@ -27,18 +27,18 @@ export interface ViewSpec {
 export type ViewSpecHash = { [viewType: string]: ViewSpec }
 
 
-export function buildViewSpecs(defaultInputs: ViewConfigInputHash, optionOverrides, dynamicOptionOverrides): ViewSpecHash {
+export function buildViewSpecs(defaultInputs: ViewConfigInputHash, optionOverrides, dynamicOptionOverrides, localeDefaults): ViewSpecHash {
   let defaultConfigs = parseViewConfigs(defaultInputs)
   let overrideConfigs = parseViewConfigs(optionOverrides.views)
   let viewDefs = compileViewDefs(defaultConfigs, overrideConfigs)
 
   return mapHash(viewDefs, function(viewDef) {
-    return buildViewSpec(viewDef, overrideConfigs, optionOverrides, dynamicOptionOverrides)
+    return buildViewSpec(viewDef, overrideConfigs, optionOverrides, dynamicOptionOverrides, localeDefaults)
   })
 }
 
 
-function buildViewSpec(viewDef: ViewDef, overrideConfigs: ViewConfigHash, optionOverrides, dynamicOptionOverrides): ViewSpec {
+function buildViewSpec(viewDef: ViewDef, overrideConfigs: ViewConfigHash, optionOverrides, dynamicOptionOverrides, localeDefaults): ViewSpec {
   let durationInput =
     viewDef.overrides.duration ||
     viewDef.defaults.duration ||
@@ -85,20 +85,14 @@ function buildViewSpec(viewDef: ViewDef, overrideConfigs: ViewConfigHash, option
     }
   }
 
-  let { options, localeDefaults } = compileOptions(
-    optionOverrides,
-    dynamicOptionOverrides,
-    viewDef.defaults,
-    { ...singleUnitOverrides, ...viewDef.overrides }
-  )
-
   return {
     type: viewDef.type,
     component: viewDef.component,
     duration,
     durationUnit,
     singleUnit,
-    options,
+    optionDefaults: viewDef.defaults,
+    optionOverrides: { ...singleUnitOverrides, ...viewDef.overrides },
 
     buttonTextOverride:
       queryButtonText(dynamicOptionOverrides) ||

+ 35 - 0
packages/core/src/util/memoize.ts

@@ -1,4 +1,5 @@
 import { isArraysEqual } from './array'
+import { isPropsEqual } from './object'
 
 
 export function memoize<Args extends any[], Res>(
@@ -35,6 +36,40 @@ export function memoize<Args extends any[], Res>(
 }
 
 
+export function memoizeObjArg<Arg extends object, Res>(
+  workerFunc: (arg: Arg) => Res,
+  resEquality?: (res0: Res, res1: Res) => boolean,
+  teardownFunc?: (res: Res) => void
+): (arg: Arg) => Res {
+
+  let currentArg: Arg | undefined
+  let currentRes: Res | undefined
+
+  return function(newArg: Arg) {
+
+    if (!currentArg) {
+      currentRes = workerFunc.call(this, newArg)
+
+    } else if (!isPropsEqual(currentArg, newArg)) {
+
+      if (teardownFunc) {
+        teardownFunc(currentRes)
+      }
+
+      let res = workerFunc.call(this, newArg)
+
+      if (!resEquality || !resEquality(res, currentRes)) {
+        currentRes = res
+      }
+    }
+
+    currentArg = newArg
+
+    return currentRes
+  }
+}
+
+
 export function memoizeArraylike<Args extends any[], Res>( // used at all?
   workerFunc: (...args: Args) => Res,
   resEquality?: (res0: Res, res1: Res) => boolean,

+ 3 - 3
packages/google-calendar/src/main.ts

@@ -52,9 +52,9 @@ let eventSourceDef: EventSourceDef = {
   },
 
   fetch(arg, onSuccess, onFailure) {
-    let calendar = arg.calendar
+    let { dateEnv, options } = arg.context
     let meta = arg.eventSource.meta
-    let apiKey = meta.googleCalendarApiKey || calendar.opt('googleCalendarApiKey')
+    let apiKey = meta.googleCalendarApiKey || options.googleCalendarApiKey
 
     if (!apiKey) {
       onFailure({
@@ -66,7 +66,7 @@ let eventSourceDef: EventSourceDef = {
         arg.range,
         apiKey,
         meta.data,
-        calendar.state.dateEnv
+        dateEnv
       )
 
       requestJson('GET', url, requestParams, function(body, xhr) {

+ 14 - 16
packages/interaction/src/interactions/UnselectAuto.ts

@@ -1,20 +1,18 @@
 import {
-  Calendar, DateSelectionApi,
+  DateSelectionApi,
   PointerDragEvent,
-  elementClosest
+  elementClosest,
+  ReducerContext
 } from '@fullcalendar/core'
 import { PointerDragging } from '../dnd/PointerDragging'
 import { EventDragging } from './EventDragging'
 
 export class UnselectAuto {
 
-  calendar: Calendar
   documentPointer: PointerDragging // for unfocusing
   isRecentPointerDateSelect = false // wish we could use a selector to detect date selection, but uses hit system
 
-  constructor(calendar: Calendar) {
-    this.calendar = calendar
-
+  constructor(private context: ReducerContext) {
     let documentPointer = this.documentPointer = new PointerDragging(document)
     documentPointer.shouldIgnoreMove = true
     documentPointer.shouldWatchScroll = false
@@ -23,11 +21,11 @@ export class UnselectAuto {
     /*
     TODO: better way to know about whether there was a selection with the pointer
     */
-    calendar.on('select', this.onSelect)
+    context.emitter.on('select', this.onSelect)
   }
 
   destroy() {
-    this.calendar.off('select', this.onSelect)
+    this.context.emitter.off('select', this.onSelect)
     this.documentPointer.destroy()
   }
 
@@ -38,29 +36,29 @@ export class UnselectAuto {
   }
 
   onDocumentPointerUp = (pev: PointerDragEvent) => {
-    let { calendar, documentPointer } = this
-    let { state } = calendar
+    let { context } = this
+    let { documentPointer } = this
 
     // touch-scrolling should never unfocus any type of selection
     if (!documentPointer.wasTouchScroll) {
 
       if (
-        state.dateSelection && // an existing date selection?
+        context.calendar.state.dateSelection && // an existing date selection?
         !this.isRecentPointerDateSelect // a new pointer-initiated date selection since last onDocumentPointerUp?
       ) {
-        let unselectAuto = calendar.viewOpt('unselectAuto')
-        let unselectCancel = calendar.viewOpt('unselectCancel')
+        let unselectAuto = context.options.unselectAuto
+        let unselectCancel = context.options.unselectCancel
 
         if (unselectAuto && (!unselectAuto || !elementClosest(documentPointer.downEl, unselectCancel))) {
-          calendar.unselect(pev)
+          context.calendar.unselect(pev)
         }
       }
 
       if (
-        state.eventSelection && // an existing event selected?
+        context.calendar.state.eventSelection && // an existing event selected?
         !elementClosest(documentPointer.downEl, EventDragging.SELECTOR) // interaction DIDN'T start on an event
       ) {
-        calendar.dispatch({ type: 'UNSELECT_EVENT' })
+        context.calendar.dispatch({ type: 'UNSELECT_EVENT' })
       }
 
     }