Ver Fonte

refactor a bunch of core stuff

Adam Shaw há 6 anos atrás
pai
commit
e5982e8815

+ 78 - 135
packages/core/src/Calendar.ts

@@ -36,7 +36,8 @@ import EventHovering from './interactions/EventHovering'
 import StandardTheme from './theme/StandardTheme'
 import { CmdFormatterFunc } from './datelib/formatting-cmd'
 import { NamedTimeZoneImplClass } from './datelib/timezone'
-import { buildComponentContext } from './component/ComponentContext'
+import { computeContextProps } from './component/ComponentContext'
+import { RenderEngine, TaskQueue } from './view-framework'
 
 export interface DateClickApi extends DatePointApi {
   dayEl: HTMLElement
@@ -72,7 +73,7 @@ export default class Calendar {
   triggerWith: EmitterInterface['triggerWith']
   hasHandlers: EmitterInterface['hasHandlers']
 
-  private buildComponentContext = memoize(buildComponentContext)
+  private computeContextProps = memoize(computeContextProps)
   private parseRawLocales = memoize(parseRawLocales)
   private buildLocale = memoize(buildLocale)
   private buildDateEnv = memoize(buildDateEnv)
@@ -103,16 +104,10 @@ export default class Calendar {
   isHandlingWindowResize: boolean
 
   state: CalendarState
-  actionQueue = []
-  isReducing: boolean = false
-
-  // isDisplaying: boolean = false // installed in DOM? accepting renders?
-  needsRerender: boolean = false // needs a render?
-  isRendering: boolean = false // currently in the executeRender function?
-  renderingPauseDepth: number = 0
+  actionQueue: TaskQueue<Action>
+  renderEngine: RenderEngine
   renderableEventStore: EventStore
-  buildDelayedRerender = memoize(buildDelayedRerender)
-  delayedRerender: any
+
   afterSizingTriggers: any = {}
   isViewUpdated: boolean = false
   isDatesUpdated: boolean = false
@@ -128,6 +123,11 @@ export default class Calendar {
     this.optionsManager = new OptionsManager(overrides || {})
     this.pluginSystem = new PluginSystem()
 
+    this.actionQueue = new TaskQueue({ // no delay. simply so that nested dispatches dont happen
+      runTask: this.runAction.bind(this),
+      drained: this.updateComponent.bind(this)
+    })
+
     // only do once. don't do in handleOptions. because can't remove plugins
     this.addPluginInputs(this.optionsManager.computed.plugins || [])
 
@@ -163,20 +163,20 @@ export default class Calendar {
 
   render() {
     if (!this.component) {
-      this.component = new CalendarComponent(this.el)
+      this.renderEngine = new RenderEngine(this.optionsManager.computed.rerenderDelay)
+      this.component = new CalendarComponent(this.renderEngine)
       this.renderableEventStore = createEmptyEventStore()
-      this.bindHandlers()
-      this.executeRender()
-    } else {
-      this.requestRerender()
+      this.bindHandlers() // TODO: have CalendarComponent handle this?
     }
+
+    this.updateComponent()
   }
 
 
   destroy() {
     if (this.component) {
       this.unbindHandlers()
-      this.component.destroy() // don't null-out. in case API needs access
+      this.component.unmount() // don't null-out. in case API needs access
       this.component = null // umm ???
 
       for (let interaction of this.calendarInteractions) {
@@ -288,127 +288,83 @@ export default class Calendar {
 
   dispatch(action: Action) {
     this.actionQueue.push(action)
+    this.actionQueue.requestRun()
+  }
 
-    if (!this.isReducing) {
-      this.isReducing = true
-      let oldState = this.state
-
-      while (this.actionQueue.length) {
-        this.state = this.reduce(
-          this.state,
-          this.actionQueue.shift(),
-          this
-        )
-      }
 
-      let newState = this.state
-      this.isReducing = false
+  runAction(action: Action) {
+    let oldState = this.state
+    let newState = this.state = reduce(this.state, action, this)
 
-      if (!oldState.loadingLevel && newState.loadingLevel) {
-        this.publiclyTrigger('loading', [ true ])
-      } else if (oldState.loadingLevel && !newState.loadingLevel) {
-        this.publiclyTrigger('loading', [ false ])
-      }
+    if (!oldState.loadingLevel && newState.loadingLevel) {
+      this.publiclyTrigger('loading', [ true ])
+    } else if (oldState.loadingLevel && !newState.loadingLevel) {
+      this.publiclyTrigger('loading', [ false ])
+    }
 
-      let view = this.component && this.component.view
+    let view = this.component && this.component.view
 
-      if (oldState.eventStore !== newState.eventStore) {
-        if (oldState.eventStore) {
-          this.isEventsUpdated = true
-        }
+    if (oldState.eventStore !== newState.eventStore) {
+      if (oldState.eventStore) {
+        this.isEventsUpdated = true
       }
+    }
 
-      if (oldState.dateProfile !== newState.dateProfile) {
-        if (oldState.dateProfile && view) { // why would view be null!?
-          this.publiclyTrigger('datesDestroy', [
-            {
-              view,
-              el: view.el
-            }
-          ])
-        }
-        this.isDatesUpdated = true
+    if (oldState.dateProfile !== newState.dateProfile) {
+      if (oldState.dateProfile && view) { // why would view be null!?
+        this.publiclyTrigger('datesDestroy', [
+          {
+            view,
+            el: view.rootEl
+          }
+        ])
       }
+      this.isDatesUpdated = true
+    }
 
-      if (oldState.viewType !== newState.viewType) {
-        if (oldState.viewType && view) { // why would view be null!?
-          this.publiclyTrigger('viewSkeletonDestroy', [
-            {
-              view,
-              el: view.el
-            }
-          ])
-        }
-        this.isViewUpdated = true
+    if (oldState.viewType !== newState.viewType) {
+      if (oldState.viewType && view) { // why would view be null!?
+        this.publiclyTrigger('viewSkeletonDestroy', [
+          {
+            view,
+            el: view.rootEl
+          }
+        ])
       }
-
-      this.requestRerender()
+      this.isViewUpdated = true
     }
   }
 
 
-  reduce(state: CalendarState, action: Action, calendar: Calendar): CalendarState {
-    return reduce(state, action, calendar)
-  }
-
-
-  // Render Queue
+  // Rendering
   // -----------------------------------------------------------------------------------------------------------------
 
 
-  requestRerender() {
-    this.needsRerender = true
-    this.delayedRerender() // will call a debounced-version of tryRerender
-  }
-
-
-  tryRerender() {
-    if (
-      this.component && // must be accepting renders
-      this.needsRerender && // indicates that a rerender was requested
-      !this.renderingPauseDepth && // not paused
-      !this.isRendering // not currently in the render loop
-    ) {
-      this.executeRender()
-    }
-  }
-
-
   batchRendering(func) {
-    this.renderingPauseDepth++
-    func()
-    this.renderingPauseDepth--
+    let { renderEngine } = this
 
-    if (this.needsRerender) {
-      this.requestRerender()
+    if (renderEngine) {
+      renderEngine.updateQueue.pause()
+      func()
+      renderEngine.updateQueue.resume()
+    } else {
+      func()
     }
   }
 
 
-  // Rendering
-  // -----------------------------------------------------------------------------------------------------------------
-
-  executeRender() {
-    // clear these BEFORE the render so that new values will accumulate during render
-    this.needsRerender = false
-
-    this.isRendering = true
-    this.renderComponent()
-    this.isRendering = false
-
-    // received a rerender request while rendering
-    if (this.needsRerender) {
-      this.delayedRerender()
-    }
-  }
-
   /*
   don't call this directly. use executeRender instead
   */
-  renderComponent() {
+  updateComponent() {
     let { state, component } = this
     let { viewType } = state
     let viewSpec = this.viewSpecs[viewType]
+    let rawOptions = this.optionsManager.computed
+
+    if (!component) {
+      return
+    }
 
     if (!viewSpec) {
       throw new Error(`View type "${viewType}" is not valid`)
@@ -425,7 +381,7 @@ export default class Calendar {
     let eventUiBySource = this.buildEventUiBySource(state.eventSources)
     let eventUiBases = this.eventUiBases = this.buildEventUiBases(renderableEventStore.defs, eventUiSingleBase, eventUiBySource)
 
-    component.receiveProps({
+    component.update(this.el, {
       ...state,
       viewSpec,
       dateProfileGenerator: this.dateProfileGenerators[viewType],
@@ -436,20 +392,21 @@ export default class Calendar {
       eventSelection: state.eventSelection,
       eventDrag: state.eventDrag,
       eventResize: state.eventResize
-    }, this.buildComponentContext(
-      this,
-      this.pluginSystem.hooks,
-      this.theme,
-      this.dateEnv,
-      this.optionsManager.computed
-    ))
+    }, {
+      calendar: this,
+      pluginHooks: this.pluginSystem.hooks,
+      theme: this.theme,
+      dateEnv: this.dateEnv,
+      options: rawOptions,
+      ...this.computeContextProps(rawOptions)
+    })
 
     if (this.isViewUpdated) {
       this.isViewUpdated = false
       this.publiclyTrigger('viewSkeletonRender', [
         {
           view: component.view,
-          el: component.view.el
+          el: component.view.rootEl
         }
       ])
     }
@@ -459,7 +416,7 @@ export default class Calendar {
       this.publiclyTrigger('datesRender', [
         {
           view: component.view,
-          el: component.view.el
+          el: component.view.rootEl
         }
       ])
     }
@@ -467,8 +424,6 @@ export default class Calendar {
     if (this.isEventsUpdated) {
       this.isEventsUpdated = false
     }
-
-    this.releaseAfterSizingTriggers()
   }
 
 
@@ -552,7 +507,7 @@ export default class Calendar {
         }
 
         /* HACK
-        has the same effect as calling this.requestRerender()
+        has the same effect as calling this.updateComponent()
         but recomputes the state's dateProfile
         */
         this.dispatch({
@@ -582,7 +537,6 @@ export default class Calendar {
 
     this.defaultAllDayEventDuration = createDuration(options.defaultAllDayEventDuration)
     this.defaultTimedEventDuration = createDuration(options.defaultTimedEventDuration)
-    this.delayedRerender = this.buildDelayedRerender(options.rerenderDelay)
     this.theme = this.buildTheme(options)
 
     let available = this.parseRawLocales(options.locales)
@@ -895,7 +849,7 @@ export default class Calendar {
 
 
   updateSize() { // public
-    if (this.component) { // when?
+    if (this.component) {
       this.component.updateSize(true)
     }
   }
@@ -1298,17 +1252,6 @@ function buildTheme(this: Calendar, calendarOptions) {
 }
 
 
-function buildDelayedRerender(this: Calendar, wait) {
-  let func = this.tryRerender.bind(this)
-
-  if (wait != null) {
-    func = debounce(func, wait)
-  }
-
-  return func
-}
-
-
 function buildEventUiBySource(eventSources: EventSourceHash): EventUiHash {
   return mapHash(eventSources, function(eventSource) {
     return eventSource.ui

+ 142 - 170
packages/core/src/CalendarComponent.ts

@@ -1,22 +1,21 @@
-import Component from './component/Component'
-import ComponentContext, { extendComponentContext } from './component/ComponentContext'
+import ComponentContext, { computeContextProps } from './component/ComponentContext'
+import { Component, renderer } from './view-framework'
 import { ViewSpec } from './structs/view-spec'
-import View from './View'
+import View, { ViewProps } from './View'
 import Toolbar from './Toolbar'
 import DateProfileGenerator, { DateProfile } from './DateProfileGenerator'
-import { prependToElement, createElement, removeElement, appendToElement, applyStyle } from './util/dom-manip'
+import { createElement, applyStyle } from './util/dom-manip'
 import { rangeContainsMarker, DateRange } from './datelib/date-range'
-import { EventStore } from './structs/event-store'
 import { EventUiHash } from './component/event-ui'
-import { BusinessHoursInput, parseBusinessHours } from './structs/business-hours'
+import { parseBusinessHours } from './structs/business-hours'
 import { memoize } from './util/memoize'
 import { computeHeightAndMargins } from './util/dom-geom'
 import { createFormatter } from './datelib/formatting'
 import { diffWholeDays, DateMarker } from './datelib/marker'
-import { memoizeRendering } from './component/memoized-rendering'
 import { CalendarState } from './reducers/types'
 import { ViewPropsTransformerClass } from './plugin-system'
 import { __assign } from 'tslib'
+import { ViewClass } from './structs/view-config'
 
 
 export interface CalendarComponentProps extends CalendarState {
@@ -30,200 +29,155 @@ export default class CalendarComponent extends Component<CalendarComponentProps>
   view: View
   header: Toolbar
   footer: Toolbar
-
-  computeTitle: (dateProfile, viewOptions) => string
-  parseBusinessHours: (input: BusinessHoursInput) => EventStore
-
-  el: HTMLElement
-  contentEl: HTMLElement
-
-  elClassNames: string[] = []
-  savedScroll: any // hack
+  viewContainerEl: HTMLElement
   isHeightAuto: boolean
   viewHeight: number
 
-  private renderSkeleton = memoizeRendering(this._renderSkeleton, this._unrenderSkeleton)
-  private renderToolbars = memoizeRendering(this._renderToolbars, this._unrenderToolbars, [ this.renderSkeleton ])
-  private buildComponentContext = memoize(buildComponentContext)
+  private computeTitle = memoize(computeTitle)
+  private parseBusinessHours = memoize((input) => {
+    return parseBusinessHours(input, this.context.calendar)
+  })
+  private buildViewComponent = renderer(this._buildViewComponent, this._clearViewComponent)
+  private computeViewContextProps = memoize(computeContextProps)
   private buildViewPropTransformers = memoize(buildViewPropTransformers)
+  private updateClassNames = renderer(this._setClassNames, this._unsetClassNames)
+  private renderViewContainer = renderer(this._renderViewContainer)
+  private buildToolbarProps = memoize(this._buildToolbarProps)
+  private renderHeader = renderer(Toolbar)
+  private renderFooter = renderer(Toolbar)
 
 
-  constructor(el: HTMLElement) {
-    super()
-
-    this.el = el
-    this.computeTitle = memoize(computeTitle)
-
-    this.parseBusinessHours = memoize((input) => {
-      return parseBusinessHours(input, this.context.calendar)
-    })
-  }
-
+  /*
+  renders INSIDE of an outer div
+  */
   render(props: CalendarComponentProps, context: ComponentContext) {
-    this.freezeHeight()
-
     let title = this.computeTitle(props.dateProfile, props.viewSpec.options)
+    let toolbarProps = this.buildToolbarProps(
+      props.viewSpec,
+      props.dateProfile,
+      props.dateProfileGenerator,
+      props.currentDate,
+      context.calendar.getNow(),
+      title
+    )
+    let innerEls: HTMLElement[] = []
 
-    this.renderSkeleton(context)
-    this.renderToolbars(props.viewSpec, props.dateProfile, props.currentDate, title)
-    this.renderView(props, title)
-
-    this.updateSize()
-    this.thawHeight()
-  }
+    this.freezeHeight() // thawed after render
+    this.updateClassNames(true)
 
-  destroy() {
-    if (this.header) {
-      this.header.destroy()
+    if (context.options.header) {
+      let header = this.renderHeader(true, {
+        extraClassName: 'fc-header-toolbar',
+        layout: context.options.header,
+        ...toolbarProps
+      })
+      innerEls.push(header.rootEl)
     }
 
-    if (this.footer) {
-      this.footer.destroy()
+    let viewContainerEl = this.renderViewContainer(true)
+    this.renderView(props, title, viewContainerEl, context)
+    innerEls.push(viewContainerEl)
+
+    if (context.options.footer) {
+      let footer = this.renderFooter(true, {
+        extraClassName: 'fc-footer-toolbar',
+        layout: context.options.footer,
+        ...toolbarProps
+      })
+      innerEls.push(footer.rootEl)
     }
 
-    this.renderSkeleton.unrender() // will call destroyView
+    this.viewContainerEl = viewContainerEl
+    return innerEls
+  }
+
 
-    super.destroy()
+  componentDidMount() {
+    this.afterRender()
   }
 
-  _renderSkeleton(context: ComponentContext) {
-    this.updateElClassNames(context)
 
-    prependToElement(
-      this.el,
-      this.contentEl = createElement('div', { className: 'fc-view-container' })
-    )
+  componentDidUpdate() {
+    this.afterRender()
+  }
 
-    let { calendar, pluginHooks } = context
 
-    for (let modifyViewContainer of pluginHooks.viewContainerModifiers) {
-      modifyViewContainer(this.contentEl, calendar)
-    }
+  afterRender() {
+    this.thawHeight()
+    this.updateSize()
+    this.context.calendar.releaseAfterSizingTriggers()
   }
 
-  _unrenderSkeleton() {
 
-    // weird to have this here
-    if (this.view) {
-      this.savedScroll = this.view.queryScroll()
-      this.view.destroy()
-      this.view = null
+  _setClassNames(props: {}, context: ComponentContext) {
+    let classList = this.location.parent.classList
+    let classNames: string[] = [
+      'fc',
+      'fc-' + context.options.dir,
+      context.theme.getClass('widget')
+    ]
+
+    for (let className of classNames) {
+      classList.add(className)
     }
 
-    removeElement(this.contentEl)
-    this.removeElClassNames()
+    return classNames
   }
 
-  removeElClassNames() {
-    let classList = this.el.classList
 
-    for (let className of this.elClassNames) {
+  _unsetClassNames(classNames: string[]) {
+    let classList = this.location.parent.classList
+
+    for (let className of classNames) {
       classList.remove(className)
     }
-
-    this.elClassNames = []
   }
 
-  updateElClassNames(context: ComponentContext) {
-    this.removeElClassNames()
-
-    let { theme, options } = context
-    this.elClassNames = [
-      'fc',
-      'fc-' + options.dir,
-      theme.getClass('widget')
-    ]
 
-    let classList = this.el.classList
+  _renderViewContainer(props: {}, context: ComponentContext) {
+    let viewContainerEl = createElement('div', { className: 'fc-view-container' })
 
-    for (let className of this.elClassNames) {
-      classList.add(className)
+    for (let modifyViewContainer of context.pluginHooks.viewContainerModifiers) {
+      modifyViewContainer(viewContainerEl, context.calendar)
     }
+
+    return viewContainerEl
   }
 
-  _renderToolbars(viewSpec: ViewSpec, dateProfile: DateProfile, currentDate: DateMarker, title: string) {
-    let { context, header, footer } = this
-    let { options, calendar } = context
-    let headerLayout = options.header
-    let footerLayout = options.footer
-    let { dateProfileGenerator } = this.props
 
-    let now = calendar.getNow()
+  _buildToolbarProps(
+    viewSpec: ViewSpec,
+    dateProfile: DateProfile,
+    dateProfileGenerator: DateProfileGenerator,
+    currentDate: DateMarker,
+    now: DateMarker,
+    title: string
+  ) {
     let todayInfo = dateProfileGenerator.build(now)
     let prevInfo = dateProfileGenerator.buildPrev(dateProfile, currentDate)
     let nextInfo = dateProfileGenerator.buildNext(dateProfile, currentDate)
 
-    let toolbarProps = {
+    return {
       title,
       activeButton: viewSpec.type,
       isTodayEnabled: todayInfo.isValid && !rangeContainsMarker(dateProfile.currentRange, now),
       isPrevEnabled: prevInfo.isValid,
       isNextEnabled: nextInfo.isValid
     }
-
-    if (headerLayout) {
-      if (!header) {
-        header = this.header = new Toolbar('fc-header-toolbar')
-        prependToElement(this.el, header.el)
-      }
-      header.receiveProps({
-        layout: headerLayout,
-        ...toolbarProps
-      }, context)
-    } else if (header) {
-      header.destroy()
-      header = this.header = null
-    }
-
-    if (footerLayout) {
-      if (!footer) {
-        footer = this.footer = new Toolbar('fc-footer-toolbar')
-        appendToElement(this.el, footer.el)
-      }
-      footer.receiveProps({
-        layout: footerLayout,
-        ...toolbarProps
-      }, context)
-    } else if (footer) {
-      footer.destroy()
-      footer = this.footer = null
-    }
-  }
-
-  _unrenderToolbars() {
-    if (this.header) {
-      this.header.destroy()
-      this.header = null
-    }
-    if (this.footer) {
-      this.footer.destroy()
-      this.footer = null
-    }
   }
 
-  renderView(props: CalendarComponentProps, title: string) {
-    let { view } = this
-    let { pluginHooks, options } = this.context
-    let { viewSpec, dateProfileGenerator } = props
-
-    if (!view || view.viewSpec !== viewSpec) {
-
-      if (view) {
-        view.destroy()
-      }
-
-      view = this.view = new viewSpec['class'](viewSpec, this.contentEl)
 
-      if (this.savedScroll) {
-        view.addScroll(this.savedScroll, true)
-        this.savedScroll = null
-      }
-    }
+  renderView(props: CalendarComponentProps, title: string, viewContainerEl: HTMLElement, context: ComponentContext) {
+    let { pluginHooks, options } = context
+    let { viewSpec } = props
 
-    view.title = title // for the API
+    let view = this.view = this.buildViewComponent(true, { viewClass: viewSpec.class })
+    view.type = viewSpec.type
+    view.title = title
 
-    let viewProps = {
-      dateProfileGenerator,
+    let viewProps: ViewProps = {
+      viewSpec,
+      dateProfileGenerator: props.dateProfileGenerator,
       dateProfile: props.dateProfile,
       businessHours: this.parseBusinessHours(viewSpec.options.businessHours),
       eventStore: props.eventStore,
@@ -243,29 +197,44 @@ export default class CalendarComponent extends Component<CalendarComponentProps>
       )
     }
 
-    view.receiveProps(viewProps, this.buildComponentContext(this.context, viewSpec, view))
+    view.update(
+      viewContainerEl,
+      viewProps,
+      {
+        ...context,
+        view,
+        options: viewSpec.options,
+        ...this.computeViewContextProps(viewSpec.options)
+      }
+    )
+  }
+
+
+  _buildViewComponent(props: { viewClass: ViewClass }) {
+    return new props.viewClass(this.renderEngine)
+  }
+
+
+  _clearViewComponent(view: View) {
+    view.unmount()
   }
 
 
   // Sizing
   // -----------------------------------------------------------------------------------------------------------------
 
-  updateSize(isResize = false) {
-    let { view } = this
 
-    if (!view) {
-      return // why?
-    }
+  updateSize(isResize = false) {
 
     if (isResize || this.isHeightAuto == null) {
       this.computeHeightVars()
     }
 
-    view.updateSize(isResize, this.viewHeight, this.isHeightAuto)
-    view.updateNowIndicator() // we need to guarantee this will run after updateSize
-    view.popScroll(isResize)
+    this.view.updateSize(isResize, this.viewHeight, this.isHeightAuto)
+    this.view.updateNowIndicator() // we need to guarantee this will run after updateSize
   }
 
+
   computeHeightVars() {
     let { calendar } = this.context // yuck. need to handle dynamic options
     let heightInput = calendar.opt('height')
@@ -282,25 +251,26 @@ export default class CalendarComponent extends Component<CalendarComponentProps>
     } else if (typeof heightInput === 'function') { // exists and is a function
       this.viewHeight = heightInput() - this.queryToolbarsHeight()
     } else if (heightInput === 'parent') { // set to height of parent element
-      let parentEl = this.el.parentNode as HTMLElement
+      let parentEl = this.location.parent.parentNode as HTMLElement
       this.viewHeight = parentEl.getBoundingClientRect().height - this.queryToolbarsHeight()
     } else {
       this.viewHeight = Math.round(
-        this.contentEl.getBoundingClientRect().width /
+        this.viewContainerEl.getBoundingClientRect().width /
         Math.max(calendar.opt('aspectRatio'), .5)
       )
     }
   }
 
+
   queryToolbarsHeight() {
     let height = 0
 
     if (this.header) {
-      height += computeHeightAndMargins(this.header.el)
+      height += computeHeightAndMargins(this.header.rootEl)
     }
 
     if (this.footer) {
-      height += computeHeightAndMargins(this.footer.el)
+      height += computeHeightAndMargins(this.footer.rootEl)
     }
 
     return height
@@ -310,15 +280,21 @@ export default class CalendarComponent extends Component<CalendarComponentProps>
   // Height "Freezing"
   // -----------------------------------------------------------------------------------------------------------------
 
+
   freezeHeight() {
-    applyStyle(this.el, {
-      height: this.el.getBoundingClientRect().height,
+    let rootEl = this.location.parent
+
+    applyStyle(rootEl, {
+      height: rootEl.getBoundingClientRect().height,
       overflow: 'hidden'
     })
   }
 
+
   thawHeight() {
-    applyStyle(this.el, {
+    let rootEl = this.location.parent
+
+    applyStyle(rootEl, {
       height: '',
       overflow: ''
     })
@@ -330,6 +306,7 @@ export default class CalendarComponent extends Component<CalendarComponentProps>
 // Title and Date Formatting
 // -----------------------------------------------------------------------------------------------------------------
 
+
 // Computes what the title at the top of the calendar should be for this view
 function computeTitle(this: CalendarComponent, dateProfile, viewOptions) {
   let range: DateRange
@@ -378,15 +355,10 @@ function computeTitleFormat(dateProfile) {
 }
 
 
-// build a context scoped to the view
-function buildComponentContext(context: ComponentContext, viewSpec: ViewSpec, view: View) {
-  return extendComponentContext(context, viewSpec.options, view)
-}
-
-
 // Plugin
 // -----------------------------------------------------------------------------------------------------------------
 
+
 function buildViewPropTransformers(theClasses: ViewPropsTransformerClass[]) {
   return theClasses.map(function(theClass) {
     return new theClass()

+ 8 - 6
packages/core/src/Toolbar.ts

@@ -34,16 +34,16 @@ export default class Toolbar extends Component<ToolbarRenderProps> {
 
   render(props: ToolbarRenderProps) {
 
-    let el = this.renderBase({
+    let el = this.renderBase(true, {
       extraClassName: props.extraClassName,
       layout: props.layout
     })
 
-    this.renderTitle({ el, text: props.title })
-    this.renderActiveButton({ el, buttonName: props.activeButton })
-    this.renderToday({ el, isEnabled: props.isTodayEnabled })
-    this.renderPrev({ el, isEnabled: props.isPrevEnabled })
-    this.renderNext({ el, isEnabled: props.isNextEnabled })
+    this.renderTitle(true, { el, text: props.title })
+    this.renderActiveButton(true, { el, buttonName: props.activeButton })
+    this.renderToday(true, { el, isEnabled: props.isTodayEnabled })
+    this.renderPrev(true, { el, isEnabled: props.isPrevEnabled })
+    this.renderNext(true, { el, isEnabled: props.isNextEnabled })
 
     return el
   }
@@ -190,6 +190,8 @@ function renderActiveButton(props: { el: HTMLElement, buttonName: string }, cont
       buttonEl.classList.add(className)
     }
   })
+
+  return props
 }
 
 

+ 5 - 22
packages/core/src/View.ts

@@ -89,33 +89,16 @@ export default abstract class View extends DateComponent<ViewProps> {
   }
 
 
-  // called from CalendarComponent
   updateSize(isResize: boolean, viewHeight: number, isAuto: boolean) {
-    let { calendar } = this.context
-    let scrollState
+  }
 
-    if (isResize) { // if NOT a resize, scroll state will be maintained via getSnapshotBeforeUpdate/componentDidUpdate
-      scrollState = this.queryScroll()
-    }
 
-    if (
-      isResize || // HACKS...
-      calendar.isViewUpdated ||
+  isLayoutSizeDirty() {
+    let { calendar } = this.context
+
+    return calendar.isViewUpdated ||
       calendar.isDatesUpdated ||
       calendar.isEventsUpdated
-    ) {
-      // sort of the catch-all sizing
-      // anything that might cause dimension changes
-      this.updateBaseSize(isResize, viewHeight, isAuto)
-    }
-
-    if (isResize) {
-      this.applyScroll(scrollState, true)
-    }
-  }
-
-
-  updateBaseSize(isResize: boolean, viewHeight: number, isAuto: boolean) {
   }
 
 

+ 14 - 52
packages/core/src/common/DayHeader.ts

@@ -1,41 +1,26 @@
-import Component from '../component/Component'
+import { Component } from '../view-framework'
 import ComponentContext from '../component/ComponentContext'
-import { htmlToElement, removeElement } from '../util/dom-manip'
+import { htmlToElement } from '../util/dom-manip'
 import { DateMarker } from '../datelib/marker'
 import { DateProfile } from '../DateProfileGenerator'
 import { createFormatter } from '../datelib/formatting'
 import { computeFallbackHeaderFormat, renderDateCell } from './table-utils'
-import { memoizeRendering } from '../component/memoized-rendering'
 
-export interface DayTableHeaderProps {
+export interface DayHeaderProps {
   dates: DateMarker[]
   dateProfile: DateProfile
   datesRepDistinctDays: boolean
   renderIntroHtml?: () => string
 }
 
-export default class DayHeader extends Component<DayTableHeaderProps> {
+export default class DayHeader extends Component<DayHeaderProps> {
 
-  parentEl: HTMLElement
-  el: HTMLElement
-  thead: HTMLElement
 
-  private renderSkeleton = memoizeRendering(this._renderSkeleton, this._unrenderSkeleton)
-
-
-  constructor(parentEl: HTMLElement) {
-    super()
-
-    this.parentEl = parentEl
-  }
-
-
-  render(props: DayTableHeaderProps, context: ComponentContext) {
+  render(props: DayHeaderProps, context: ComponentContext) {
+    let { theme } = context
     let { dates, datesRepDistinctDays } = props
     let parts = []
 
-    this.renderSkeleton(context)
-
     if (props.renderIntroHtml) {
       parts.push(props.renderIntroHtml())
     }
@@ -62,38 +47,15 @@ export default class DayHeader extends Component<DayTableHeaderProps> {
       parts.reverse()
     }
 
-    this.thead.innerHTML = '<tr>' + parts.join('') + '</tr>'
-  }
-
-
-  destroy() {
-    super.destroy()
-
-    this.renderSkeleton.unrender()
-  }
-
-
-  _renderSkeleton(context: ComponentContext) {
-    let { theme } = context
-    let { parentEl } = this
-
-    parentEl.innerHTML = '' // because might be nbsp
-    parentEl.appendChild(
-      this.el = htmlToElement(
-        '<div class="fc-row ' + theme.getClass('headerRow') + '">' +
-          '<table class="' + theme.getClass('tableGrid') + '">' +
-            '<thead></thead>' +
-          '</table>' +
-        '</div>'
-      )
+    return htmlToElement(
+      '<div class="fc-row ' + theme.getClass('headerRow') + '">' +
+        '<table class="' + theme.getClass('tableGrid') + '">' +
+          '<thead>' +
+            '<tr>' + parts.join('') + '</tr>' +
+          '</thead>' +
+        '</table>' +
+      '</div>'
     )
-
-    this.thead = this.el.querySelector('thead')
-  }
-
-
-  _unrenderSkeleton() {
-    removeElement(this.el)
   }
 
 }

+ 14 - 23
packages/core/src/common/slicing-utils.ts

@@ -3,7 +3,7 @@ import { EventStore, expandRecurring } from '../structs/event-store'
 import { EventUiHash } from '../component/event-ui'
 import { sliceEventStore, EventRenderRange } from '../component/event-rendering'
 import { DateProfile } from '../DateProfileGenerator'
-import DateComponent, { Seg, EventSegUiInteractionState } from '../component/DateComponent' // TODO: rename EventSegUiInteractionState, move here
+import { Seg, EventSegUiInteractionState } from '../component/DateComponent' // TODO: rename EventSegUiInteractionState, move here
 import { DateSpan, fabricateEventRange } from '../structs/date-span'
 import { EventInteractionState } from '../interactions/event-interaction-state'
 import { Duration } from '../datelib/duration'
@@ -46,32 +46,31 @@ export default abstract class Slicer<SegType extends Seg, ExtraArgs extends any[
     dateProfile: DateProfile,
     nextDayThreshold: Duration | null,
     calendar: Calendar,
-    component: DateComponent<any>, // TODO: kill
     ...extraArgs: ExtraArgs
   ): SlicedProps<SegType> {
     let { eventUiBases } = props
-    let eventSegs = this.sliceEventStore(props.eventStore, eventUiBases, dateProfile, nextDayThreshold, component, ...extraArgs)
+    let eventSegs = this.sliceEventStore(props.eventStore, eventUiBases, dateProfile, nextDayThreshold, ...extraArgs)
 
     return {
-      dateSelectionSegs: this.sliceDateSelection(props.dateSelection, eventUiBases, component, ...extraArgs),
-      businessHourSegs: this.sliceBusinessHours(props.businessHours, dateProfile, nextDayThreshold, calendar, component, ...extraArgs),
+      dateSelectionSegs: this.sliceDateSelection(props.dateSelection, eventUiBases, calendar, ...extraArgs),
+      businessHourSegs: this.sliceBusinessHours(props.businessHours, dateProfile, nextDayThreshold, calendar, ...extraArgs),
       fgEventSegs: eventSegs.fg,
       bgEventSegs: eventSegs.bg,
-      eventDrag: this.sliceEventDrag(props.eventDrag, eventUiBases, dateProfile, nextDayThreshold, component, ...extraArgs),
-      eventResize: this.sliceEventResize(props.eventResize, eventUiBases, dateProfile, nextDayThreshold, component, ...extraArgs),
+      eventDrag: this.sliceEventDrag(props.eventDrag, eventUiBases, dateProfile, nextDayThreshold, ...extraArgs),
+      eventResize: this.sliceEventResize(props.eventResize, eventUiBases, dateProfile, nextDayThreshold, ...extraArgs),
       eventSelection: props.eventSelection
     } // TODO: give interactionSegs?
   }
 
   sliceNowDate( // does not memoize
     date: DateMarker,
-    component: DateComponent<any>, // TODO: kill
+    calendar: Calendar,
     ...extraArgs: ExtraArgs
   ): SegType[] {
     return this._sliceDateSpan(
       { range: { start: date, end: addMs(date, 1) }, allDay: false }, // add 1 ms, protect against null range
       {},
-      component,
+      calendar,
       ...extraArgs
     )
   }
@@ -81,7 +80,6 @@ export default abstract class Slicer<SegType extends Seg, ExtraArgs extends any[
     dateProfile: DateProfile,
     nextDayThreshold: Duration | null,
     calendar: Calendar,
-    component: DateComponent<any>, // TODO: kill
     ...extraArgs: ExtraArgs
   ): SegType[] {
     if (!businessHours) {
@@ -97,7 +95,6 @@ export default abstract class Slicer<SegType extends Seg, ExtraArgs extends any[
       {},
       dateProfile,
       nextDayThreshold,
-      component,
       ...extraArgs
     ).bg
   }
@@ -107,7 +104,6 @@ export default abstract class Slicer<SegType extends Seg, ExtraArgs extends any[
     eventUiBases: EventUiHash,
     dateProfile: DateProfile,
     nextDayThreshold: Duration | null,
-    component: DateComponent<any>, // TODO: kill
     ...extraArgs: ExtraArgs
   ): { bg: SegType[], fg: SegType[] } {
     if (eventStore) {
@@ -119,8 +115,8 @@ export default abstract class Slicer<SegType extends Seg, ExtraArgs extends any[
       )
 
       return {
-        bg: this.sliceEventRanges(rangeRes.bg, component, extraArgs),
-        fg: this.sliceEventRanges(rangeRes.fg, component, extraArgs)
+        bg: this.sliceEventRanges(rangeRes.bg, extraArgs),
+        fg: this.sliceEventRanges(rangeRes.fg, extraArgs)
       }
 
     } else {
@@ -133,7 +129,6 @@ export default abstract class Slicer<SegType extends Seg, ExtraArgs extends any[
     eventUiBases: EventUiHash,
     dateProfile: DateProfile,
     nextDayThreshold: Duration | null,
-    component: DateComponent<any>, // TODO: kill
     ...extraArgs: ExtraArgs
   ): EventSegUiInteractionState {
     if (!interaction) {
@@ -148,7 +143,7 @@ export default abstract class Slicer<SegType extends Seg, ExtraArgs extends any[
     )
 
     return {
-      segs: this.sliceEventRanges(rangeRes.fg, component, extraArgs),
+      segs: this.sliceEventRanges(rangeRes.fg, extraArgs),
       affectedInstances: interaction.affectedEvents.instances,
       isEvent: interaction.isEvent,
       sourceSeg: interaction.origSeg
@@ -158,18 +153,17 @@ export default abstract class Slicer<SegType extends Seg, ExtraArgs extends any[
   private _sliceDateSpan(
     dateSpan: DateSpan,
     eventUiBases: EventUiHash,
-    component: DateComponent<any>, // TODO: kill
+    calendar: Calendar,
     ...extraArgs: ExtraArgs
   ): SegType[] {
     if (!dateSpan) {
       return []
     }
 
-    let eventRange = fabricateEventRange(dateSpan, eventUiBases, component.context.calendar)
+    let eventRange = fabricateEventRange(dateSpan, eventUiBases, calendar)
     let segs = this.sliceRange(dateSpan.range, ...extraArgs)
 
     for (let seg of segs) {
-      seg.component = component
       seg.eventRange = eventRange
     }
 
@@ -181,13 +175,12 @@ export default abstract class Slicer<SegType extends Seg, ExtraArgs extends any[
   */
   private sliceEventRanges(
     eventRanges: EventRenderRange[],
-    component: DateComponent<any>, // TODO: kill
     extraArgs: ExtraArgs
   ): SegType[] {
     let segs: SegType[] = []
 
     for (let eventRange of eventRanges) {
-      segs.push(...this.sliceEventRange(eventRange, component, extraArgs))
+      segs.push(...this.sliceEventRange(eventRange, extraArgs))
     }
 
     return segs
@@ -198,13 +191,11 @@ export default abstract class Slicer<SegType extends Seg, ExtraArgs extends any[
   */
   private sliceEventRange(
     eventRange: EventRenderRange,
-    component: DateComponent<any>, // TODO: kill
     extraArgs: ExtraArgs
   ): SegType[] {
     let segs = this.sliceRange(eventRange.range, ...extraArgs)
 
     for (let seg of segs) {
-      seg.component = component
       seg.eventRange = eventRange
       seg.isStart = eventRange.isStart && seg.isStart
       seg.isEnd = eventRange.isEnd && seg.isEnd

+ 0 - 105
packages/core/src/component/Component.ts

@@ -1,105 +0,0 @@
-import ComponentContext from './ComponentContext'
-
-let guid = 0
-
-
-export type EqualityFuncHash = { [propName: string]: (obj0, obj1) => boolean }
-
-export default class Component<PropsType> {
-
-  equalityFuncs: EqualityFuncHash // can't initialize here. done below...
-
-  uid: string
-  props: PropsType | null
-  context: ComponentContext
-
-  constructor() {
-    this.uid = String(guid++)
-  }
-
-  static addEqualityFuncs(newFuncs: EqualityFuncHash) {
-    this.prototype.equalityFuncs = {
-      ...this.prototype.equalityFuncs,
-      ...newFuncs
-    }
-  }
-
-  receiveProps(props: PropsType, context: ComponentContext) {
-    let oldContext = this.context
-    this.context = context
-
-    if (!oldContext) {
-      this.firstContext(context)
-    }
-
-    let { anyChanges, comboProps } = recycleProps(this.props || {}, props, this.equalityFuncs)
-
-    this.props = comboProps
-
-    if (anyChanges) {
-
-      if (oldContext) {
-        this.beforeUpdate()
-      }
-
-      this.render(comboProps, context)
-
-      if (oldContext) {
-        this.afterUpdate()
-      }
-    }
-  }
-
-  protected render(props: PropsType, context: ComponentContext) {
-  }
-
-  firstContext(context: ComponentContext) {
-  }
-
-  beforeUpdate() {
-  }
-
-  afterUpdate() {
-  }
-
-  // after destroy is called, this component won't ever be used again
-  destroy() {
-  }
-
-}
-
-Component.prototype.equalityFuncs = {}
-
-
-/*
-Reuses old values when equal. If anything is unequal, returns newProps as-is.
-Great for PureComponent, but won't be feasible with React, so just eliminate and use React's DOM diffing.
-*/
-function recycleProps(oldProps, newProps, equalityFuncs: EqualityFuncHash) {
-  let comboProps = {} as any // some old, some new
-  let anyChanges = false
-
-  for (let key in newProps) {
-    if (
-      key in oldProps && (
-        oldProps[key] === newProps[key] ||
-        (equalityFuncs[key] && equalityFuncs[key](oldProps[key], newProps[key]))
-      )
-    ) {
-      // equal to old? use old prop
-      comboProps[key] = oldProps[key]
-    } else {
-      comboProps[key] = newProps[key]
-      anyChanges = true
-    }
-  }
-
-  for (let key in oldProps) {
-    if (!(key in newProps)) {
-      anyChanges = true
-      break
-    }
-  }
-
-  return { anyChanges, comboProps }
-}

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

@@ -33,7 +33,7 @@ PURPOSES:
 - hook up to fg, fill, and mirror renderers
 - interface for dragging and hits
 */
-export default abstract class DateComponent<PropsType> extends Component<PropsType> {
+export default abstract class DateComponent<PropsType, StateType = {}> extends Component<PropsType, StateType> {
 
   // self-config, overridable by subclasses. must set on prototype
   fgSegSelector: string // lets eventRender produce elements without fc-event class
@@ -117,7 +117,7 @@ export default abstract class DateComponent<PropsType> extends Component<PropsTy
 
 
   isPopover() {
-    return this.mountedEls[0].classList.contains('fc-popover')
+    return this.rootEl.classList.contains('fc-popover')
   }
 
 

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

@@ -191,7 +191,7 @@ export function compileEventUi(eventDef: EventDef, eventUiBases: EventUiHash) {
 
 // triggers
 
-export function triggerRenderedSegs(context: ComponentContext, segs: Seg[], isMirrors: boolean) {
+export function triggerPositionedSegs(context: ComponentContext, segs: Seg[], isMirrors: boolean) {
   let { calendar, view } = context
 
   if (calendar.hasPublicHandlers('eventPositioned')) {

+ 26 - 13
packages/core/src/component/renderers/FgEventRenderer.ts

@@ -3,7 +3,7 @@ import { createFormatter, DateFormatter } from '../../datelib/formatting'
 import { htmlToElements } from '../../util/dom-manip'
 import { compareByFieldSpecs } from '../../util/misc'
 import { EventUi } from '../event-ui'
-import { EventRenderRange, filterSegsViaEls, triggerRenderedSegs, triggerWillRemoveSegs } from '../event-rendering'
+import { EventRenderRange, filterSegsViaEls, triggerPositionedSegs, triggerWillRemoveSegs } from '../event-rendering'
 import { Seg } from '../DateComponent'
 import { Component } from '../../view-framework'
 import ComponentContext from '../ComponentContext'
@@ -40,21 +40,23 @@ export default abstract class FgEventRenderer<
   renderSegs(props: BaseFgEventRendererProps, context: ComponentContext) {
     this.updateComputedOptions(context.options)
 
-    let segs = this.segs = this.renderSegsPlain({
+    let { segs } = this.renderSegsPlain(true, {
       segs: props.segs,
       mirrorInfo: props.mirrorInfo
     })
 
-    this.renderSelectedInstance({
+    this.renderSelectedInstance(true, {
       segs,
       instanceId: props.selectedInstanceId
     })
 
-    this.renderHiddenInstances({
+    this.renderHiddenInstances(true, {
       segs,
       hiddenInstances: props.hiddenInstances
     })
 
+    this.segs = segs
+
     return segs
   }
 
@@ -83,15 +85,17 @@ export default abstract class FgEventRenderer<
 
   // doesn't worry about selection/hidden state
   _renderSegsPlain({ segs, mirrorInfo } : { segs: Seg[], mirrorInfo: any }, context: ComponentContext) {
-    segs = this.renderSegEls(segs, mirrorInfo)
-    this.isSizeDirty = true
-    triggerRenderedSegs(context, segs, Boolean(mirrorInfo)) // will use publiclyTriggerAfterSizing, will fire later
-    return segs
+    let isMirror = Boolean(mirrorInfo)
+    segs = this.renderSegEls(segs, mirrorInfo) // returns a subset!
+
+    triggerPositionedSegs(context, segs, false) // isMirror=false
+
+    return { segs, isMirror }
   }
 
 
-  _unrenderSegsPlain({ mirrorInfo }: { segs: Seg[], mirrorInfo: any }, context: ComponentContext, segs: Seg[]) {
-    triggerWillRemoveSegs(context, segs, Boolean(mirrorInfo))
+  _unrenderSegsPlain({ segs, isMirror }: { segs: Seg[], isMirror: boolean }, context: ComponentContext) {
+    triggerWillRemoveSegs(context, segs, isMirror)
   }
 
 
@@ -261,8 +265,9 @@ export default abstract class FgEventRenderer<
   assignSizes(force: boolean, userComponent: any) {
     if (force || this.isSizeDirty) {
       this.assignSegSizes(this.segs, userComponent)
-      this.isSizeDirty = false
     }
+
+    this.isSizeDirty = false
   }
 
 
@@ -281,7 +286,9 @@ export default abstract class FgEventRenderer<
 // TODO: slow. use more hashes to quickly reference relevant elements
 
 
-function renderHiddenInstances({ segs, hiddenInstances }: { segs: Seg[], hiddenInstances: { [instanceId: string]: any } }) {
+function renderHiddenInstances(props: { segs: Seg[], hiddenInstances: { [instanceId: string]: any } }) {
+  let { segs, hiddenInstances } = props
+
   if (hiddenInstances) {
     for (let seg of segs) {
       if (hiddenInstances[seg.eventRange.instance.instanceId]) {
@@ -289,6 +296,8 @@ function renderHiddenInstances({ segs, hiddenInstances }: { segs: Seg[], hiddenI
       }
     }
   }
+
+  return props
 }
 
 
@@ -303,7 +312,9 @@ function unrenderHiddenInstances({ segs, hiddenInstances }: { segs: Seg[], hidde
 }
 
 
-function renderSelectedInstance({ segs, instanceId }: { segs: Seg[], instanceId: string }) {
+function renderSelectedInstance(props: { segs: Seg[], instanceId: string }) {
+  let { segs, instanceId } = props
+
   if (instanceId) {
     for (let seg of segs) {
       let eventInstance = seg.eventRange.instance
@@ -315,6 +326,8 @@ function renderSelectedInstance({ segs, instanceId }: { segs: Seg[], instanceId:
       }
     }
   }
+
+  return props
 }
 
 

+ 15 - 10
packages/core/src/component/renderers/FillRenderer.ts

@@ -1,7 +1,7 @@
 import { cssToStr } from '../../util/html'
 import { htmlToElements, elementMatches } from '../../util/dom-manip'
 import { Seg } from '../DateComponent'
-import { filterSegsViaEls, triggerRenderedSegs, triggerWillRemoveSegs } from '../event-rendering'
+import { filterSegsViaEls, triggerPositionedSegs, triggerWillRemoveSegs } from '../event-rendering'
 import ComponentContext from '../ComponentContext'
 import { Component, renderer} from '../../view-framework'
 
@@ -19,25 +19,29 @@ export default abstract class FillRenderer<FillRendererProps extends BaseFillRen
 
   // for sizing
   private segs: Seg[]
-  private isSizeDirty = false
+  private isSizeDirty: boolean = false // NOTE: should also flick this when attaching segs to new containers
 
 
-  _renderSegs(props: BaseFillRendererProps, context: ComponentContext) {
-    let segs = this.segs = this.renderSegEls(props.segs, props.type) // assignes `.el` to each seg. returns successfully rendered segs
+  _renderSegs({ segs, type }: BaseFillRendererProps, context: ComponentContext) {
 
-    if (props.type === 'bgEvent') {
-      triggerRenderedSegs(context, segs, false) // isMirror=false
+    // assignes `.el` to each seg. returns successfully rendered segs, a SUBSET
+    segs = this.renderSegEls(segs, type)
+
+    if (type === 'bgEvent') {
+      triggerPositionedSegs(context, segs, false) // isMirror=false
     }
 
+    this.segs = segs
     this.isSizeDirty = true
+
     return segs
   }
 
 
   // Unrenders a specific type of fill that is currently rendered on the grid
-  _unrenderSegs(props: BaseFillRendererProps, context: ComponentContext, segs: Seg[]) {
-    if (props.type === 'bgEvent') {
-      triggerWillRemoveSegs(context, segs, false) // isMirror=false. will use publiclyTriggerAfterSizing so will fire after
+  _unrenderSegs(segs: Seg[], context: ComponentContext) {
+    if (this.props.type === 'bgEvent') {
+      triggerWillRemoveSegs(context, segs, false) // isMirror=false
     }
   }
 
@@ -122,8 +126,9 @@ export default abstract class FillRenderer<FillRendererProps extends BaseFillRen
   assignSizes(force: boolean, userComponent: any) {
     if (force || this.isSizeDirty) {
       this.assignSegSizes(this.segs, userComponent)
-      this.isSizeDirty = false
     }
+
+    this.isSizeDirty = false
   }
 
 

+ 9 - 9
packages/core/src/main.ts

@@ -44,7 +44,6 @@ export {
 } from './util/array'
 
 export { memoize, memoizeOutput } from './util/memoize'
-export { memoizeRendering, MemoizedRendering } from './component/memoized-rendering'
 
 export {
   intersectRects,
@@ -58,7 +57,7 @@ export { mapHash, filterHash, isPropsEqual } from './util/object'
 
 export {
   findElements,
-  findChildren,
+  findDirectChildren,
   htmlToElement,
   createElement,
   insertAfterElement,
@@ -98,16 +97,15 @@ export { default as EmitterMixin, EmitterInterface } from './common/EmitterMixin
 export { DateRange, rangeContainsMarker, intersectRanges, rangesEqual, rangesIntersect, rangeContainsRange } from './datelib/date-range'
 export { default as Mixin } from './common/Mixin'
 export { default as PositionCache } from './common/PositionCache'
-export { default as ScrollComponent, ScrollbarWidths } from './common/ScrollComponent'
+export { default as Scroller, ScrollerProps, ScrollbarWidths } from './common/Scroller'
 export { ScrollController, ElementScrollController, WindowScrollController } from './common/scroll-controller'
 export { default as Theme } from './theme/Theme'
-export { default as Component } from './component/Component'
 export { default as ComponentContext } from './component/ComponentContext'
 export { default as DateComponent, Seg, EventSegUiInteractionState } from './component/DateComponent'
 export { default as Calendar, DatePointTransform, DateSpanTransform, DateSelectionApi } from './Calendar'
-export { default as View, ViewProps } from './View'
-export { default as FgEventRenderer, buildSegCompareObj, BaseFgEventRendererProps } from './component/renderers/FgEventRenderer'
-export { default as FillRenderer } from './component/renderers/FillRenderer'
+export { default as View, ViewProps, renderViewEl } from './View'
+export { default as FgEventRenderer, buildSegCompareObj, BaseFgEventRendererProps, sortEventSegs } from './component/renderers/FgEventRenderer'
+export { default as FillRenderer, BaseFillRendererProps } from './component/renderers/FillRenderer'
 
 export { default as DateProfileGenerator, DateProfile } from './DateProfileGenerator'
 export { ViewDef } from './structs/view-def'
@@ -157,12 +155,12 @@ export { CalendarComponentProps } from './CalendarComponent'
 export { default as DayHeader } from './common/DayHeader'
 export { computeFallbackHeaderFormat, renderDateCell } from './common/table-utils'
 
-export { default as DaySeries } from './common/DaySeries'
+export { default as DaySeries } from './common/DaySeriesModel'
 
 export { EventInteractionState } from './interactions/event-interaction-state'
 export { EventRenderRange, sliceEventStore, hasBgRendering, getElSeg, computeEventDraggable, computeEventStartResizable, computeEventEndResizable } from './component/event-rendering'
 
-export { default as DayTable, DayTableSeg, DayTableCell } from './common/DayTable'
+export { default as DayTableModel, DayTableSeg, DayTableCell } from './common/DayTableModel'
 
 export { default as Slicer, SlicedProps } from './common/slicing-utils'
 
@@ -171,3 +169,5 @@ export { Constraint, ConstraintInput, AllowFunc, isPropsValid, isInteractionVali
 export { default as EventApi } from './api/EventApi'
 
 export { default as requestJson } from './util/requestJson'
+
+export { Component, renderer } from './view-framework'

+ 2 - 2
packages/core/src/util/dom-manip.ts

@@ -163,8 +163,8 @@ export function findElements(container: HTMLElement[] | HTMLElement | NodeListOf
 }
 
 // accepts multiple subject els
-// only queries direct child elements
-export function findChildren(parent: HTMLElement[] | HTMLElement, selector?: string): HTMLElement[] {
+// only queries direct child elements // TODO: rename to findDirectChildren!
+export function findDirectChildren(parent: HTMLElement[] | HTMLElement, selector?: string): HTMLElement[] {
   let parents = parent instanceof HTMLElement ? [ parent ] : parent
   let allMatches = []
 

+ 3 - 3
packages/daygrid/src/Table.ts

@@ -2,7 +2,7 @@ import {
   createElement,
   insertAfterElement,
   findElements,
-  findChildren,
+  findDirectChildren,
   removeElement,
   computeRect,
   PositionCache,
@@ -546,7 +546,7 @@ export default class Table extends Component<TableProps, TableState> {
     rowStruct
   ): (number | false) {
     let rowBottom = rowEl.getBoundingClientRect().bottom // relative to viewport!
-    let trEls = findChildren(rowStruct.tbodyEl) as HTMLTableRowElement[]
+    let trEls = findDirectChildren(rowStruct.tbodyEl) as HTMLTableRowElement[]
     let i
     let trEl: HTMLTableRowElement
 
@@ -608,7 +608,7 @@ export default class Table extends Component<TableProps, TableState> {
       levelSegs = rowStruct.segLevels[levelLimit - 1]
       cellMatrix = rowStruct.cellMatrix
 
-      limitedNodes = findChildren(rowStruct.tbodyEl).slice(levelLimit) // get level <tr> elements past the limit
+      limitedNodes = findDirectChildren(rowStruct.tbodyEl).slice(levelLimit) // get level <tr> elements past the limit
       limitedNodes.forEach(function(node) {
         node.classList.add('fc-limited') // hide elements and get a simple DOM-nodes array
       })