Explorar o código

use ReducerContext instead of Calendar in many plaecs

Adam Shaw %!s(int64=5) %!d(string=hai) anos
pai
achega
456cace969
Modificáronse 36 ficheiros con 379 adicións e 355 borrados
  1. 1 1
      packages-premium
  2. 9 8
      packages/core/src/Calendar.tsx
  3. 4 2
      packages/core/src/CalendarComponent.tsx
  4. 1 0
      packages/core/src/OptionsManager.ts
  5. 0 1
      packages/core/src/Toolbar.tsx
  6. 10 10
      packages/core/src/common/slicing-utils.ts
  7. 22 33
      packages/core/src/component/ComponentContext.ts
  8. 2 4
      packages/core/src/component/DateComponent.ts
  9. 2 2
      packages/core/src/component/event-rendering.ts
  10. 5 5
      packages/core/src/component/event-ui.ts
  11. 3 3
      packages/core/src/interactions/event-dragging.ts
  12. 1 0
      packages/core/src/main.ts
  13. 19 10
      packages/core/src/reducers/CalendarStateReducer.ts
  14. 38 0
      packages/core/src/reducers/ReducerContext.ts
  15. 33 44
      packages/core/src/reducers/eventSources.ts
  16. 16 16
      packages/core/src/reducers/eventStore.ts
  17. 3 8
      packages/core/src/reducers/types.ts
  18. 3 3
      packages/core/src/structs/business-hours.ts
  19. 3 3
      packages/core/src/structs/date-span.ts
  20. 13 13
      packages/core/src/structs/event-mutation.ts
  21. 8 8
      packages/core/src/structs/event-source.ts
  22. 10 10
      packages/core/src/structs/event-store.ts
  23. 23 23
      packages/core/src/structs/event.ts
  24. 1 1
      packages/core/src/structs/view-config.tsx
  25. 36 30
      packages/core/src/validation.ts
  26. 1 1
      packages/daygrid/src/DayTable.tsx
  27. 1 1
      packages/daygrid/src/DayTableView.tsx
  28. 1 1
      packages/daygrid/src/TableRow.tsx
  29. 36 36
      packages/interaction/src/interactions-external/ExternalElementDragging.ts
  30. 51 52
      packages/interaction/src/interactions/EventDragging.ts
  31. 5 5
      packages/interaction/src/interactions/EventResizing.ts
  32. 5 4
      packages/list/src/ListView.tsx
  33. 3 3
      packages/timegrid/src/DayTimeCols.tsx
  34. 5 8
      packages/timegrid/src/DayTimeColsView.tsx
  35. 1 1
      packages/timegrid/src/TimeCol.tsx
  36. 4 5
      packages/timegrid/src/TimeCols.tsx

+ 1 - 1
packages-premium

@@ -1 +1 @@
-Subproject commit dd387a0606c4833cbea14ea7d394989367c3d118
+Subproject commit d96810c9170913cc15f4500ae7a507855e36ec92

+ 9 - 8
packages/core/src/Calendar.tsx

@@ -30,6 +30,7 @@ import { removeExact } from './util/array'
 import { guid } from './util/misc'
 import { CssDimValue } from './scrollgrid/util'
 import { applyStyleProp } from './util/dom-manip'
+import { ReducerContext } from './reducers/ReducerContext'
 import { CalendarStateReducer } from './reducers/CalendarStateReducer'
 import { parseToolbars } from './toolbar-parse'
 import { getNow } from './reducers/current-date'
@@ -231,7 +232,7 @@ export class Calendar {
         this.renderableEventStore :
         state.eventStore
 
-    let eventUiSingleBase = this.buildEventUiSingleBase(viewSpec.options)
+    let eventUiSingleBase = this.buildEventUiSingleBase(viewSpec.options, this.state)
     let eventUiBySource = this.buildEventUiBySource(state.eventSources)
     let eventUiBases = this.eventUiBases = this.buildEventUiBases(renderableEventStore.defs, eventUiSingleBase, eventUiBySource)
 
@@ -363,7 +364,7 @@ export class Calendar {
     this.defaultAllDayEventDuration = createDuration(rawOptions.defaultAllDayEventDuration)
     this.defaultTimedEventDuration = createDuration(rawOptions.defaultTimedEventDuration)
 
-    this.selectionConfig = buildSelectionConfig(rawOptions, this) // relies on dateEnv
+    this.selectionConfig = buildSelectionConfig(rawOptions, this.state)
 
     this.toolbarConfig = parseToolbars(rawOptions, this.state.theme, rawOptions.direction === 'rtl', this)
   }
@@ -840,7 +841,7 @@ export class Calendar {
       }
     }
 
-    let tuple = parseEvent(eventInput, sourceId, this)
+    let tuple = parseEvent(eventInput, sourceId, this.state)
 
     if (tuple) {
 
@@ -956,7 +957,7 @@ export class Calendar {
       return sourceInput
     }
 
-    let eventSource = parseEventSource(sourceInput, this.state.pluginHooks, this)
+    let eventSource = parseEventSource(sourceInput, this.state)
 
     if (eventSource) { // TODO: error otherwise?
       this.dispatch({ type: 'ADD_EVENT_SOURCES', sources: [ eventSource ] })
@@ -997,16 +998,16 @@ export class Calendar {
 // -----------------------------------------------------------------------------------------------------------------
 
 
-function buildSelectionConfig(rawOptions, calendar: Calendar) {
-  return processScopedUiProps('select', rawOptions, calendar)
+function buildSelectionConfig(rawOptions, context: ReducerContext) {
+  return processScopedUiProps('select', rawOptions, context)
 }
 
 
-function buildEventUiSingleBase(this: Calendar, rawOptions) { // DANGEROUS: `this` context must be a Calendar
+function buildEventUiSingleBase(rawOptions, context: ReducerContext) {
   if (rawOptions.editable) { // so 'editable' affected events
     rawOptions = { ...rawOptions, eventEditable: true }
   }
-  return processScopedUiProps('event', rawOptions, this)
+  return processScopedUiProps('event', rawOptions, context)
 }
 
 

+ 4 - 2
packages/core/src/CalendarComponent.tsx

@@ -47,7 +47,7 @@ export class CalendarComponent extends Component<CalendarComponentProps, Calenda
 
   private computeTitle = memoize(computeTitle)
   private buildViewContext = memoize(buildViewContext)
-  private parseBusinessHours = memoize((input) => parseBusinessHours(input, this.props.calendar))
+  private parseBusinessHours = memoize((input) => parseBusinessHours(input, this.props))
   private buildViewPropTransformers = memoize(buildViewPropTransformers)
   private buildToolbarProps = memoize(buildToolbarProps)
   private reportClassNames = memoize(reportClassNames)
@@ -110,8 +110,10 @@ export class CalendarComponent extends Component<CalendarComponentProps, Calenda
       props.dateProfile,
       props.dateProfileGenerator,
       props.dateEnv,
-      props.pluginHooks,
       props.theme,
+      props.pluginHooks,
+      props.dispatch,
+      props.emitter,
       props.calendar
     )
 

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

@@ -3,6 +3,7 @@ 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,

+ 0 - 1
packages/core/src/Toolbar.tsx

@@ -20,7 +20,6 @@ export interface ToolbarContent {
 
 export class Toolbar extends BaseComponent<ToolbarProps> {
 
-
   render(props: ToolbarProps) {
     let { model } = props
     let forceLtr = false

+ 10 - 10
packages/core/src/common/slicing-utils.ts

@@ -9,7 +9,7 @@ import { EventInteractionState } from '../interactions/event-interaction-state'
 import { Duration } from '../datelib/duration'
 import { memoize } from '../util/memoize'
 import { DateMarker, addMs, addDays } from '../datelib/marker'
-import { Calendar } from '../Calendar'
+import { ReducerContext } from '../reducers/ReducerContext'
 
 export interface SliceableProps {
   dateSelection: DateSpan
@@ -46,15 +46,15 @@ export abstract class Slicer<SegType extends Seg, ExtraArgs extends any[] = []>
     props: SliceableProps,
     dateProfile: DateProfile,
     nextDayThreshold: Duration | null,
-    calendar: Calendar,
+    context: ReducerContext,
     ...extraArgs: ExtraArgs
   ): SlicedProps<SegType> {
     let { eventUiBases } = props
     let eventSegs = this.sliceEventStore(props.eventStore, eventUiBases, dateProfile, nextDayThreshold, ...extraArgs)
 
     return {
-      dateSelectionSegs: this.sliceDateSelection(props.dateSelection, eventUiBases, calendar, ...extraArgs),
-      businessHourSegs: this.sliceBusinessHours(props.businessHours, dateProfile, nextDayThreshold, calendar, ...extraArgs),
+      dateSelectionSegs: this.sliceDateSelection(props.dateSelection, eventUiBases, context, ...extraArgs),
+      businessHourSegs: this.sliceBusinessHours(props.businessHours, dateProfile, nextDayThreshold, context, ...extraArgs),
       fgEventSegs: eventSegs.fg,
       bgEventSegs: eventSegs.bg,
       eventDrag: this.sliceEventDrag(props.eventDrag, eventUiBases, dateProfile, nextDayThreshold, ...extraArgs),
@@ -65,13 +65,13 @@ export abstract class Slicer<SegType extends Seg, ExtraArgs extends any[] = []>
 
   sliceNowDate( // does not memoize
     date: DateMarker,
-    calendar: Calendar,
+    context: ReducerContext,
     ...extraArgs: ExtraArgs
   ): SegType[] {
     return this._sliceDateSpan(
       { range: { start: date, end: addMs(date, 1) }, allDay: false }, // add 1 ms, protect against null range
       {},
-      calendar,
+      context,
       ...extraArgs
     )
   }
@@ -80,7 +80,7 @@ export abstract class Slicer<SegType extends Seg, ExtraArgs extends any[] = []>
     businessHours: EventStore,
     dateProfile: DateProfile,
     nextDayThreshold: Duration | null,
-    calendar: Calendar,
+    context: ReducerContext,
     ...extraArgs: ExtraArgs
   ): SegType[] {
     if (!businessHours) {
@@ -91,7 +91,7 @@ export abstract class Slicer<SegType extends Seg, ExtraArgs extends any[] = []>
       expandRecurring(
         businessHours,
         computeActiveRange(dateProfile, Boolean(nextDayThreshold)),
-        calendar
+        context
       ),
       {},
       dateProfile,
@@ -153,14 +153,14 @@ export abstract class Slicer<SegType extends Seg, ExtraArgs extends any[] = []>
   private _sliceDateSpan(
     dateSpan: DateSpan,
     eventUiBases: EventUiHash,
-    calendar: Calendar,
+    context: ReducerContext,
     ...extraArgs: ExtraArgs
   ): SegType[] {
     if (!dateSpan) {
       return []
     }
 
-    let eventRange = fabricateEventRange(dateSpan, eventUiBases, calendar)
+    let eventRange = fabricateEventRange(dateSpan, eventUiBases, context)
     let segs = this.sliceRange(dateSpan.range, ...extraArgs)
 
     for (let seg of segs) {

+ 22 - 33
packages/core/src/component/ComponentContext.ts

@@ -3,38 +3,31 @@ import { ResizeHandler } from '../Calendar'
 import { ViewApi } from '../ViewApi'
 import { Theme } from '../theme/Theme'
 import { DateEnv } from '../datelib/env'
-import { parseFieldSpecs } from '../util/misc'
-import { createDuration, Duration } from '../datelib/duration'
 import { PluginHooks } from '../plugin-system'
 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 { Action } from '../reducers/types'
+import { Emitter } from '../common/Emitter'
+
 
 export const ComponentContextType = createContext<ComponentContext>({} as any) // for Components
 
 // TODO: rename file
 // TODO: rename to ViewContext
 
-export interface ComponentContext {
+export interface ComponentContext extends ReducerContext {
+  isRtl: boolean
+  theme: Theme
+  dateProfileGenerator: DateProfileGenerator
+  dateProfile: DateProfile
   viewSpec: ViewSpec
   viewApi: ViewApi
-  options: any
-  dateProfile: DateProfile
-  dateProfileGenerator: DateProfileGenerator
-  dateEnv: DateEnv
-  pluginHooks: PluginHooks
-  theme: Theme
-  calendar: Calendar
-
   addResizeHandler: (handler: ResizeHandler) => void
   removeResizeHandler: (handler: ResizeHandler) => void
   createScrollResponder: (execFunc: ScrollRequestHandler) => ScrollResponder
-
-  // computed from options...
-  isRtl: boolean
-  eventOrderSpecs: any
-  nextDayThreshold: Duration
 }
 
 
@@ -44,26 +37,33 @@ export function buildViewContext(
   dateProfile: DateProfile,
   dateProfileGenerator: DateProfileGenerator,
   dateEnv: DateEnv,
-  pluginHooks: PluginHooks,
   theme: Theme,
-  calendar: Calendar,
+  pluginHooks: PluginHooks,
+  dispatch: (action: Action) => void,
+  emitter: Emitter,
+  calendar: Calendar
 ): ComponentContext {
+  let { options } = viewSpec
+
   return {
     viewSpec,
     viewApi: buildViewApi(viewSpec, viewTitle, dateProfile, dateEnv),
-    options: viewSpec.options,
     dateProfile,
     dateProfileGenerator,
     dateEnv,
-    pluginHooks,
+    isRtl: options.direction === 'rtl',
     theme,
+    options,
+    computedOptions: buildComputedOptions(options),
+    pluginHooks,
+    dispatch,
+    emitter,
     calendar,
     addResizeHandler: calendar.addResizeHandler,
     removeResizeHandler: calendar.removeResizeHandler,
     createScrollResponder(execFunc: ScrollRequestHandler) {
       return new ScrollResponder(calendar, execFunc)
-    },
-    ...computeContextProps(viewSpec.options, theme, calendar)
+    }
   }
 }
 
@@ -78,14 +78,3 @@ function buildViewApi(viewSpec: ViewSpec, viewTitle: string, dateProfile: DatePr
     currentEnd: dateEnv.toDate(dateProfile.currentRange.end)
   }
 }
-
-
-function computeContextProps(options: any, theme: Theme, calendar: Calendar) {
-  let isRtl = options.direction === 'rtl'
-
-  return {
-    isRtl,
-    eventOrderSpecs: parseFieldSpecs(options.eventOrder),
-    nextDayThreshold: createDuration(options.nextDayThreshold)
-  }
-}

+ 2 - 4
packages/core/src/component/DateComponent.ts

@@ -65,7 +65,6 @@ export abstract class DateComponent<Props={}, State={}> extends BaseComponent<Pr
   // -----------------------------------------------------------------------------------------------------------------
 
   isInteractionValid(interaction: EventInteractionState) {
-    let { calendar } = this.context
     let dateProfile = (this.props as any).dateProfile // HACK
     let instances = interaction.mutatedEvents.instances
 
@@ -77,11 +76,10 @@ export abstract class DateComponent<Props={}, State={}> extends BaseComponent<Pr
       }
     }
 
-    return isInteractionValid(interaction, calendar)
+    return isInteractionValid(interaction, this.context)
   }
 
   isDateSelectionValid(selection: DateSpan): boolean {
-    let { calendar } = this.context
     let dateProfile = (this.props as any).dateProfile // HACK
 
     if (
@@ -91,7 +89,7 @@ export abstract class DateComponent<Props={}, State={}> extends BaseComponent<Pr
       return false
     }
 
-    return isDateSelectionValid(selection, calendar)
+    return isDateSelectionValid(selection, this.context)
   }
 
 

+ 2 - 2
packages/core/src/component/event-rendering.ts

@@ -225,13 +225,13 @@ export interface EventMeta { // for *Content handlers
 
 
 export function computeSegDraggable(seg: Seg, context: ComponentContext) {
-  let { pluginHooks, calendar } = context
+  let { pluginHooks } = context
   let transformers = pluginHooks.isDraggableTransformers
   let { def, ui } = seg.eventRange
   let val = ui.startEditable
 
   for (let transformer of transformers) {
-    val = transformer(val, def, ui, calendar)
+    val = transformer(val, def, ui, context)
   }
 
   return val

+ 5 - 5
packages/core/src/component/event-ui.ts

@@ -1,7 +1,7 @@
 import { Constraint, AllowFunc, normalizeConstraint, ConstraintInput } from '../validation'
 import { parseClassName } from '../util/html'
 import { refineProps, capitaliseFirstLetter } from '../util/misc'
-import { Calendar } from '../Calendar'
+import { ReducerContext } from '../reducers/ReducerContext'
 
 // TODO: better called "EventSettings" or "EventConfig"
 // TODO: move this file into structs
@@ -53,9 +53,9 @@ export const UNSCOPED_EVENT_UI_PROPS = {
   textColor: String
 }
 
-export function processUnscopedUiProps(rawProps: UnscopedEventUiInput, calendar: Calendar, leftovers?): EventUi {
+export function processUnscopedUiProps(rawProps: UnscopedEventUiInput, context: ReducerContext, leftovers?): EventUi {
   let props = refineProps(rawProps, UNSCOPED_EVENT_UI_PROPS, {}, leftovers)
-  let constraint = normalizeConstraint(props.constraint, calendar)
+  let constraint = normalizeConstraint(props.constraint, context)
 
   return {
     display: props.display,
@@ -71,7 +71,7 @@ export function processUnscopedUiProps(rawProps: UnscopedEventUiInput, calendar:
   }
 }
 
-export function processScopedUiProps(prefix: string, rawScoped: any, calendar: Calendar, leftovers?): EventUi {
+export function processScopedUiProps(prefix: string, rawScoped: any, context: ReducerContext, leftovers?): EventUi {
   let rawUnscoped = {} as any
   let wasFound = {} as any
 
@@ -93,7 +93,7 @@ export function processScopedUiProps(prefix: string, rawScoped: any, calendar: C
     }
   }
 
-  return processUnscopedUiProps(rawUnscoped, calendar)
+  return processUnscopedUiProps(rawUnscoped, context)
 }
 
 const EMPTY_EVENT_UI: EventUi = {

+ 3 - 3
packages/core/src/interactions/event-dragging.ts

@@ -1,9 +1,9 @@
-import { Calendar } from '../Calendar'
 import { EventMutation } from '../structs/event-mutation'
 import { Hit } from './hit'
 import { EventDef } from '../structs/event'
 import { EventUi } from '../component/event-ui'
+import { ReducerContext } from '../reducers/ReducerContext'
 
 export type eventDragMutationMassager = (mutation: EventMutation, hit0: Hit, hit1: Hit) => void
-export type EventDropTransformers = (mutation: EventMutation, calendar: Calendar) => any
-export type eventIsDraggableTransformer = (val: boolean, eventDef: EventDef, eventUi: EventUi, Calendar: Calendar) => boolean
+export type EventDropTransformers = (mutation: EventMutation, context: ReducerContext) => any
+export type eventIsDraggableTransformer = (val: boolean, eventDef: EventDef, eventUi: EventUi, context: ReducerContext) => boolean

+ 1 - 0
packages/core/src/main.ts

@@ -138,6 +138,7 @@ export { DragMetaInput, DragMeta, parseDragMeta } from './structs/drag-meta'
 
 export { createPlugin, PluginDef, PluginDefInput, ViewPropsTransformer, ViewContainerAppend } from './plugin-system'
 export { reducerFunc, Action, CalendarState } from './reducers/types'
+export { ReducerContext } from './reducers/ReducerContext'
 export { CalendarComponentProps } from './CalendarComponent'
 
 export { DayHeader } from './common/DayHeader'

+ 19 - 10
packages/core/src/reducers/CalendarStateReducer.ts

@@ -1,4 +1,3 @@
-
 import { buildLocale, RawLocaleInfo } from '../datelib/locale'
 import { memoize } from '../util/memoize'
 import { Action, CalendarState } from './types'
@@ -21,6 +20,7 @@ import { reduceSelectedEvent } from './selected-event'
 import { reduceEventDrag } from './event-drag'
 import { reduceEventResize } from './event-resize'
 import { Emitter } from '../common/Emitter'
+import { ReducerContext, buildComputedOptions } from './ReducerContext'
 
 
 export class CalendarStateReducer {
@@ -31,6 +31,7 @@ export class CalendarStateReducer {
   private buildTheme = memoize(buildTheme)
   private buildViewSpecs = memoize(buildViewSpecs)
   private buildDateProfileGenerator = memoize(buildDateProfileGenerators)
+  private buildComputedOptions = memoize(buildComputedOptions)
 
 
   reduce(state: CalendarState, action: Action, emitter: Emitter, calendar: Calendar): CalendarState {
@@ -66,7 +67,7 @@ export class CalendarStateReducer {
 
     let pluginHooks = this.buildPluginHooks(options.plugins)
     let viewSpecs = this.buildViewSpecs(pluginHooks.views, optionOverrides, dynamicOptionOverrides)
-    let prevDateEnv = state.dateEnv
+    let prevDateEnv = state ? state.dateEnv : null
     let dateEnv = this.buildDateEnv(
       options.timeZone,
       options.locale,
@@ -79,6 +80,16 @@ export class CalendarStateReducer {
     let dateProfileGenerators = this.buildDateProfileGenerator(viewSpecs, dateEnv)
     let theme = this.buildTheme(options, pluginHooks)
 
+    let reducerContext: ReducerContext = {
+      dateEnv,
+      options,
+      computedOptions: this.buildComputedOptions(options),
+      pluginHooks,
+      emitter,
+      dispatch: state.dispatch || calendar.dispatch.bind(calendar), // will reuse past functions! TODO: memoize? TODO: calendar should bind?
+      calendar
+    }
+
     let viewType = state.viewType || options.initialView || pluginHooks.initialView // weird how we do INIT
     viewType = reduceViewType(viewType, action, pluginHooks.views)
 
@@ -87,15 +98,13 @@ export class CalendarStateReducer {
     let dateProfile = reduceDateProfile(state.dateProfile, action, currentDate, dateProfileGenerator)
     currentDate = reduceCurrentDate(currentDate, action, dateProfile)
 
-    let eventSources = reduceEventSources(state.eventSources, action, dateProfile, pluginHooks, options, emitter, calendar)
+    let eventSources = reduceEventSources(state.eventSources, action, dateProfile, reducerContext)
 
-    let nextState = {
-      ...state, // preserve previous state from plugin reducers
+    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,
       optionOverrides,
       dynamicOptionOverrides,
-      options,
-      dateEnv,
-      pluginHooks,
       availableRawLocales: availableLocaleData.map,
       theme,
       viewSpecs,
@@ -104,7 +113,7 @@ export class CalendarStateReducer {
       dateProfile,
       currentDate,
       eventSources,
-      eventStore: reduceEventStore(state.eventStore, action, eventSources, dateProfile, dateEnv, prevDateEnv, calendar),
+      eventStore: reduceEventStore(state.eventStore, action, eventSources, dateProfile, prevDateEnv, reducerContext),
       dateSelection: reduceDateSelection(state.dateSelection, action),
       eventSelection: reduceSelectedEvent(state.eventSelection, action),
       eventDrag: reduceEventDrag(state.eventDrag, action),
@@ -114,7 +123,7 @@ export class CalendarStateReducer {
     }
 
     for (let reducerFunc of pluginHooks.reducers) {
-      nextState = reducerFunc(nextState, action, options, calendar)
+      nextState = reducerFunc(nextState, action, reducerContext)
     }
 
     return nextState

+ 38 - 0
packages/core/src/reducers/ReducerContext.ts

@@ -0,0 +1,38 @@
+import { Action } from './types'
+import { PluginHooks } from '../plugin-system'
+import { DateEnv } from '../datelib/env'
+import { Calendar } from '../Calendar'
+import { Emitter } from '../common/Emitter'
+import { parseFieldSpecs } from '../util/misc'
+import { createDuration, Duration } from '../datelib/duration'
+
+
+export interface ReducerContext {
+  dateEnv: DateEnv
+  options: any
+  computedOptions: ComputedOptions
+  pluginHooks: PluginHooks
+  emitter: Emitter
+  calendar: Calendar
+  dispatch(action: Action): void
+}
+
+export interface ComputedOptions {
+  eventOrderSpecs: any
+  nextDayThreshold: Duration
+  defaultAllDayEventDuration: Duration
+  defaultTimedEventDuration: Duration
+  slotDuration: Duration | null
+  snapDuration: Duration | null
+}
+
+export function buildComputedOptions(options: any): ComputedOptions {
+  return {
+    eventOrderSpecs: parseFieldSpecs(options.eventOrder),
+    nextDayThreshold: createDuration(options.nextDayThreshold),
+    defaultAllDayEventDuration: createDuration(options.defaultAllDayEventDuration),
+    defaultTimedEventDuration: createDuration(options.defaultTimedEventDuration),
+    slotDuration: options.slotDuration ? createDuration(options.slotDuration) : null,
+    snapDuration: options.snapDuration ? createDuration(options.snapDuration) : null
+  }
+}

+ 33 - 44
packages/core/src/reducers/eventSources.ts

@@ -1,30 +1,26 @@
 import { EventSource, EventSourceHash, doesSourceNeedRange, parseEventSource } from '../structs/event-source'
-import { Calendar } from '../Calendar'
 import { arrayToHash, filterHash } from '../util/object'
 import { DateRange } from '../datelib/date-range'
 import { DateProfile } from '../DateProfileGenerator'
 import { Action } from './types'
 import { guid } from '../util/misc'
-import { PluginHooks } from '../plugin-system'
-import { Emitter } from '../common/Emitter'
+import { ReducerContext } from './ReducerContext'
 
-export function reduceEventSources(eventSources: EventSourceHash, action: Action, dateProfile: DateProfile | null, pluginHooks: PluginHooks, rawOptions, emitter: Emitter, calendar: Calendar): EventSourceHash {
+
+export function reduceEventSources(eventSources: EventSourceHash, action: Action, dateProfile: DateProfile | null, context: ReducerContext): EventSourceHash {
   let activeRange = dateProfile ? dateProfile.activeRange : null
 
   switch (action.type) {
     case 'INIT':
       return addSources(
         eventSources,
-        parseInitialSources(action.optionOverrides, pluginHooks, calendar),
+        parseInitialSources(action.optionOverrides, context),
         activeRange,
-        pluginHooks,
-        rawOptions,
-        emitter,
-        calendar
+        context
       )
 
     case 'ADD_EVENT_SOURCES': // already parsed
-      return addSources(eventSources, action.sources, activeRange, pluginHooks, rawOptions, emitter, calendar)
+      return addSources(eventSources, action.sources, activeRange, context)
 
     case 'REMOVE_EVENT_SOURCE':
       return removeSource(eventSources, action.sourceId)
@@ -34,7 +30,7 @@ export function reduceEventSources(eventSources: EventSourceHash, action: Action
     case 'SET_DATE':
     case 'SET_VIEW_TYPE':
       if (dateProfile) {
-        return fetchDirtySources(eventSources, activeRange, pluginHooks, rawOptions, emitter, calendar)
+        return fetchDirtySources(eventSources, activeRange, context)
       } else {
         return eventSources
       }
@@ -44,22 +40,18 @@ export function reduceEventSources(eventSources: EventSourceHash, action: Action
         eventSources,
         (action as any).sourceIds ? // why no type?
           arrayToHash((action as any).sourceIds) :
-          excludeStaticSources(eventSources, pluginHooks),
+          excludeStaticSources(eventSources, context),
         activeRange,
-        pluginHooks,
-        emitter,
-        calendar
+        context
       )
 
     case 'SET_OPTION':
       if (action.optionName === 'timeZone') {
         return fetchSourcesByIds(
           eventSources,
-          excludeStaticSources(eventSources, pluginHooks),
+          excludeStaticSources(eventSources, context),
           activeRange,
-          pluginHooks,
-          emitter,
-          calendar
+          context
         )
       } else {
         return eventSources
@@ -78,7 +70,7 @@ export function reduceEventSources(eventSources: EventSourceHash, action: Action
 }
 
 
-function addSources(eventSourceHash: EventSourceHash, sources: EventSource[], fetchRange: DateRange | null, pluginHooks: PluginHooks, rawOptions, emitter: Emitter, calendar: Calendar): EventSourceHash {
+function addSources(eventSourceHash: EventSourceHash, sources: EventSource[], fetchRange: DateRange | null, context: ReducerContext): EventSourceHash {
   let hash: EventSourceHash = {}
 
   for (let source of sources) {
@@ -86,7 +78,7 @@ function addSources(eventSourceHash: EventSourceHash, sources: EventSource[], fe
   }
 
   if (fetchRange) {
-    hash = fetchDirtySources(hash, fetchRange, pluginHooks, rawOptions, emitter, calendar)
+    hash = fetchDirtySources(hash, fetchRange, context)
   }
 
   return { ...eventSourceHash, ...hash }
@@ -100,26 +92,25 @@ function removeSource(eventSourceHash: EventSourceHash, sourceId: string): Event
 }
 
 
-function fetchDirtySources(sourceHash: EventSourceHash, fetchRange: DateRange, pluginHooks: PluginHooks, rawOptions, emitter: Emitter, calendar: Calendar): EventSourceHash {
+function fetchDirtySources(sourceHash: EventSourceHash, fetchRange: DateRange, context: ReducerContext): EventSourceHash {
   return fetchSourcesByIds(
     sourceHash,
     filterHash(sourceHash, function(eventSource) {
-      return isSourceDirty(eventSource, fetchRange, rawOptions, pluginHooks)
+      return isSourceDirty(eventSource, fetchRange, context)
     }),
     fetchRange,
-    pluginHooks,
-    emitter,
-    calendar
+    context
   )
 }
 
 
-function isSourceDirty(eventSource: EventSource, fetchRange: DateRange, rawOptions, pluginHooks: PluginHooks) {
+function isSourceDirty(eventSource: EventSource, fetchRange: DateRange, context: ReducerContext) {
 
-  if (!doesSourceNeedRange(eventSource, pluginHooks)) {
+  if (!doesSourceNeedRange(eventSource, context)) {
     return !eventSource.latestFetchId
+
   } else {
-    return !rawOptions.lazyFetching ||
+    return !context.options.lazyFetching ||
       !eventSource.fetchRange ||
       eventSource.isFetching || // always cancel outdated in-progress fetches
       fetchRange.start < eventSource.fetchRange.start ||
@@ -132,9 +123,7 @@ function fetchSourcesByIds(
   prevSources: EventSourceHash,
   sourceIdHash: { [sourceId: string]: any },
   fetchRange: DateRange,
-  pluginHooks: PluginHooks,
-  emitter: Emitter,
-  calendar: Calendar
+  context: ReducerContext
 ): EventSourceHash {
   let nextSources: EventSourceHash = {}
 
@@ -142,7 +131,7 @@ function fetchSourcesByIds(
     let source = prevSources[sourceId]
 
     if (sourceIdHash[sourceId]) {
-      nextSources[sourceId] = fetchSource(source, fetchRange, pluginHooks, emitter, calendar)
+      nextSources[sourceId] = fetchSource(source, fetchRange, context)
     } else {
       nextSources[sourceId] = source
     }
@@ -152,14 +141,14 @@ function fetchSourcesByIds(
 }
 
 
-function fetchSource(eventSource: EventSource, fetchRange: DateRange, pluginHooks: PluginHooks, emitter: Emitter, calendar: Calendar) {
-  let sourceDef = pluginHooks.eventSourceDefs[eventSource.sourceDefId]
+function fetchSource(eventSource: EventSource, fetchRange: DateRange, context: ReducerContext) {
+  let sourceDef = context.pluginHooks.eventSourceDefs[eventSource.sourceDefId]
   let fetchId = guid()
 
   sourceDef.fetch(
     {
       eventSource,
-      calendar,
+      calendar: context.calendar,
       range: fetchRange
     },
     function(res) {
@@ -170,10 +159,10 @@ function fetchSource(eventSource: EventSource, fetchRange: DateRange, pluginHook
         sourceSuccessRes = eventSource.success(rawEvents, res.xhr)
       }
 
-      let calSuccessRes = emitter.trigger('eventSourceSuccess', rawEvents, res.xhr)
+      let calSuccessRes = context.emitter.trigger('eventSourceSuccess', rawEvents, res.xhr)
       rawEvents = sourceSuccessRes || calSuccessRes || rawEvents
 
-      calendar.dispatch({
+      context.dispatch({
         type: 'RECEIVE_EVENTS',
         sourceId: eventSource.sourceId,
         fetchId,
@@ -188,9 +177,9 @@ function fetchSource(eventSource: EventSource, fetchRange: DateRange, pluginHook
         eventSource.failure(error)
       }
 
-      emitter.trigger('eventSourceFailure', error)
+      context.emitter.trigger('eventSourceFailure', error)
 
-      calendar.dispatch({
+      context.dispatch({
         type: 'RECEIVE_EVENT_ERROR',
         sourceId: eventSource.sourceId,
         fetchId,
@@ -229,14 +218,14 @@ function receiveResponse(sourceHash: EventSourceHash, sourceId: string, fetchId:
 }
 
 
-function excludeStaticSources(eventSources: EventSourceHash, pluginHooks: PluginHooks): EventSourceHash {
+function excludeStaticSources(eventSources: EventSourceHash, context: ReducerContext): EventSourceHash {
   return filterHash(eventSources, function(eventSource) {
-    return doesSourceNeedRange(eventSource, pluginHooks)
+    return doesSourceNeedRange(eventSource, context)
   })
 }
 
 
-function parseInitialSources(rawOptions, pluginHooks, calendar: Calendar) {
+function parseInitialSources(rawOptions, context: ReducerContext) {
   let rawSources = rawOptions.eventSources || []
   let singleRawSource = rawOptions.events
   let sources = [] // parsed
@@ -246,7 +235,7 @@ function parseInitialSources(rawOptions, pluginHooks, calendar: Calendar) {
   }
 
   for (let rawSource of rawSources) {
-    let source = parseEventSource(rawSource, pluginHooks, calendar)
+    let source = parseEventSource(rawSource, context)
     if (source) {
       sources.push(source)
     }

+ 16 - 16
packages/core/src/reducers/eventStore.ts

@@ -1,4 +1,3 @@
-import { Calendar } from '../Calendar'
 import { filterHash, mapHash } from '../util/object'
 import { EventMutation, applyMutationToEventStore } from '../structs/event-mutation'
 import { EventDef, EventInstance, EventInput, EventInstanceHash } from '../structs/event'
@@ -18,9 +17,10 @@ import { DateRange } from '../datelib/date-range'
 import { DateProfile } from '../DateProfileGenerator'
 import { DateEnv } from '../datelib/env'
 import { EventUiHash } from '../component/event-ui'
+import { ReducerContext } from './ReducerContext'
 
 
-export function reduceEventStore(eventStore: EventStore, action: Action, eventSources: EventSourceHash, dateProfile: DateProfile, dateEnv: DateEnv, prevDateEnv: DateEnv, calendar: Calendar): EventStore {
+export function reduceEventStore(eventStore: EventStore, action: Action, eventSources: EventSourceHash, dateProfile: DateProfile, prevDateEnv: DateEnv, context: ReducerContext): EventStore {
   switch (action.type) {
     case 'INIT':
       return createEmptyEventStore()
@@ -32,7 +32,7 @@ export function reduceEventStore(eventStore: EventStore, action: Action, eventSo
         action.fetchId,
         action.fetchRange,
         action.rawEvents,
-        calendar
+        context
       )
 
     case 'ADD_EVENTS': // already parsed, but not expanded
@@ -40,7 +40,7 @@ export function reduceEventStore(eventStore: EventStore, action: Action, eventSo
         eventStore,
         action.eventStore, // new ones
         dateProfile ? dateProfile.activeRange : null,
-        calendar
+        context
       )
 
     case 'MERGE_EVENTS': // already parsed and expanded
@@ -51,20 +51,20 @@ export function reduceEventStore(eventStore: EventStore, action: Action, eventSo
     case 'SET_DATE':
     case 'SET_VIEW_TYPE':
       if (dateProfile) {
-        return expandRecurring(eventStore, dateProfile.activeRange, calendar)
+        return expandRecurring(eventStore, dateProfile.activeRange, context)
       } else {
         return eventStore
       }
 
     case 'SET_OPTION':
       if (action.optionName === 'timeZone') {
-        return rezoneDates(eventStore, prevDateEnv, dateEnv)
+        return rezoneDates(eventStore, prevDateEnv, context.dateEnv)
       } else {
         return eventStore
       }
 
     case 'MUTATE_EVENTS':
-      return applyMutationToRelated(eventStore, action.instanceId, action.mutation, action.fromApi, calendar)
+      return applyMutationToRelated(eventStore, action.instanceId, action.mutation, action.fromApi, context)
 
     case 'REMOVE_EVENT_INSTANCES':
       return excludeInstances(eventStore, action.instances)
@@ -97,7 +97,7 @@ function receiveRawEvents(
   fetchId: string,
   fetchRange: DateRange | null,
   rawEvents: EventInput[],
-  calendar: Calendar
+  context: ReducerContext
 ): EventStore {
 
   if (
@@ -106,13 +106,13 @@ function receiveRawEvents(
   ) {
 
     let subset = parseEvents(
-      transformRawEvents(rawEvents, eventSource, calendar),
+      transformRawEvents(rawEvents, eventSource, context),
       eventSource.sourceId,
-      calendar
+      context
     )
 
     if (fetchRange) {
-      subset = expandRecurring(subset, fetchRange, calendar)
+      subset = expandRecurring(subset, fetchRange, context)
     }
 
     return mergeEventStores(
@@ -125,10 +125,10 @@ function receiveRawEvents(
 }
 
 
-function addEvent(eventStore: EventStore, subset: EventStore, expandRange: DateRange | null, calendar: Calendar): EventStore {
+function addEvent(eventStore: EventStore, subset: EventStore, expandRange: DateRange | null, context: ReducerContext): EventStore {
 
   if (expandRange) {
-    subset = expandRecurring(subset, expandRange, calendar)
+    subset = expandRecurring(subset, expandRange, context)
   }
 
   return mergeEventStores(eventStore, subset)
@@ -160,7 +160,7 @@ function rezoneDates(eventStore: EventStore, oldDateEnv: DateEnv, newDateEnv: Da
 }
 
 
-function applyMutationToRelated(eventStore: EventStore, instanceId: string, mutation: EventMutation, fromApi: boolean, calendar: Calendar): EventStore {
+function applyMutationToRelated(eventStore: EventStore, instanceId: string, mutation: EventMutation, fromApi: boolean, context: ReducerContext): EventStore {
   let relevant = getRelevantEvents(eventStore, instanceId)
   let eventConfigBase = fromApi ?
     { '': { // HACK. always allow API to mutate events
@@ -175,10 +175,10 @@ function applyMutationToRelated(eventStore: EventStore, instanceId: string, muta
       textColor: '',
       classNames: []
     } } as EventUiHash :
-    calendar.eventUiBases
+    context.calendar.eventUiBases
 
 
-  relevant = applyMutationToEventStore(relevant, eventConfigBase, mutation, calendar)
+  relevant = applyMutationToEventStore(relevant, eventConfigBase, mutation, context)
 
   return mergeEventStores(eventStore, relevant)
 }

+ 3 - 8
packages/core/src/reducers/types.ts

@@ -6,15 +6,13 @@ import { EventSource, EventSourceHash, EventSourceError } from '../structs/event
 import { DateProfile, DateProfileGenerator } from '../DateProfileGenerator'
 import { EventInteractionState } from '../interactions/event-interaction-state'
 import { DateSpan } from '../structs/date-span'
-import { DateEnv } from '../datelib/env'
-import { Calendar } from '../Calendar'
 import { DateMarker } from '../datelib/marker'
-import { PluginHooks } from '../plugin-system'
 import { RawLocaleMap } from '../datelib/locale'
 import { Theme } from '../theme/Theme'
 import { ViewSpecHash } from '../structs/view-spec'
+import { ReducerContext } from './ReducerContext'
 
-export interface CalendarState {
+export interface CalendarState extends ReducerContext {
   eventSources: EventSourceHash
   eventSourceLoadingLevel: number
   loadingLevel: number
@@ -26,18 +24,15 @@ export interface CalendarState {
   eventSelection: string
   eventDrag: EventInteractionState | null
   eventResize: EventInteractionState | null
-  options: any
   optionOverrides: any
   dynamicOptionOverrides: any
-  dateEnv: DateEnv
-  pluginHooks: PluginHooks
   availableRawLocales: RawLocaleMap
   theme: Theme
   dateProfileGenerator: DateProfileGenerator
   viewSpecs: ViewSpecHash
 }
 
-export type reducerFunc = (state: CalendarState, action: Action, rawOptions: any, calendar: Calendar) => CalendarState
+export type reducerFunc = (state: CalendarState, action: Action, context: ReducerContext) => CalendarState
 
 export type Action =
 

+ 3 - 3
packages/core/src/structs/business-hours.ts

@@ -1,6 +1,6 @@
-import { Calendar } from '../Calendar'
 import { EventInput } from './event'
 import { EventStore, parseEvents } from './event-store'
+import { ReducerContext } from '../reducers/ReducerContext'
 
 /*
 Utils for converting raw business hour input into an EventStore,
@@ -21,11 +21,11 @@ const DEF_DEFAULTS = {
 /*
 TODO: pass around as EventDefHash!!!
 */
-export function parseBusinessHours(input: BusinessHoursInput, calendar: Calendar): EventStore {
+export function parseBusinessHours(input: BusinessHoursInput, context: ReducerContext): EventStore {
   return parseEvents(
     refineInputs(input),
     '',
-    calendar
+    context
   )
 }
 

+ 3 - 3
packages/core/src/structs/date-span.ts

@@ -5,7 +5,7 @@ import { Duration } from '../datelib/duration'
 import { parseEventDef, createEventInstance } from './event'
 import { EventRenderRange, compileEventUi } from '../component/event-rendering'
 import { EventUiHash } from '../component/event-ui'
-import { Calendar } from '../Calendar'
+import { ReducerContext } from '../reducers/ReducerContext'
 
 /*
 A data-structure for a date-range that will be visually displayed.
@@ -147,13 +147,13 @@ export function buildDatePointApi(span: DateSpan, dateEnv: DateEnv): DatePointAp
   }
 }
 
-export function fabricateEventRange(dateSpan: DateSpan, eventUiBases: EventUiHash, calendar: Calendar): EventRenderRange {
+export function fabricateEventRange(dateSpan: DateSpan, eventUiBases: EventUiHash, context: ReducerContext): EventRenderRange {
   let def = parseEventDef(
     { editable: false },
     '', // sourceId
     dateSpan.allDay,
     true, // hasEnd
-    calendar
+    context
   )
 
   return {

+ 13 - 13
packages/core/src/structs/event-mutation.ts

@@ -1,11 +1,11 @@
 import { Duration } from '../datelib/duration'
 import { EventStore, createEmptyEventStore } from './event-store'
 import { EventDef, EventInstance } from './event'
-import { Calendar } from '../Calendar'
 import { computeAlignedDayRange } from '../util/misc'
 import { startOfDay } from '../datelib/marker'
 import { EventUiHash, EventUi } from '../component/event-ui'
 import { compileEventUis } from '../component/event-rendering'
+import { ReducerContext } from '../reducers/ReducerContext'
 
 /*
 A data structure for how to modify an EventDef/EventInstance within an EventStore
@@ -20,30 +20,30 @@ export interface EventMutation {
 }
 
 // applies the mutation to ALL defs/instances within the event store
-export function applyMutationToEventStore(eventStore: EventStore, eventConfigBase: EventUiHash, mutation: EventMutation, calendar: Calendar): EventStore {
+export function applyMutationToEventStore(eventStore: EventStore, eventConfigBase: EventUiHash, mutation: EventMutation, context: ReducerContext): EventStore {
   let eventConfigs = compileEventUis(eventStore.defs, eventConfigBase)
   let dest = createEmptyEventStore()
 
   for (let defId in eventStore.defs) {
     let def = eventStore.defs[defId]
 
-    dest.defs[defId] = applyMutationToEventDef(def, eventConfigs[defId], mutation, calendar.state.pluginHooks.eventDefMutationAppliers, calendar)
+    dest.defs[defId] = applyMutationToEventDef(def, eventConfigs[defId], mutation, context)
   }
 
   for (let instanceId in eventStore.instances) {
     let instance = eventStore.instances[instanceId]
     let def = dest.defs[instance.defId] // important to grab the newly modified def
 
-    dest.instances[instanceId] = applyMutationToEventInstance(instance, def, eventConfigs[instance.defId], mutation, calendar)
+    dest.instances[instanceId] = applyMutationToEventInstance(instance, def, eventConfigs[instance.defId], mutation, context)
   }
 
   return dest
 }
 
-export type eventDefMutationApplier = (eventDef: EventDef, mutation: EventMutation, calendar: Calendar) => void
+export type eventDefMutationApplier = (eventDef: EventDef, mutation: EventMutation, context: ReducerContext) => void
 
 
-function applyMutationToEventDef(eventDef: EventDef, eventConfig: EventUi, mutation: EventMutation, appliers: eventDefMutationApplier[], calendar: Calendar): EventDef {
+function applyMutationToEventDef(eventDef: EventDef, eventConfig: EventUi, mutation: EventMutation, context: ReducerContext): EventDef {
   let standardProps = mutation.standardProps || {}
 
   // if hasEnd has not been specified, guess a good value based on deltas.
@@ -67,11 +67,11 @@ function applyMutationToEventDef(eventDef: EventDef, eventConfig: EventUi, mutat
     copy.extendedProps = { ...copy.extendedProps, ...mutation.extendedProps }
   }
 
-  for (let applier of appliers) {
-    applier(copy, mutation, calendar)
+  for (let applier of context.pluginHooks.eventDefMutationAppliers) {
+    applier(copy, mutation, context)
   }
 
-  if (!copy.hasEnd && calendar.opt('forceEventDuration')) {
+  if (!copy.hasEnd && context.options.forceEventDuration) {
     copy.hasEnd = true
   }
 
@@ -84,9 +84,9 @@ function applyMutationToEventInstance(
   eventDef: EventDef, // must first be modified by applyMutationToEventDef
   eventConfig: EventUi,
   mutation: EventMutation,
-  calendar: Calendar
+  context: ReducerContext
 ): EventInstance {
-  let dateEnv = calendar.state.dateEnv
+  let { dateEnv } = context
   let forceAllDay = mutation.standardProps && mutation.standardProps.allDay === true
   let clearEnd = mutation.standardProps && mutation.standardProps.hasEnd === false
   let copy = { ...eventInstance } as EventInstance
@@ -119,7 +119,7 @@ function applyMutationToEventInstance(
   if (clearEnd) {
     copy.range = {
       start: copy.range.start,
-      end: calendar.getDefaultEventEnd(eventDef.allDay, copy.range.start)
+      end: context.calendar.getDefaultEventEnd(eventDef.allDay, copy.range.start)
     }
   }
 
@@ -134,7 +134,7 @@ function applyMutationToEventInstance(
 
   // handle invalid durations
   if (copy.range.end < copy.range.start) {
-    copy.range.end = calendar.getDefaultEventEnd(eventDef.allDay, copy.range.start)
+    copy.range.end = context.calendar.getDefaultEventEnd(eventDef.allDay, copy.range.start)
   }
 
   return copy

+ 8 - 8
packages/core/src/structs/event-source.ts

@@ -5,7 +5,7 @@ import { DateRange } from '../datelib/date-range'
 import { EventSourceFunc } from '../event-sources/func-event-source'
 import { EventUi, processUnscopedUiProps } from '../component/event-ui'
 import { ConstraintInput, AllowFunc } from '../validation'
-import { PluginHooks } from '../plugin-system'
+import { ReducerContext } from '../reducers/ReducerContext'
 
 /*
 Parsing and normalization of the EventSource data type, which defines how event data is fetched.
@@ -109,15 +109,15 @@ const SIMPLE_SOURCE_PROPS = {
 }
 
 
-export function doesSourceNeedRange(eventSource: EventSource, pluginHooks: PluginHooks) {
-  let defs = pluginHooks.eventSourceDefs
+export function doesSourceNeedRange(eventSource: EventSource, context: ReducerContext) {
+  let defs = context.pluginHooks.eventSourceDefs
 
   return !defs[eventSource.sourceDefId].ignoreRange
 }
 
 
-export function parseEventSource(raw: EventSourceInput, pluginHooks: PluginHooks, calendar: Calendar): EventSource | null {
-  let defs = pluginHooks.eventSourceDefs
+export function parseEventSource(raw: EventSourceInput, context: ReducerContext): EventSource | null {
+  let defs = context.pluginHooks.eventSourceDefs
 
   for (let i = defs.length - 1; i >= 0; i--) { // later-added plugins take precedence
     let def = defs[i]
@@ -128,7 +128,7 @@ export function parseEventSource(raw: EventSourceInput, pluginHooks: PluginHooks
         typeof raw === 'object' ? raw : {},
         meta,
         i,
-        calendar
+        context
       )
 
       res._raw = raw
@@ -140,11 +140,11 @@ export function parseEventSource(raw: EventSourceInput, pluginHooks: PluginHooks
 }
 
 
-function parseEventSourceProps(raw: ExtendedEventSourceInput, meta: object, sourceDefId: number, calendar: Calendar): EventSource {
+function parseEventSourceProps(raw: ExtendedEventSourceInput, meta: object, sourceDefId: number, context: ReducerContext): EventSource {
   let leftovers0 = {}
   let props = refineProps(raw, SIMPLE_SOURCE_PROPS, {}, leftovers0)
   let leftovers1 = {}
-  let ui = processUnscopedUiProps(leftovers0, calendar, leftovers1)
+  let ui = processUnscopedUiProps(leftovers0, context, leftovers1)
 
   props.isFetching = false
   props.latestFetchId = ''

+ 10 - 10
packages/core/src/structs/event-store.ts

@@ -10,9 +10,9 @@ import {
 } from './event'
 import { EventSource } from './event-source'
 import { expandRecurringRanges } from './recurring-event'
-import { Calendar } from '../Calendar'
 import { filterHash } from '../util/object'
 import { DateRange } from '../datelib/date-range'
+import { ReducerContext } from '../reducers/ReducerContext'
 
 /*
 A data structure that encapsulates EventDefs and EventInstances.
@@ -28,13 +28,13 @@ export interface EventStore {
 export function parseEvents(
   rawEvents: EventInput[],
   sourceId: string,
-  calendar: Calendar,
+  context: ReducerContext,
   allowOpenRange?: boolean
 ): EventStore {
   let eventStore = createEmptyEventStore()
 
   for (let rawEvent of rawEvents) {
-    let tuple = parseEvent(rawEvent, sourceId, calendar, allowOpenRange)
+    let tuple = parseEvent(rawEvent, sourceId, context, allowOpenRange)
 
     if (tuple) {
       eventTupleToStore(tuple, eventStore)
@@ -54,8 +54,8 @@ export function eventTupleToStore(tuple: EventTuple, eventStore: EventStore = cr
   return eventStore
 }
 
-export function expandRecurring(eventStore: EventStore, framingRange: DateRange, calendar: Calendar): EventStore {
-  let dateEnv = calendar.state.dateEnv
+export function expandRecurring(eventStore: EventStore, framingRange: DateRange, context: ReducerContext): EventStore {
+  let { dateEnv, pluginHooks, computedOptions } = context
   let { defs, instances } = eventStore
 
   // remove existing recurring instances
@@ -71,11 +71,11 @@ export function expandRecurring(eventStore: EventStore, framingRange: DateRange,
 
       if (!duration) {
         duration = def.allDay ?
-          calendar.defaultAllDayEventDuration :
-          calendar.defaultTimedEventDuration
+          computedOptions.defaultAllDayEventDuration :
+          computedOptions.defaultTimedEventDuration
       }
 
-      let starts = expandRecurringRanges(def, duration, framingRange, calendar.state.dateEnv, calendar.state.pluginHooks.recurringTypes)
+      let starts = expandRecurringRanges(def, duration, framingRange, dateEnv, pluginHooks.recurringTypes)
 
       for (let start of starts) {
         let instance = createEventInstance(defId, {
@@ -119,8 +119,8 @@ function isEventDefsGrouped(def0: EventDef, def1: EventDef): boolean {
   return Boolean(def0.groupId && def0.groupId === def1.groupId)
 }
 
-export function transformRawEvents(rawEvents, eventSource: EventSource, calendar: Calendar) {
-  let calEachTransform = calendar.opt('eventDataTransform')
+export function transformRawEvents(rawEvents, eventSource: EventSource, context: ReducerContext) {
+  let calEachTransform = context.options.eventDataTransform
   let sourceEachTransform = eventSource ? eventSource.eventDataTransform : null
 
   if (sourceEachTransform) {

+ 23 - 23
packages/core/src/structs/event.ts

@@ -1,12 +1,12 @@
 import { refineProps, guid } from '../util/misc'
 import { DateInput } from '../datelib/env'
-import { Calendar } from '../Calendar'
 import { DateRange } from '../datelib/date-range'
 import { startOfDay } from '../datelib/marker'
 import { parseRecurring } from './recurring-event'
 import { Duration } from '../datelib/duration'
 import { UnscopedEventUiInput, EventUi, processUnscopedUiProps } from '../component/event-ui'
 import { __assign } from 'tslib'
+import { ReducerContext } from '../reducers/ReducerContext'
 
 /*
 Utils for parsing event-input data. Each util parses a subset of the event-input's data.
@@ -77,19 +77,19 @@ export const DATE_PROPS = {
 }
 
 
-export function parseEvent(raw: EventInput, sourceId: string, calendar: Calendar, allowOpenRange?: boolean): EventTuple | null {
-  let defaultAllDay = computeIsdefaultAllDay(sourceId, calendar)
+export function parseEvent(raw: EventInput, sourceId: string, context: ReducerContext, allowOpenRange?: boolean): EventTuple | null {
+  let defaultAllDay = computeIsDefaultAllDay(sourceId, context)
   let leftovers0 = {}
   let recurringRes = parseRecurring(
     raw, // raw, but with single-event stuff stripped out
     defaultAllDay,
-    calendar.state.dateEnv,
-    calendar.state.pluginHooks.recurringTypes,
+    context.dateEnv,
+    context.pluginHooks.recurringTypes,
     leftovers0 // will populate with non-recurring props
   )
 
   if (recurringRes) {
-    let def = parseEventDef(leftovers0, sourceId, recurringRes.allDay, Boolean(recurringRes.duration), calendar)
+    let def = parseEventDef(leftovers0, sourceId, recurringRes.allDay, Boolean(recurringRes.duration), context)
 
     def.recurringDef = { // don't want all the props from recurringRes. TODO: more efficient way to do this
       typeId: recurringRes.typeId,
@@ -101,10 +101,10 @@ export function parseEvent(raw: EventInput, sourceId: string, calendar: Calendar
 
   } else {
     let leftovers1 = {}
-    let singleRes = parseSingle(raw, defaultAllDay, calendar, leftovers1, allowOpenRange)
+    let singleRes = parseSingle(raw, defaultAllDay, context, leftovers1, allowOpenRange)
 
     if (singleRes) {
-      let def = parseEventDef(leftovers1, sourceId, singleRes.allDay, singleRes.hasEnd, calendar)
+      let def = parseEventDef(leftovers1, sourceId, singleRes.allDay, singleRes.hasEnd, context)
       let instance = createEventInstance(def.defId, singleRes.range, singleRes.forcedStartTzo, singleRes.forcedEndTzo)
 
       return { def, instance }
@@ -120,16 +120,16 @@ Will NOT populate extendedProps with the leftover properties.
 Will NOT populate date-related props.
 The EventNonDateInput has been normalized (id => publicId, etc).
 */
-export function parseEventDef(raw: EventNonDateInput, sourceId: string, allDay: boolean, hasEnd: boolean, calendar: Calendar): EventDef {
+export function parseEventDef(raw: EventNonDateInput, sourceId: string, allDay: boolean, hasEnd: boolean, context: ReducerContext): EventDef {
   let leftovers = {}
-  let def = pluckNonDateProps(raw, calendar, leftovers) as EventDef
+  let def = pluckNonDateProps(raw, context, leftovers) as EventDef
 
   def.defId = guid()
   def.sourceId = sourceId
   def.allDay = allDay
   def.hasEnd = hasEnd
 
-  for (let eventDefParser of calendar.state.pluginHooks.eventDefParsers) {
+  for (let eventDefParser of context.pluginHooks.eventDefParsers) {
     let newLeftovers = {}
     eventDefParser(def, leftovers, newLeftovers)
     leftovers = newLeftovers
@@ -164,7 +164,7 @@ export function createEventInstance(
 }
 
 
-function parseSingle(raw: EventInput, defaultAllDay: boolean | null, calendar: Calendar, leftovers?, allowOpenRange?: boolean) {
+function parseSingle(raw: EventInput, defaultAllDay: boolean | null, context: ReducerContext, leftovers?, allowOpenRange?: boolean) {
   let props = pluckDateProps(raw, leftovers)
   let allDay = props.allDay
   let startMeta
@@ -173,7 +173,7 @@ function parseSingle(raw: EventInput, defaultAllDay: boolean | null, calendar: C
   let endMeta
   let endMarker = null
 
-  startMeta = calendar.state.dateEnv.createMarkerMeta(props.start)
+  startMeta = context.dateEnv.createMarkerMeta(props.start)
 
   if (startMeta) {
     startMarker = startMeta.marker
@@ -182,7 +182,7 @@ function parseSingle(raw: EventInput, defaultAllDay: boolean | null, calendar: C
   }
 
   if (props.end != null) {
-    endMeta = calendar.state.dateEnv.createMarkerMeta(props.end)
+    endMeta = context.dateEnv.createMarkerMeta(props.end)
   }
 
   if (allDay == null) {
@@ -214,13 +214,13 @@ function parseSingle(raw: EventInput, defaultAllDay: boolean | null, calendar: C
   if (endMarker) {
     hasEnd = true
   } else if (!allowOpenRange) {
-    hasEnd = calendar.opt('forceEventDuration') || false
+    hasEnd = context.options.forceEventDuration || false
 
-    endMarker = calendar.state.dateEnv.add(
+    endMarker = context.dateEnv.add(
       startMarker,
       allDay ?
-        calendar.defaultAllDayEventDuration :
-        calendar.defaultTimedEventDuration
+        context.computedOptions.defaultAllDayEventDuration :
+        context.computedOptions.defaultTimedEventDuration
     )
   }
 
@@ -244,10 +244,10 @@ function pluckDateProps(raw: EventInput, leftovers: any) {
 }
 
 
-function pluckNonDateProps(raw: EventInput, calendar: Calendar, leftovers?) {
+function pluckNonDateProps(raw: EventInput, context: ReducerContext, leftovers?) {
   let preLeftovers = {}
   let props = refineProps(raw, NON_DATE_PROPS, {}, preLeftovers)
-  let ui = processUnscopedUiProps(preLeftovers, calendar, leftovers)
+  let ui = processUnscopedUiProps(preLeftovers, context, leftovers)
 
   props.publicId = props.id
   delete props.id
@@ -258,16 +258,16 @@ function pluckNonDateProps(raw: EventInput, calendar: Calendar, leftovers?) {
 }
 
 
-function computeIsdefaultAllDay(sourceId: string, calendar: Calendar): boolean | null {
+function computeIsDefaultAllDay(sourceId: string, context: ReducerContext): boolean | null {
   let res = null
 
   if (sourceId) {
-    let source = calendar.state.eventSources[sourceId]
+    let source = context.calendar.state.eventSources[sourceId]
     res = source.defaultAllDay
   }
 
   if (res == null) {
-    res = calendar.opt('defaultAllDay')
+    res = context.options.defaultAllDay
   }
 
   return res

+ 1 - 1
packages/core/src/structs/view-config.tsx

@@ -73,7 +73,7 @@ function createViewHookComponent(options) {
         {(context: ComponentContext) => (
           <ViewRoot viewSpec={context.viewSpec}>
             {(rootElRef, viewClassNames) => {
-              let hookProps = { ...viewProps, nextDayThreshold: context.nextDayThreshold }
+              let hookProps = { ...viewProps, nextDayThreshold: context.computedOptions.nextDayThreshold }
               return (
                 <RenderHook name='' options={options} hookProps={hookProps} elRef={rootElRef}>
                   {(rootElRef, customClassNames, innerElRef, innerContent) => (

+ 36 - 30
packages/core/src/validation.ts

@@ -1,5 +1,4 @@
 import { EventStore, expandRecurring, filterEventStoreDefs, parseEvents, createEmptyEventStore } from './structs/event-store'
-import { Calendar } from './Calendar'
 import { DateSpan, DateSpanApi } from './structs/date-span'
 import { rangeContainsRange, rangesIntersect, DateRange, OpenDateRange } from './datelib/date-range'
 import { EventApi } from './api/EventApi'
@@ -9,31 +8,36 @@ import { EventInput } from './structs/event'
 import { EventInteractionState } from './interactions/event-interaction-state'
 import { SplittableProps } from './component/event-splitting'
 import { mapHash } from './util/object'
+import { ReducerContext } from './reducers/ReducerContext'
 
 // TODO: rename to "criteria" ?
 export type ConstraintInput = 'businessHours' | string | EventInput | EventInput[]
 export type Constraint = 'businessHours' | string | EventStore | false // false means won't pass at all
 export type OverlapFunc = ((stillEvent: EventApi, movingEvent: EventApi | null) => boolean)
 export type AllowFunc = (span: DateSpanApi, movingEvent: EventApi | null) => boolean
-export type isPropsValidTester = (props: SplittableProps, calendar: Calendar) => boolean
+export type isPropsValidTester = (props: SplittableProps, context: ReducerContext) => boolean
 
 
 // high-level segmenting-aware tester functions
 // ------------------------------------------------------------------------------------------------------------------------
 
-export function isInteractionValid(interaction: EventInteractionState, calendar: Calendar) {
-  return isNewPropsValid({ eventDrag: interaction }, calendar) // HACK: the eventDrag props is used for ALL interactions
+
+export function isInteractionValid(interaction: EventInteractionState, context: ReducerContext) {
+  return isNewPropsValid({ eventDrag: interaction }, context) // HACK: the eventDrag props is used for ALL interactions
 }
 
-export function isDateSelectionValid(dateSelection: DateSpan, calendar: Calendar) {
-  return isNewPropsValid({ dateSelection }, calendar)
+
+export function isDateSelectionValid(dateSelection: DateSpan, context: ReducerContext) {
+  return isNewPropsValid({ dateSelection }, context)
 }
 
-function isNewPropsValid(newProps, calendar: Calendar) {
-  let view = calendar.component.view
+
+function isNewPropsValid(newProps, context: ReducerContext) {
+  let { calendar } = context
+  let viewComponent = calendar.component.view
 
   let props = {
-    businessHours: view ? view.props.businessHours : createEmptyEventStore(), // why? yuck
+    businessHours: viewComponent ? viewComponent.props.businessHours : createEmptyEventStore(), // why? yuck
     dateSelection: '',
     eventStore: calendar.state.eventStore,
     eventUiBases: calendar.eventUiBases,
@@ -43,16 +47,17 @@ function isNewPropsValid(newProps, calendar: Calendar) {
     ...newProps
   }
 
-  return (calendar.state.pluginHooks.isPropsValid || isPropsValid)(props, calendar)
+  return (calendar.state.pluginHooks.isPropsValid || isPropsValid)(props, context)
 }
 
-export function isPropsValid(state: SplittableProps, calendar: Calendar, dateSpanMeta = {}, filterConfig?): boolean {
 
-  if (state.eventDrag && !isInteractionPropsValid(state, calendar, dateSpanMeta, filterConfig)) {
+export function isPropsValid(state: SplittableProps, context: ReducerContext, dateSpanMeta = {}, filterConfig?): boolean {
+
+  if (state.eventDrag && !isInteractionPropsValid(state, context, dateSpanMeta, filterConfig)) {
     return false
   }
 
-  if (state.dateSelection && !isDateSelectionPropsValid(state, calendar, dateSpanMeta, filterConfig)) {
+  if (state.dateSelection && !isDateSelectionPropsValid(state, context, dateSpanMeta, filterConfig)) {
     return false
   }
 
@@ -63,7 +68,8 @@ export function isPropsValid(state: SplittableProps, calendar: Calendar, dateSpa
 // Moving Event Validation
 // ------------------------------------------------------------------------------------------------------------------------
 
-function isInteractionPropsValid(state: SplittableProps, calendar: Calendar, dateSpanMeta: any, filterConfig): boolean {
+function isInteractionPropsValid(state: SplittableProps, context: ReducerContext, dateSpanMeta: any, filterConfig): boolean {
+  let { calendar } = context
   let interaction = state.eventDrag // HACK: the eventDrag props is used for ALL interactions
 
   let subjectEventStore = interaction.mutatedEvents
@@ -92,13 +98,13 @@ function isInteractionPropsValid(state: SplittableProps, calendar: Calendar, dat
     let subjectDef = subjectDefs[subjectInstance.defId]
 
     // constraint
-    if (!allConstraintsPass(subjectConfig.constraints, subjectRange, otherEventStore, state.businessHours, calendar)) {
+    if (!allConstraintsPass(subjectConfig.constraints, subjectRange, otherEventStore, state.businessHours, context)) {
       return false
     }
 
     // overlap
 
-    let overlapFunc = calendar.opt('eventOverlap')
+    let overlapFunc = context.options.eventOverlap
     if (typeof overlapFunc !== 'function') { overlapFunc = null }
 
     for (let otherInstanceId in otherInstances) {
@@ -165,27 +171,27 @@ function isInteractionPropsValid(state: SplittableProps, calendar: Calendar, dat
 // Date Selection Validation
 // ------------------------------------------------------------------------------------------------------------------------
 
-function isDateSelectionPropsValid(state: SplittableProps, calendar: Calendar, dateSpanMeta: any, filterConfig): boolean {
+function isDateSelectionPropsValid(state: SplittableProps, context: ReducerContext, dateSpanMeta: any, filterConfig): boolean {
   let relevantEventStore = state.eventStore
   let relevantDefs = relevantEventStore.defs
   let relevantInstances = relevantEventStore.instances
 
   let selection = state.dateSelection
   let selectionRange = selection.range
-  let { selectionConfig } = calendar
+  let { selectionConfig } = context.calendar
 
   if (filterConfig) {
     selectionConfig = filterConfig(selectionConfig)
   }
 
   // constraint
-  if (!allConstraintsPass(selectionConfig.constraints, selectionRange, relevantEventStore, state.businessHours, calendar)) {
+  if (!allConstraintsPass(selectionConfig.constraints, selectionRange, relevantEventStore, state.businessHours, context)) {
     return false
   }
 
   // overlap
 
-  let overlapFunc = calendar.opt('selectOverlap')
+  let overlapFunc = context.options.selectOverlap
   if (typeof overlapFunc !== 'function') { overlapFunc = null }
 
   for (let relevantInstanceId in relevantInstances) {
@@ -199,7 +205,7 @@ function isDateSelectionPropsValid(state: SplittableProps, calendar: Calendar, d
       }
 
       if (overlapFunc && !overlapFunc(
-        new EventApi(calendar, relevantDefs[relevantInstance.defId], relevantInstance)
+        new EventApi(context.calendar, relevantDefs[relevantInstance.defId], relevantInstance)
       )) {
         return false
       }
@@ -212,7 +218,7 @@ function isDateSelectionPropsValid(state: SplittableProps, calendar: Calendar, d
     let fullDateSpan = { ...dateSpanMeta, ...selection }
 
     if (!selectionAllow(
-      calendar.buildDateSpanApi(fullDateSpan),
+      context.calendar.buildDateSpanApi(fullDateSpan),
       null
     )) {
       return false
@@ -231,11 +237,11 @@ function allConstraintsPass(
   subjectRange: DateRange,
   otherEventStore: EventStore,
   businessHoursUnexpanded: EventStore,
-  calendar: Calendar
+  context: ReducerContext
 ): boolean {
   for (let constraint of constraints) {
     if (!anyRangesContainRange(
-      constraintToRanges(constraint, subjectRange, otherEventStore, businessHoursUnexpanded, calendar),
+      constraintToRanges(constraint, subjectRange, otherEventStore, businessHoursUnexpanded, context),
       subjectRange
     )) {
       return false
@@ -250,12 +256,12 @@ function constraintToRanges(
   subjectRange: DateRange, // for expanding a recurring constraint, or expanding business hours
   otherEventStore: EventStore, // for if constraint is an even group ID
   businessHoursUnexpanded: EventStore, // for if constraint is 'businessHours'
-  calendar: Calendar // for expanding businesshours
+  context: ReducerContext // for expanding businesshours
 ): OpenDateRange[] {
 
   if (constraint === 'businessHours') {
     return eventStoreToRanges(
-      expandRecurring(businessHoursUnexpanded, subjectRange, calendar)
+      expandRecurring(businessHoursUnexpanded, subjectRange, context)
     )
 
   } else if (typeof constraint === 'string') { // an group ID
@@ -267,7 +273,7 @@ function constraintToRanges(
 
   } else if (typeof constraint === 'object' && constraint) { // non-null object
     return eventStoreToRanges(
-      expandRecurring(constraint, subjectRange, calendar)
+      expandRecurring(constraint, subjectRange, context)
     )
   }
 
@@ -302,12 +308,12 @@ function anyRangesContainRange(outerRanges: DateRange[], innerRange: DateRange):
 // Parsing
 // ------------------------------------------------------------------------------------------------------------------------
 
-export function normalizeConstraint(input: ConstraintInput, calendar: Calendar): Constraint | null {
+export function normalizeConstraint(input: ConstraintInput, context: ReducerContext): Constraint | null {
   if (Array.isArray(input)) {
-    return parseEvents(input, '', calendar, true) // allowOpenRange=true
+    return parseEvents(input, '', context, true) // allowOpenRange=true
 
   } else if (typeof input === 'object' && input) { // non-null object
-    return parseEvents([ input ], '', calendar, true) // allowOpenRange=true
+    return parseEvents([ input ], '', context, true) // allowOpenRange=true
 
   } else if (input != null) {
     return String(input)

+ 1 - 1
packages/daygrid/src/DayTable.tsx

@@ -53,7 +53,7 @@ export class DayTable extends DateComponent<DayTableProps, ComponentContext> {
       <Table
         ref={this.tableRef}
         elRef={this.handleRootEl}
-        { ...this.slicer.sliceProps(props, context.dateProfile, props.nextDayThreshold, context.calendar, dayTableModel) }
+        { ...this.slicer.sliceProps(props, context.dateProfile, props.nextDayThreshold, context, dayTableModel) }
         cells={dayTableModel.cells}
         colGroupNode={props.colGroupNode}
         tableMinWidth={props.tableMinWidth}

+ 1 - 1
packages/daygrid/src/DayTableView.tsx

@@ -43,7 +43,7 @@ export class DayTableView extends TableView {
         eventSelection={props.eventSelection}
         eventDrag={props.eventDrag}
         eventResize={props.eventResize}
-        nextDayThreshold={context.nextDayThreshold}
+        nextDayThreshold={context.computedOptions.nextDayThreshold}
         colGroupNode={contentArg.tableColGroupNode}
         tableMinWidth={contentArg.tableMinWidth}
         dayMaxEvents={options.dayMaxEvents}

+ 1 - 1
packages/daygrid/src/TableRow.tsx

@@ -86,7 +86,7 @@ export class TableRow extends DateComponent<TableRowProps, TableRowState> {
       state.segHeights,
       state.maxContentHeight,
       colCnt,
-      context.eventOrderSpecs
+      context.computedOptions.eventOrderSpecs
     )
 
     let selectedInstanceHash = // TODO: messy way to compute this

+ 36 - 36
packages/interaction/src/interactions-external/ExternalElementDragging.ts

@@ -6,7 +6,6 @@ import {
   createEmptyEventStore, eventTupleToStore,
   config,
   DateSpan, DatePointApi,
-  Calendar,
   EventInteractionState,
   DragMetaInput, DragMeta, parseDragMeta,
   EventApi,
@@ -14,7 +13,8 @@ import {
   enableCursor, disableCursor,
   isInteractionValid,
   ElementDragging,
-  ViewApi
+  ViewApi,
+  ReducerContext
 } from '@fullcalendar/core'
 import { HitDragging } from '../interactions/HitDragging'
 import { __assign } from 'tslib'
@@ -36,7 +36,7 @@ of a calendar onto a calendar.
 export class ExternalElementDragging {
 
   hitDragging: HitDragging
-  receivingCalendar: Calendar | null = null
+  receivingContext: ReducerContext | null = null
   droppableEvent: EventTuple | null = null // will exist for all drags, even if create:false
   suppliedDragMeta: DragMetaGenerator | null = null
   dragMeta: DragMeta | null = null
@@ -68,7 +68,7 @@ export class ExternalElementDragging {
 
   handleHitUpdate = (hit: Hit | null, isFinal: boolean, ev: PointerDragEvent) => {
     let { dragging } = this.hitDragging
-    let receivingCalendar: Calendar | null = null
+    let receivingContext: ReducerContext | null = null
     let droppableEvent: EventTuple | null = null
     let isInvalid = false
     let interaction: EventInteractionState = {
@@ -78,18 +78,18 @@ export class ExternalElementDragging {
     }
 
     if (hit) {
-      receivingCalendar = hit.component.context.calendar
+      receivingContext = hit.component.context
 
-      if (this.canDropElOnCalendar(ev.subjectEl as HTMLElement, receivingCalendar)) {
+      if (this.canDropElOnCalendar(ev.subjectEl as HTMLElement, receivingContext)) {
 
         droppableEvent = computeEventForDateSpan(
           hit.dateSpan,
           this.dragMeta!,
-          receivingCalendar
+          receivingContext
         )
 
         interaction.mutatedEvents = eventTupleToStore(droppableEvent)
-        isInvalid = !isInteractionValid(interaction, receivingCalendar)
+        isInvalid = !isInteractionValid(interaction, receivingContext)
 
         if (isInvalid) {
           interaction.mutatedEvents = createEmptyEventStore()
@@ -98,7 +98,7 @@ export class ExternalElementDragging {
       }
     }
 
-    this.displayDrag(receivingCalendar, interaction)
+    this.displayDrag(receivingContext, interaction)
 
     // show mirror if no already-rendered mirror element OR if we are shutting down the mirror (?)
     // TODO: wish we could somehow wait for dispatch to guarantee render
@@ -115,46 +115,46 @@ export class ExternalElementDragging {
     if (!isFinal) {
       dragging.setMirrorNeedsRevert(!droppableEvent)
 
-      this.receivingCalendar = receivingCalendar
+      this.receivingContext = receivingContext
       this.droppableEvent = droppableEvent
     }
   }
 
   handleDragEnd = (pev: PointerDragEvent) => {
-    let { receivingCalendar, droppableEvent } = this
+    let { receivingContext, droppableEvent } = this
 
     this.clearDrag()
 
-    if (receivingCalendar && droppableEvent) {
+    if (receivingContext && droppableEvent) {
       let finalHit = this.hitDragging.finalHit!
       let finalView = finalHit.component.context.viewApi
       let dragMeta = this.dragMeta!
       let arg = {
-        ...receivingCalendar.buildDatePointApi(finalHit.dateSpan),
+        ...receivingContext.calendar.buildDatePointApi(finalHit.dateSpan),
         draggedEl: pev.subjectEl as HTMLElement,
         jsEvent: pev.origEvent as MouseEvent, // Is this always a mouse event? See #4655
         view: finalView
       }
-      receivingCalendar.emitter.trigger('drop', arg)
+      receivingContext.emitter.trigger('drop', arg)
 
       if (dragMeta.create) {
-        receivingCalendar.dispatch({
+        receivingContext.dispatch({
           type: 'MERGE_EVENTS',
           eventStore: eventTupleToStore(droppableEvent)
         })
 
         if (pev.isTouch) {
-          receivingCalendar.dispatch({
+          receivingContext.dispatch({
             type: 'SELECT_EVENT',
             eventInstanceId: droppableEvent.instance.instanceId
           })
         }
 
         // signal that an external event landed
-        receivingCalendar.emitter.trigger('eventReceive', {
+        receivingContext.emitter.trigger('eventReceive', {
           draggedEl: pev.subjectEl as HTMLElement,
           event: new EventApi(
-            receivingCalendar,
+            receivingContext.calendar,
             droppableEvent.def,
             droppableEvent.instance
           ),
@@ -163,30 +163,30 @@ export class ExternalElementDragging {
       }
     }
 
-    this.receivingCalendar = null
+    this.receivingContext = null
     this.droppableEvent = null
   }
 
-  displayDrag(nextCalendar: Calendar | null, state: EventInteractionState) {
-    let prevCalendar = this.receivingCalendar
+  displayDrag(nextContext: ReducerContext | null, state: EventInteractionState) {
+    let prevContext = this.receivingContext
 
-    if (prevCalendar && prevCalendar !== nextCalendar) {
-      prevCalendar.dispatch({ type: 'UNSET_EVENT_DRAG' })
+    if (prevContext && prevContext !== nextContext) {
+      prevContext.dispatch({ type: 'UNSET_EVENT_DRAG' })
     }
 
-    if (nextCalendar) {
-      nextCalendar.dispatch({ type: 'SET_EVENT_DRAG', state })
+    if (nextContext) {
+      nextContext.dispatch({ type: 'SET_EVENT_DRAG', state })
     }
   }
 
   clearDrag() {
-    if (this.receivingCalendar) {
-      this.receivingCalendar.dispatch({ type: 'UNSET_EVENT_DRAG' })
+    if (this.receivingContext) {
+      this.receivingContext.dispatch({ type: 'UNSET_EVENT_DRAG' })
     }
   }
 
-  canDropElOnCalendar(el: HTMLElement, receivingCalendar: Calendar): boolean {
-    let dropAccept = receivingCalendar.opt('dropAccept')
+  canDropElOnCalendar(el: HTMLElement, receivingContext: ReducerContext): boolean {
+    let dropAccept = receivingContext.options.dropAccept
 
     if (typeof dropAccept === 'function') {
       return dropAccept(el)
@@ -202,10 +202,10 @@ export class ExternalElementDragging {
 // Utils for computing event store from the DragMeta
 // ----------------------------------------------------------------------------------------------------
 
-function computeEventForDateSpan(dateSpan: DateSpan, dragMeta: DragMeta, calendar: Calendar): EventTuple {
+function computeEventForDateSpan(dateSpan: DateSpan, dragMeta: DragMeta, context: ReducerContext): EventTuple {
   let defProps = { ...dragMeta.leftoverProps }
 
-  for (let transform of calendar.state.pluginHooks.externalDefTransforms) {
+  for (let transform of context.pluginHooks.externalDefTransforms) {
     __assign(defProps, transform(dateSpan, dragMeta))
   }
 
@@ -213,8 +213,8 @@ function computeEventForDateSpan(dateSpan: DateSpan, dragMeta: DragMeta, calenda
     defProps,
     dragMeta.sourceId,
     dateSpan.allDay,
-    calendar.opt('forceEventDuration') || Boolean(dragMeta.duration), // hasEnd
-    calendar
+    context.options.forceEventDuration || Boolean(dragMeta.duration), // hasEnd
+    context
   )
 
   let start = dateSpan.range.start
@@ -222,12 +222,12 @@ function computeEventForDateSpan(dateSpan: DateSpan, dragMeta: DragMeta, calenda
   // only rely on time info if drop zone is all-day,
   // otherwise, we already know the time
   if (dateSpan.allDay && dragMeta.startTime) {
-    start = calendar.state.dateEnv.add(start, dragMeta.startTime)
+    start = context.dateEnv.add(start, dragMeta.startTime)
   }
 
   let end = dragMeta.duration ?
-    calendar.state.dateEnv.add(start, dragMeta.duration) :
-    calendar.getDefaultEventEnd(dateSpan.allDay, start)
+    context.dateEnv.add(start, dragMeta.duration) :
+    context.calendar.getDefaultEventEnd(dateSpan.allDay, start)
 
   let instance = createEventInstance(def.defId, { start, end })
 

+ 51 - 52
packages/interaction/src/interactions/EventDragging.ts

@@ -5,14 +5,14 @@ import {
   startOfDay,
   elementClosest,
   EventStore, getRelevantEvents, createEmptyEventStore,
-  Calendar,
   EventInteractionState,
   diffDates, enableCursor, disableCursor,
   EventRenderRange, getElSeg,
   EventApi,
   eventDragMutationMassager,
   Interaction, InteractionSettings, interactionSettingsStore,
-  EventDropTransformers
+  EventDropTransformers,
+  ReducerContext
 } from '@fullcalendar/core'
 import { HitDragging, isHitsEqual } from './HitDragging'
 import { FeaturefulElementDragging } from '../dnd/FeaturefulElementDragging'
@@ -34,7 +34,7 @@ export class EventDragging extends Interaction { // TODO: rename to EventSelecti
   isDragging: boolean = false
   eventRange: EventRenderRange | null = null
   relevantEvents: EventStore | null = null // the events being dragged
-  receivingCalendar: Calendar | null = null
+  receivingContext: ReducerContext | null = null
   validMutation: EventMutation | null = null
   mutatedRelevantEvents: EventStore | null = null
 
@@ -134,10 +134,10 @@ export class EventDragging extends Interaction { // TODO: rename to EventSelecti
 
     let relevantEvents = this.relevantEvents!
     let initialHit = this.hitDragging.initialHit!
-    let initialCalendar = this.component.context.calendar
+    let initialContext = this.component.context
 
     // states based on new hit
-    let receivingCalendar: Calendar | null = null
+    let receivingContext: ReducerContext | null = null
     let mutation: EventMutation | null = null
     let mutatedRelevantEvents: EventStore | null = null
     let isInvalid = false
@@ -149,17 +149,17 @@ export class EventDragging extends Interaction { // TODO: rename to EventSelecti
 
     if (hit) {
       let receivingComponent = hit.component
-      receivingCalendar = receivingComponent.context.calendar
-      let receivingOptions = receivingComponent.context.options
+      receivingContext = receivingComponent.context
+      let receivingOptions = receivingContext.options
 
       if (
-        initialCalendar === receivingCalendar ||
+        initialContext === receivingContext ||
         receivingOptions.editable && receivingOptions.droppable
       ) {
-        mutation = computeEventMutation(initialHit, hit, receivingCalendar.state.pluginHooks.eventDragMutationMassagers)
+        mutation = computeEventMutation(initialHit, hit, receivingContext.calendar.state.pluginHooks.eventDragMutationMassagers)
 
         if (mutation) {
-          mutatedRelevantEvents = applyMutationToEventStore(relevantEvents, receivingCalendar.eventUiBases, mutation, receivingCalendar)
+          mutatedRelevantEvents = applyMutationToEventStore(relevantEvents, receivingContext.calendar.eventUiBases, mutation, receivingContext)
           interaction.mutatedEvents = mutatedRelevantEvents
 
           if (!receivingComponent.isInteractionValid(interaction)) {
@@ -171,11 +171,11 @@ export class EventDragging extends Interaction { // TODO: rename to EventSelecti
           }
         }
       } else {
-        receivingCalendar = null
+        receivingContext = null
       }
     }
 
-    this.displayDrag(receivingCalendar, interaction)
+    this.displayDrag(receivingContext, interaction)
 
     if (!isInvalid) {
       enableCursor()
@@ -186,7 +186,7 @@ export class EventDragging extends Interaction { // TODO: rename to EventSelecti
     if (!isFinal) {
 
       if (
-        initialCalendar === receivingCalendar && // TODO: write test for this
+        initialContext === receivingContext && // TODO: write test for this
         isHitsEqual(initialHit, hit)
       ) {
         mutation = null
@@ -201,7 +201,7 @@ export class EventDragging extends Interaction { // TODO: rename to EventSelecti
       )
 
       // assign states based on new hit
-      this.receivingCalendar = receivingCalendar
+      this.receivingContext = receivingContext
       this.validMutation = mutation
       this.mutatedRelevantEvents = mutatedRelevantEvents
     }
@@ -216,20 +216,19 @@ export class EventDragging extends Interaction { // TODO: rename to EventSelecti
   handleDragEnd = (ev: PointerDragEvent) => {
 
     if (this.isDragging) {
-      let { context } = this.component
-      let initialCalendar = context.calendar
-      let initialView = context.viewApi
-      let { receivingCalendar, validMutation } = this
+      let initialContext = this.component.context
+      let initialView = initialContext.viewApi
+      let { receivingContext, validMutation } = this
       let eventDef = this.eventRange!.def
       let eventInstance = this.eventRange!.instance
-      let eventApi = new EventApi(initialCalendar, eventDef, eventInstance)
+      let eventApi = new EventApi(initialContext.calendar, eventDef, eventInstance)
       let relevantEvents = this.relevantEvents!
       let mutatedRelevantEvents = this.mutatedRelevantEvents!
       let { finalHit } = this.hitDragging
 
       this.clearDrag() // must happen after revert animation
 
-      initialCalendar.emitter.trigger('eventDragStop', {
+      initialContext.emitter.trigger('eventDragStop', {
         el: this.subjectEl,
         event: eventApi,
         jsEvent: ev.origEvent as MouseEvent, // Is this always a mouse event? See #4655
@@ -239,17 +238,17 @@ export class EventDragging extends Interaction { // TODO: rename to EventSelecti
       if (validMutation) {
 
         // dropped within same calendar
-        if (receivingCalendar === initialCalendar) {
+        if (receivingContext === initialContext) {
 
-          initialCalendar.dispatch({
+          initialContext.dispatch({
             type: 'MERGE_EVENTS',
             eventStore: mutatedRelevantEvents
           })
 
           let transformed: ReturnType<EventDropTransformers> = {}
 
-          for (let transformer of initialCalendar.state.pluginHooks.eventDropTransformers) {
-            __assign(transformed, transformer(validMutation, initialCalendar))
+          for (let transformer of initialContext.calendar.state.pluginHooks.eventDropTransformers) {
+            __assign(transformed, transformer(validMutation, initialContext))
           }
 
           const eventDropArg = {
@@ -258,12 +257,12 @@ export class EventDragging extends Interaction { // TODO: rename to EventSelecti
             delta: validMutation.datesDelta!,
             oldEvent: eventApi,
             event: new EventApi( // the data AFTER the mutation
-              initialCalendar,
+              initialContext.calendar,
               mutatedRelevantEvents.defs[eventDef.defId],
               eventInstance ? mutatedRelevantEvents.instances[eventInstance.instanceId] : null
             ),
             revert: function() {
-              initialCalendar.dispatch({
+              initialContext.dispatch({
                 type: 'MERGE_EVENTS',
                 eventStore: relevantEvents
               })
@@ -272,45 +271,45 @@ export class EventDragging extends Interaction { // TODO: rename to EventSelecti
             view: initialView
           }
 
-          initialCalendar.emitter.trigger('eventDrop', eventDropArg)
+          initialContext.emitter.trigger('eventDrop', eventDropArg)
 
         // dropped in different calendar
-        } else if (receivingCalendar) {
+        } else if (receivingContext) {
 
-          initialCalendar.emitter.trigger('eventLeave', {
+          initialContext.emitter.trigger('eventLeave', {
             draggedEl: ev.subjectEl as HTMLElement,
             event: eventApi,
             view: initialView
           })
 
-          initialCalendar.dispatch({
+          initialContext.dispatch({
             type: 'REMOVE_EVENT_INSTANCES',
             instances: this.mutatedRelevantEvents!.instances
           })
 
-          receivingCalendar.dispatch({
+          receivingContext.dispatch({
             type: 'MERGE_EVENTS',
             eventStore: this.mutatedRelevantEvents!
           })
 
           if (ev.isTouch) {
-            receivingCalendar.dispatch({
+            receivingContext.dispatch({
               type: 'SELECT_EVENT',
               eventInstanceId: eventInstance.instanceId
             })
           }
 
-          receivingCalendar.emitter.trigger('drop', {
-            ...receivingCalendar.buildDatePointApi(finalHit.dateSpan),
+          receivingContext.emitter.trigger('drop', {
+            ...receivingContext.calendar.buildDatePointApi(finalHit.dateSpan),
             draggedEl: ev.subjectEl as HTMLElement,
             jsEvent: ev.origEvent as MouseEvent, // Is this always a mouse event? See #4655
             view: finalHit.component.context.viewApi
           })
 
-          receivingCalendar.emitter.trigger('eventReceive', {
+          receivingContext.emitter.trigger('eventReceive', {
             draggedEl: ev.subjectEl as HTMLElement,
             event: new EventApi( // the data AFTER the mutation
-              receivingCalendar,
+              receivingContext.calendar,
               mutatedRelevantEvents.defs[eventDef.defId],
               mutatedRelevantEvents.instances[eventInstance.instanceId]
             ),
@@ -319,7 +318,7 @@ export class EventDragging extends Interaction { // TODO: rename to EventSelecti
         }
 
       } else {
-        initialCalendar.emitter.trigger('_noEventDrop')
+        initialContext.emitter.trigger('_noEventDrop')
       }
     }
 
@@ -327,17 +326,17 @@ export class EventDragging extends Interaction { // TODO: rename to EventSelecti
   }
 
   // render a drag state on the next receivingCalendar
-  displayDrag(nextCalendar: Calendar | null, state: EventInteractionState) {
-    let initialCalendar = this.component.context.calendar
-    let prevCalendar = this.receivingCalendar
+  displayDrag(nextContext: ReducerContext | null, state: EventInteractionState) {
+    let initialContext = this.component.context
+    let prevContext = this.receivingContext
 
     // does the previous calendar need to be cleared?
-    if (prevCalendar && prevCalendar !== nextCalendar) {
+    if (prevContext && prevContext !== nextContext) {
 
       // does the initial calendar need to be cleared?
       // if so, don't clear all the way. we still need to to hide the affectedEvents
-      if (prevCalendar === initialCalendar) {
-        prevCalendar.dispatch({
+      if (prevContext === initialContext) {
+        prevContext.dispatch({
           type: 'SET_EVENT_DRAG',
           state: {
             affectedEvents: state.affectedEvents,
@@ -348,25 +347,25 @@ export class EventDragging extends Interaction { // TODO: rename to EventSelecti
 
       // completely clear the old calendar if it wasn't the initial
       } else {
-        prevCalendar.dispatch({ type: 'UNSET_EVENT_DRAG' })
+        prevContext.dispatch({ type: 'UNSET_EVENT_DRAG' })
       }
     }
 
-    if (nextCalendar) {
-      nextCalendar.dispatch({ type: 'SET_EVENT_DRAG', state })
+    if (nextContext) {
+      nextContext.dispatch({ type: 'SET_EVENT_DRAG', state })
     }
   }
 
   clearDrag() {
-    let initialCalendar = this.component.context.calendar
-    let { receivingCalendar } = this
+    let initialCalendar = this.component.context
+    let { receivingContext } = this
 
-    if (receivingCalendar) {
-      receivingCalendar.dispatch({ type: 'UNSET_EVENT_DRAG' })
+    if (receivingContext) {
+      receivingContext.dispatch({ type: 'UNSET_EVENT_DRAG' })
     }
 
     // the initial calendar might have an dummy drag state from displayDrag
-    if (initialCalendar !== receivingCalendar) {
+    if (initialCalendar !== receivingContext) {
       initialCalendar.dispatch({ type: 'UNSET_EVENT_DRAG' })
     }
   }
@@ -376,7 +375,7 @@ export class EventDragging extends Interaction { // TODO: rename to EventSelecti
     this.isDragging = false
     this.eventRange = null
     this.relevantEvents = null
-    this.receivingCalendar = null
+    this.receivingContext = null
     this.validMutation = null
     this.mutatedRelevantEvents = null
   }

+ 5 - 5
packages/interaction/src/interactions/EventResizing.ts

@@ -89,7 +89,7 @@ export class EventResizing extends Interaction {
   }
 
   handleHitUpdate = (hit: Hit | null, isFinal: boolean, ev: PointerDragEvent) => {
-    let { calendar, pluginHooks } = this.component.context
+    let { context } = this.component
     let relevantEvents = this.relevantEvents!
     let initialHit = this.hitDragging.initialHit!
     let eventInstance = this.eventRange.instance!
@@ -108,12 +108,12 @@ export class EventResizing extends Interaction {
         hit,
         (ev.subjectEl as HTMLElement).classList.contains('fc-event-resizer-start'),
         eventInstance.range,
-        pluginHooks.eventResizeJoinTransforms
+        context.pluginHooks.eventResizeJoinTransforms
       )
     }
 
     if (mutation) {
-      mutatedRelevantEvents = applyMutationToEventStore(relevantEvents, calendar.eventUiBases, mutation, calendar)
+      mutatedRelevantEvents = applyMutationToEventStore(relevantEvents, context.calendar.eventUiBases, mutation, context)
       interaction.mutatedEvents = mutatedRelevantEvents
 
       if (!this.component.isInteractionValid(interaction)) {
@@ -126,12 +126,12 @@ export class EventResizing extends Interaction {
     }
 
     if (mutatedRelevantEvents) {
-      calendar.dispatch({
+      context.dispatch({
         type: 'SET_EVENT_RESIZE',
         state: interaction
       })
     } else {
-      calendar.dispatch({ type: 'UNSET_EVENT_RESIZE' })
+      context.dispatch({ type: 'UNSET_EVENT_RESIZE' })
     }
 
     if (!isInvalid) {

+ 5 - 4
packages/list/src/ListView.tsx

@@ -100,7 +100,7 @@ export class ListView extends DateComponent<ViewProps> {
 
 
   renderSegList(allSegs: Seg[], dayDates: DateMarker[]) {
-    let { theme, eventOrderSpecs } = this.context
+    let { theme, computedOptions } = this.context
     let segsByDay = groupSegsByDay(allSegs) // sparse array
 
     return (
@@ -120,7 +120,7 @@ export class ListView extends DateComponent<ViewProps> {
               />
             )
 
-            daySegs = sortEventSegs(daySegs, eventOrderSpecs)
+            daySegs = sortEventSegs(daySegs, computedOptions.eventOrderSpecs)
 
             for (let seg of daySegs) {
               innerNodes.push(
@@ -153,7 +153,7 @@ export class ListView extends DateComponent<ViewProps> {
         eventStore,
         eventUiBases,
         this.context.dateProfile.activeRange,
-        this.context.nextDayThreshold
+        this.context.computedOptions.nextDayThreshold
       ).fg,
       dayRanges
     )
@@ -172,7 +172,8 @@ export class ListView extends DateComponent<ViewProps> {
 
 
   eventRangeToSegs(eventRange: EventRenderRange, dayRanges: DateRange[]) {
-    let { dateEnv, nextDayThreshold } = this.context
+    let { dateEnv } = this.context
+    let { nextDayThreshold } = this.context.computedOptions
     let range = eventRange.range
     let allDay = eventRange.def.allDay
     let dayIndex

+ 3 - 3
packages/timegrid/src/DayTimeCols.tsx

@@ -53,7 +53,7 @@ export class DayTimeCols extends DateComponent<DayTimeColsProps> {
 
 
   render(props: DayTimeColsProps, state: {}, context: ComponentContext) {
-    let { dateEnv, options, calendar, dateProfile } = context
+    let { dateEnv, options, dateProfile } = context
     let { dayTableModel } = props
     let dayRanges = this.buildDayRanges(dayTableModel, dateProfile, dateEnv)
 
@@ -65,7 +65,7 @@ export class DayTimeCols extends DateComponent<DayTimeColsProps> {
           <TimeCols
             ref={this.timeColsRef}
             rootElRef={this.handleRootEl}
-            {...this.slicer.sliceProps(props, dateProfile, null, context.calendar, dayRanges)}
+            {...this.slicer.sliceProps(props, dateProfile, null, context, dayRanges)}
             axis={props.axis}
             slatMetas={props.slatMetas}
             slotDuration={props.slotDuration}
@@ -76,7 +76,7 @@ export class DayTimeCols extends DateComponent<DayTimeColsProps> {
             clientHeight={props.clientHeight}
             expandRows={props.expandRows}
             nowDate={nowDate}
-            nowIndicatorSegs={options.nowIndicator && this.slicer.sliceNowDate(nowDate, calendar, dayRanges)}
+            nowIndicatorSegs={options.nowIndicator && this.slicer.sliceNowDate(nowDate, context, dayRanges)}
             todayRange={todayRange}
             onScrollTopRequest={props.onScrollTopRequest}
             forPrint={props.forPrint}

+ 5 - 8
packages/timegrid/src/DayTimeColsView.tsx

@@ -7,8 +7,7 @@ import {
   DayTableModel,
   memoize,
   ViewProps,
-  ChunkContentCallbackArgs,
-  createDuration
+  ChunkContentCallbackArgs
 } from '@fullcalendar/core'
 import { DayTable } from '@fullcalendar/daygrid'
 import { TimeColsView } from './TimeColsView'
@@ -19,16 +18,14 @@ import { buildSlatMetas } from './TimeColsSlats'
 export class DayTimeColsView extends TimeColsView {
 
   private buildTimeColsModel = memoize(buildTimeColsModel)
-  private parseSlotDuration = memoize(createDuration)
   private buildSlatMetas = memoize(buildSlatMetas)
 
 
   render(props: ViewProps, state: {}, context: ComponentContext) {
-    let { nextDayThreshold, options, dateEnv, dateProfile, dateProfileGenerator } = context
+    let { options, computedOptions, dateEnv, dateProfile, dateProfileGenerator } = context
     let dayTableModel = this.buildTimeColsModel(dateProfile, dateProfileGenerator)
     let splitProps = this.allDaySplitter.splitProps(props)
-    let slotDuration = this.parseSlotDuration(options.slotDuration)
-    let slatMetas = this.buildSlatMetas(dateProfile.slotMinTime, dateProfile.slotMaxTime, options.slotLabelInterval, slotDuration, dateEnv)
+    let slatMetas = this.buildSlatMetas(dateProfile.slotMinTime, dateProfile.slotMaxTime, options.slotLabelInterval, computedOptions.slotDuration, dateEnv)
     let { dayMinWidth } = options
 
     let headerContent = options.dayHeaders &&
@@ -42,7 +39,7 @@ export class DayTimeColsView extends TimeColsView {
       <DayTable
         {...splitProps['allDay']}
         dayTableModel={dayTableModel}
-        nextDayThreshold={nextDayThreshold}
+        nextDayThreshold={computedOptions.nextDayThreshold}
         tableMinWidth={contentArg.tableMinWidth}
         colGroupNode={contentArg.tableColGroupNode}
         renderRowIntro={dayMinWidth ? null : this.renderTableRowAxis}
@@ -60,7 +57,7 @@ export class DayTimeColsView extends TimeColsView {
         {...splitProps['timed']}
         dayTableModel={dayTableModel}
         axis={!dayMinWidth}
-        slotDuration={slotDuration}
+        slotDuration={computedOptions.slotDuration}
         slatMetas={slatMetas}
         forPrint={props.forPrint}
         tableColGroupNode={contentArg.tableColGroupNode}

+ 1 - 1
packages/timegrid/src/TimeCol.tsx

@@ -102,7 +102,7 @@ export class TimeCol extends BaseComponent<TimeColProps> {
 
     // assigns TO THE SEGS THEMSELVES
     // also, receives resorted array
-    segs = computeSegCoords(segs, props.date, props.slatCoords, context.options.eventMinHeight, context.eventOrderSpecs) as TimeColsSeg[]
+    segs = computeSegCoords(segs, props.date, props.slatCoords, context.options.eventMinHeight, context.computedOptions.eventOrderSpecs) as TimeColsSeg[]
 
     return segs.map((seg) => {
       let instanceId = seg.eventRange.instance.instanceId

+ 4 - 5
packages/timegrid/src/TimeCols.tsx

@@ -1,6 +1,5 @@
 import {
   h, VNode, Ref,
-  createDuration,
   addDurations,
   multiplyDuration,
   wholeDivideDurations,
@@ -150,10 +149,10 @@ export class TimeCols extends BaseComponent<TimeColsProps, TimeColsState> {
 
 
   positionToHit(positionLeft, positionTop) {
-    let { dateProfile, dateEnv, options } = this.context
+    let { dateProfile, dateEnv, computedOptions } = this.context
     let { colCoords } = this
     let { slatCoords } = this.state
-    let { snapDuration, snapsPerSlot } = this.processSlotOptions(this.props.slotDuration, options.snapDuration)
+    let { snapDuration, snapsPerSlot } = this.processSlotOptions(this.props.slotDuration, computedOptions.snapDuration)
 
     let colIndex = colCoords.leftToIndex(positionLeft)
     let slatIndex = slatCoords.positions.topToIndex(positionTop)
@@ -194,8 +193,8 @@ export class TimeCols extends BaseComponent<TimeColsProps, TimeColsState> {
 }
 
 
-function processSlotOptions(slotDuration: Duration, snapDurationInput) {
-  let snapDuration = snapDurationInput ? createDuration(snapDurationInput) : slotDuration
+function processSlotOptions(slotDuration: Duration, snapDurationOverride: Duration | null) {
+  let snapDuration = snapDurationOverride || slotDuration
   let snapsPerSlot = wholeDivideDurations(slotDuration, snapDuration)
 
   if (snapsPerSlot === null) {