Răsfoiți Sursa

SimpleDayGrid, SimpleTimeGrid

Adam Shaw 7 ani în urmă
părinte
comite
70f1d720b2

+ 123 - 8
src/View.ts

@@ -1,19 +1,30 @@
 import { assignTo } from './util/object'
 import { parseFieldSpecs } from './util/misc'
-import DateProfileGenerator from './DateProfileGenerator'
-import StandardDateComponent from './component/StandardDateComponent'
+import DateProfileGenerator, { DateProfile } from './DateProfileGenerator'
 import { DateMarker, addMs } from './datelib/marker'
-import { createDuration } from './datelib/duration'
+import { createDuration, Duration } from './datelib/duration'
 import { default as EmitterMixin, EmitterInterface } from './common/EmitterMixin'
 import { ViewSpec } from './structs/view-spec'
 import { createElement } from './util/dom-manip'
 import { ComponentContext } from './component/Component'
+import DateComponent from './component/DateComponent'
+import { EventStore } from './structs/event-store'
+import { EventUiHash } from './component/event-rendering'
+import { DateSpan } from './structs/date-span'
+import { EventInteractionUiState } from './interactions/event-interaction-state'
+
+export interface ViewProps {
+  dateProfile: DateProfile | null
+  businessHours: EventStore
+  eventStore: EventStore
+  eventUis: EventUiHash
+  dateSelection: DateSpan | null
+  eventSelection: string
+  eventDrag: EventInteractionUiState | null
+  eventResize: EventInteractionUiState | null
+}
 
-
-/* An abstract class from which other views inherit from
-----------------------------------------------------------------------------------------------------------------------*/
-
-export default abstract class View extends StandardDateComponent {
+export default abstract class View extends DateComponent<ViewProps> {
 
   // config properties, initialized after class on prototype
   usesMinMaxTime: boolean // whether minTime/maxTime will affect the activeRange. Views must opt-in.
@@ -34,6 +45,7 @@ export default abstract class View extends StandardDateComponent {
   queuedScroll: any
 
   eventOrderSpecs: any // criteria for ordering events when they have same date/time
+  nextDayThreshold: Duration
 
   // now indicator
   isNowIndicatorRendered: boolean
@@ -60,6 +72,7 @@ export default abstract class View extends StandardDateComponent {
     this.dateProfileGenerator = dateProfileGenerator
     this.type = viewSpec.type
     this.eventOrderSpecs = parseFieldSpecs(this.opt('eventOrder'))
+    this.nextDayThreshold = createDuration(this.opt('nextDayThreshold'))
 
     parentEl.appendChild(this.el)
     this.initialize()
@@ -91,6 +104,108 @@ export default abstract class View extends StandardDateComponent {
   }
 
 
+  // General Rendering
+  // -----------------------------------------------------------------------------------------------------------------
+
+
+  render(props: ViewProps) {
+    this.subrender('afterSkeletonRender', [], 'beforeSkeletonUnrender', true)
+    let dateId = this.subrender('_renderDates', [ props.dateProfile ], '_unrenderDates', true)
+    this.subrender('renderBusinessHours', [ props.businessHours, props.dateProfile, dateId ], 'unrenderBusinessHours', true)
+    this.subrender('renderDateSelectionState', [ props.dateSelection, dateId ], 'unrenderDateSelectionState', true)
+    let evId = this.subrender('renderEvents', [ props.eventStore, props.eventUis, dateId ], 'unrenderEvents', true)
+    this.subrender('renderEventSelection', [ props.eventSelection, evId ], 'unrenderEventSelection', true)
+    this.subrender('renderEventDragState', [ props.eventDrag, dateId ], 'unrenderEventDragState', true)
+    this.subrender('renderEventResizeState', [ props.eventResize, dateId ], 'unrenderEventResizeState', true)
+  }
+
+  _renderDates(dateProfile: DateProfile) {
+    this.renderDates(dateProfile)
+    this.afterDatesRender()
+  }
+
+  _unrenderDates() {
+    this.beforeDatesUnrender()
+    this.unrenderDates()
+  }
+
+  renderDates(dateProfile: DateProfile) {}
+  unrenderDates() {}
+
+  renderBusinessHours(businessHours: EventStore) {}
+  unrenderBusinessHours() {}
+
+  renderDateSelectionState(selection: DateSpan) {}
+  unrenderDateSelectionState() {}
+
+  renderEvents(eventStore: EventStore, eventUis: EventUiHash) {}
+  unrenderEvents() {}
+
+  renderEventSelection(instanceId: string) {}
+  unrenderEventSelection() {}
+
+  renderEventDragState(state: EventInteractionUiState) {}
+  unrenderEventDragState() {}
+
+  renderEventResizeState(state: EventInteractionUiState) {}
+  unrenderEventResizeState() {}
+
+
+  // Sizing
+  // -----------------------------------------------------------------------------------------------------------------
+
+
+  updateSize(viewHeight: number, isAuto: boolean, isResize: boolean) {
+    let map = this.dirtySizeMethodNames
+
+    if (isResize || map.has('afterSkeletonRender') || map.has('_renderDates') || map.has('renderEvents')) {
+      // sort of the catch-all sizing
+      // anything that might cause dimension changes
+      this.updateBaseSize(viewHeight, isAuto, isResize)
+    }
+
+    if (isResize || map.has('renderBusinessHours')) {
+      this.computeBusinessHoursSize()
+    }
+
+    if (isResize || map.has('renderDateSelectionState') || map.has('renderEventDragState') || map.has('renderEventResizeState')) {
+      if (this.mirrorRenderer) {
+        this.mirrorRenderer.computeSizes()
+      }
+      if (this.fillRenderer) {
+        this.fillRenderer.computeSizes('highlight')
+      }
+    }
+
+    if (isResize || map.has('renderEvents')) {
+      this.computeEventsSize()
+    }
+
+    if (isResize || map.has('renderBusinessHours')) {
+      this.assignBusinessHoursSize()
+    }
+
+    if (isResize || map.has('renderDateSelectionState') || map.has('renderEventDragState') || map.has('renderEventResizeState')) {
+      if (this.mirrorRenderer) {
+        this.mirrorRenderer.assignSizes()
+      }
+      if (this.fillRenderer) {
+        this.fillRenderer.assignSizes('highlight')
+      }
+    }
+
+    if (isResize || map.has('renderEvents')) {
+      this.assignEventsSize()
+    }
+
+    this.dirtySizeMethodNames = new Map()
+  }
+
+
+  updateBaseSize(viewHeight: number, isAuto: boolean, isResize: boolean) {
+  }
+
+
   // Skeleton Rendering
   // -----------------------------------------------------------------------------------------------------------------
 

+ 13 - 27
src/agenda/AbstractAgendaView.ts

@@ -17,12 +17,10 @@ import { EventInteractionUiState } from '../interactions/event-interaction-state
 import reselector from '../util/reselector'
 import { EventUiHash, hasBgRendering } from '../component/event-rendering'
 import { buildGotoAnchorHtml, getAllDayHtml } from '../component/date-rendering'
-import { DateMarker, diffDays } from '../datelib/marker'
+import { diffDays } from '../datelib/marker'
 import { ComponentContext } from '../component/Component'
 import { ViewSpec } from '../structs/view-spec'
 import DateProfileGenerator from '../DateProfileGenerator'
-import DayGridSlicer from '../basic/DayGridSlicer'
-import TimeGridSlicer from './TimeGridSlicer'
 
 const AGENDA_ALL_DAY_EVENT_LIMIT = 5
 const WEEK_HEADER_FORMAT = createFormatter({ week: 'short' })
@@ -41,35 +39,23 @@ export default abstract class AgendaView extends View {
   scroller: ScrollComponent
   axisWidth: any // the width of the time axis running down the side
 
-  dayGridSlicer: DayGridSlicer
-  timeGridSlicer: TimeGridSlicer
-
   // reselectors
-  filterEventsForTimeGrid: any
-  filterEventsForDayGrid: any
-  buildEventDragForTimeGrid: any
-  buildEventDragForDayGrid: any
-  buildEventResizeForTimeGrid: any
-  buildEventResizeForDayGrid: any
+  filterEventsForTimeGrid = reselector(filterEventsForTimeGrid)
+  filterEventsForDayGrid = reselector(filterEventsForDayGrid)
+  buildEventDragForTimeGrid = reselector(buildInteractionForTimeGrid)
+  buildEventDragForDayGrid = reselector(buildInteractionForDayGrid)
+  buildEventResizeForTimeGrid = reselector(buildInteractionForTimeGrid)
+  buildEventResizeForDayGrid = reselector(buildInteractionForDayGrid)
 
 
   constructor(
     context: ComponentContext,
     viewSpec: ViewSpec,
     dateProfileGenerator: DateProfileGenerator,
-    parentEl: HTMLElement,
-    TimeGridClass,
-    DayGridClass
+    parentEl: HTMLElement
   ) {
     super(context, viewSpec, dateProfileGenerator, parentEl)
 
-    this.filterEventsForTimeGrid = reselector(filterEventsForTimeGrid)
-    this.filterEventsForDayGrid = reselector(filterEventsForDayGrid)
-    this.buildEventDragForTimeGrid = reselector(buildInteractionForTimeGrid)
-    this.buildEventDragForDayGrid = reselector(buildInteractionForDayGrid)
-    this.buildEventResizeForTimeGrid = reselector(buildInteractionForTimeGrid)
-    this.buildEventResizeForDayGrid = reselector(buildInteractionForDayGrid)
-
     this.el.classList.add('fc-agenda-view')
     this.el.innerHTML = this.renderSkeletonHtml()
 
@@ -84,7 +70,7 @@ export default abstract class AgendaView extends View {
     let timeGridEl = createElement('div', { className: 'fc-time-grid' })
     timeGridWrapEl.appendChild(timeGridEl)
 
-    this.timeGrid = new TimeGridClass(
+    this.timeGrid = new TimeGrid(
       this.context,
       timeGridEl,
       {
@@ -95,7 +81,7 @@ export default abstract class AgendaView extends View {
 
     if (this.opt('allDaySlot')) { // should we display the "all-day" area?
 
-      this.dayGrid = new DayGridClass( // the all-day subcomponent of this view
+      this.dayGrid = new DayGrid( // the all-day subcomponent of this view
         this.context,
         this.el.querySelector('.fc-day-grid'),
         {
@@ -169,9 +155,9 @@ export default abstract class AgendaView extends View {
   }
 
 
-  renderNowIndicator(date: DateMarker) {
-    this.timeGrid.renderNowIndicator(date)
-  }
+  // subclasses should implement
+  // renderNowIndicator(date: DateMarker) {
+  // }
 
 
   unrenderNowIndicator() {

+ 54 - 57
src/agenda/AgendaView.ts

@@ -1,22 +1,23 @@
 import AbstractAgendaView from './AbstractAgendaView'
-import TimeGrid from './TimeGrid'
-import DayGrid from '../basic/DayGrid'
 import DateProfileGenerator, { DateProfile } from '../DateProfileGenerator'
 import { ComponentContext } from '../component/Component'
 import { ViewSpec } from '../structs/view-spec'
 import DayHeader from '../common/DayHeader'
-import { StandardDateComponentProps } from '../component/StandardDateComponent'
-import { assignTo } from '../util/object'
-import reselector from '../util/reselector'
-import DayGridSlicer from '../basic/DayGridSlicer'
-import TimeGridSlicer from './TimeGridSlicer'
-import DayTable from '../common/DayTable'
 import DaySeries from '../common/DaySeries'
+import DayTable from '../common/DayTable'
+import SimpleTimeGrid from './SimpleTimeGrid'
+import SimpleDayGrid from '../basic/SimpleDayGrid'
+import reselector from '../util/reselector'
+import { ViewProps } from 'src/View'
 
 
 export default class AgendaView extends AbstractAgendaView {
 
   header: DayHeader
+  simpleDayGrid: SimpleDayGrid
+  simpleTimeGrid: SimpleTimeGrid
+
+  buildDayTable = reselector(buildDayTable)
 
   constructor(
     context: ComponentContext,
@@ -24,7 +25,7 @@ export default class AgendaView extends AbstractAgendaView {
     dateProfileGenerator: DateProfileGenerator,
     parentEl: HTMLElement
   ) {
-    super(context, viewSpec, dateProfileGenerator, parentEl, TimeGrid, DayGrid)
+    super(context, viewSpec, dateProfileGenerator, parentEl)
 
     if (this.opt('columnHeader')) {
       this.header = new DayHeader(
@@ -32,6 +33,12 @@ export default class AgendaView extends AbstractAgendaView {
         this.el.querySelector('.fc-head-container')
       )
     }
+
+    this.simpleTimeGrid = new SimpleTimeGrid(context, this.timeGrid)
+
+    if (this.dayGrid) {
+      this.simpleDayGrid = new SimpleDayGrid(context, this.dayGrid)
+    }
   }
 
   destroy() {
@@ -42,68 +49,58 @@ export default class AgendaView extends AbstractAgendaView {
     }
   }
 
-  render(props: StandardDateComponentProps) {
+  render(props: ViewProps) {
     super.render(props)
 
-    let allDaySeletion = null
-    let timedSelection = null
-
-    if (props.dateSelection) {
-      if (props.dateSelection.allDay) {
-        allDaySeletion = props.dateSelection
-      } else {
-        timedSelection = props.dateSelection
-      }
-    }
-
-    let timeGridSlicer = this.buildTimeGridSlicer(props.dateProfile)
+    let { dateProfile, dateSelection } = this.props
+    let dayTable = this.buildDayTable(dateProfile, this.dateProfileGenerator)
 
     if (this.header) {
       this.header.receiveProps({
-        dateProfile: props.dateProfile,
-        dates: timeGridSlicer.daySeries.dates,
+        dateProfile,
+        dates: dayTable.headerDates,
         datesRepDistinctDays: true,
         renderIntroHtml: this.renderHeadIntroHtml
       })
     }
 
-    this.timeGrid.receiveProps(
-      assignTo({}, props, {
-        eventStore: this.filterEventsForTimeGrid(props.eventStore, props.eventUis),
-        dateSelection: timedSelection,
-        eventDrag: this.buildEventDragForTimeGrid(props.eventDrag),
-        eventResize: this.buildEventResizeForTimeGrid(props.eventResize),
-        slicer: timeGridSlicer
+    this.simpleTimeGrid.receiveProps({
+      dateProfile,
+      dayTable,
+      businessHours: props.businessHours,
+      dateSelection: dateSelection && !dateSelection.allDay ? dateSelection : null,
+      eventStore: this.filterEventsForTimeGrid(props.eventStore, props.eventUis),
+      eventUis: props.eventUis,
+      eventSelection: props.eventSelection,
+      eventDrag: this.buildEventDragForTimeGrid(props.eventDrag),
+      eventResize: this.buildEventResizeForTimeGrid(props.eventResize)
+    })
+
+    if (this.simpleDayGrid) {
+      this.simpleDayGrid.receiveProps({
+        dateProfile,
+        dayTable,
+        businessHours: props.businessHours,
+        dateSelection: dateSelection && dateSelection.allDay ? dateSelection : null,
+        eventStore: this.filterEventsForDayGrid(props.eventStore, props.eventUis),
+        eventUis: props.eventUis,
+        eventSelection: props.eventSelection,
+        eventDrag: this.buildEventDragForDayGrid(props.eventDrag),
+        eventResize: this.buildEventResizeForDayGrid(props.eventResize),
+        nextDayThreshold: this.nextDayThreshold,
+        isRigid: false
       })
-    )
-
-    if (this.dayGrid) {
-      this.dayGrid.receiveProps(
-        assignTo({}, props, {
-          eventStore: this.filterEventsForDayGrid(props.eventStore, props.eventUis),
-          dateSelection: allDaySeletion,
-          eventDrag: this.buildEventDragForDayGrid(props.eventDrag),
-          eventResize: this.buildEventResizeForDayGrid(props.eventResize),
-          slicer: this.buildDayGridSlicer(props.dateProfile)
-        })
-      )
     }
   }
 
-  buildDayGridSlicer = reselector(function(this: AgendaView, dateProfile: DateProfile) {
-    return new DayGridSlicer(
-      new DaySeries(dateProfile.renderRange, this.dateProfileGenerator), // TODO: reuse!!!
-      this.isRtl,
-      false // breakOnWeeks
-    )
-  })
+  renderNowIndicator(date) {
+    this.simpleTimeGrid.renderNowIndicator(date)
+  }
 
-  buildTimeGridSlicer = reselector(function(this: AgendaView, dateProfile) {
-    return new TimeGridSlicer(
-      dateProfile,
-      this.dateProfileGenerator,
-      this.dateEnv
-    )
-  })
+}
+
+function buildDayTable(dateProfile: DateProfile, dateProfileGenerator: DateProfileGenerator): DayTable {
+  let daySeries = new DaySeries(dateProfile.renderRange, this.dateProfileGenerator)
 
+  return new DayTable(daySeries, false)
 }

+ 191 - 0
src/agenda/SimpleTimeGrid.ts

@@ -0,0 +1,191 @@
+import TimeGrid, { TimeGridSeg } from './TimeGrid'
+import Component from '../component/Component'
+import { DateProfile } from '../DateProfileGenerator'
+import { EventStore } from '../structs/event-store'
+import { EventUiHash, EventRenderRange, sliceEventStore } from '../component/event-rendering'
+import { EventInteractionUiState } from '../interactions/event-interaction-state'
+import { DateSpan, fabricateEventRange } from '../structs/date-span'
+import reselector from '../util/reselector'
+import { intersectRanges, DateRange } from '../datelib/date-range'
+import { sliceBusinessHours } from '../structs/business-hours'
+import DayTable from '../common/DayTable'
+import { DateEnv } from '../datelib/env'
+import { DateMarker, addMs } from '../datelib/marker'
+
+export interface SimpleTimeGridProps {
+  dateProfile: DateProfile | null
+  dayTable: DayTable
+  businessHours: EventStore
+  eventStore: EventStore
+  eventUis: EventUiHash
+  dateSelection: DateSpan | null
+  eventSelection: string
+  eventDrag: EventInteractionUiState | null
+  eventResize: EventInteractionUiState | null
+}
+
+export default class SimpleTimeGrid extends Component<SimpleTimeGridProps> {
+
+  timeGrid: TimeGrid
+  colRanges: DateRange[]
+
+  buildColRanges = reselector(buildColRanges)
+  eventStoreToSegs = reselector(eventStoreToSegs)
+  businessHoursToSegs = reselector(businessHoursToSegs)
+  selectionToSegs = reselector(dateSpanToSegs)
+  buildEventDrag = reselector(buildSegInteraction)
+  buildEventResize = reselector(buildSegInteraction)
+
+  constructor(context, timeGrid: TimeGrid) {
+    super(context)
+
+    this.timeGrid = timeGrid
+  }
+
+  render(props: SimpleTimeGridProps) {
+    let { timeGrid } = this
+    let { dateProfile, dayTable } = props
+
+    let colRanges = this.colRanges =
+      this.buildColRanges(dayTable, dateProfile, this.dateEnv)
+
+    timeGrid.receiveProps({
+      dateProfile,
+      cells: dayTable.cells[0],
+      businessHourSegs: this.businessHoursToSegs(props.businessHours, dateProfile, colRanges, timeGrid),
+      eventSegs: this.eventStoreToSegs(props.eventStore, props.eventUis, dateProfile, colRanges, timeGrid),
+      dateSelectionSegs: this.selectionToSegs(props.dateSelection, colRanges, timeGrid),
+      eventSelection: props.eventSelection,
+      eventDrag: this.buildEventDrag(props.eventDrag, props.eventUis, dateProfile, colRanges, timeGrid),
+      eventResize: this.buildEventResize(props.eventResize, props.eventUis, dateProfile, colRanges, timeGrid)
+    })
+  }
+
+  renderNowIndicator(date: DateMarker) {
+    this.timeGrid.renderNowIndicator(
+
+      // seg system might be overkill, but it handles scenario where line needs to be rendered
+      //  more than once because of columns with the same date (resources columns for example)
+      dateSpanToSegs({
+        range: {
+          start: date,
+          end: addMs(date, 1) // protect against null range
+        },
+        allDay: false
+      }, this.colRanges, this.timeGrid),
+
+      date
+    )
+  }
+
+}
+
+function buildColRanges(dayTable: DayTable, dateProfile: DateProfile, dateEnv: DateEnv): DateRange[] {
+  let ranges: DateRange[] = []
+
+  for (let col = 0; col < dayTable.colCnt; col++) {
+    let date = dayTable.cells[0][col].date
+
+    ranges.push({
+      start: dateEnv.add(date, dateProfile.minTime),
+      end: dateEnv.add(date, dateProfile.maxTime)
+    })
+  }
+
+  return ranges
+}
+
+function eventStoreToSegs(eventStore: EventStore, eventUis: EventUiHash, dateProfile: DateProfile, colRanges: DateRange[], timeGrid: TimeGrid) {
+  return eventRangesToSegs(
+    sliceEventStore(eventStore, eventUis, dateProfile.activeRange),
+    colRanges,
+    timeGrid
+  )
+}
+
+function businessHoursToSegs(businessHours: EventStore, dateProfile: DateProfile, colRanges: DateRange[], timeGrid: TimeGrid) {
+  return eventRangesToSegs(
+    sliceBusinessHours(businessHours, dateProfile.activeRange, null, timeGrid.calendar),
+    colRanges,
+    timeGrid
+  )
+}
+
+function buildSegInteraction(interaction: EventInteractionUiState, eventUis: EventUiHash, dateProfile: DateProfile, colRanges: DateRange[], timeGrid: TimeGrid) {
+  if (!interaction) {
+    return null
+  }
+
+  return {
+    segs: eventRangesToSegs(
+      sliceEventStore(interaction.mutatedEvents, eventUis, dateProfile.activeRange),
+      colRanges,
+      timeGrid
+    ),
+    affectedInstances: interaction.affectedEvents.instances,
+    isEvent: interaction.isEvent,
+    sourceSeg: interaction.origSeg
+  }
+}
+
+function dateSpanToSegs(dateSpan: DateSpan, colRanges: DateRange[], timeGrid: TimeGrid): TimeGridSeg[] {
+
+  if (!dateSpan) {
+    return []
+  }
+
+  let eventRange = fabricateEventRange(dateSpan)
+  let { range } = dateSpan
+  let segs: TimeGridSeg[] = []
+
+  for (let col = 0; col < colRanges.length; col++) {
+    let segRange = intersectRanges(range, colRanges[col])
+
+    if (segRange) {
+      segs.push({
+        component: timeGrid,
+        eventRange,
+        start: segRange.start,
+        end: segRange.end,
+        isStart: segRange.start.valueOf() === range.start.valueOf(),
+        isEnd: segRange.end.valueOf() === range.end.valueOf(),
+        col
+      })
+    }
+  }
+
+  return segs
+}
+
+function eventRangesToSegs(eventRanges: EventRenderRange[], colRanges: DateRange[], timeGrid: TimeGrid): TimeGridSeg[] {
+  let segs = []
+
+  for (let eventRange of eventRanges) {
+    segs.push(...eventRangeToSegs(colRanges, eventRange, timeGrid))
+  }
+
+  return segs
+}
+
+function eventRangeToSegs(colRanges: DateRange[], eventRange: EventRenderRange, timeGrid: TimeGrid): TimeGridSeg[] {
+  let { range } = eventRange
+  let segs = []
+
+  for (let col = 0; col < colRanges.length; col++) {
+    let segRange = intersectRanges(range, colRanges[col])
+
+    if (segRange) {
+      segs.push({
+        component: timeGrid,
+        eventRange,
+        start: segRange.start,
+        end: segRange.end,
+        isStart: segRange.start.valueOf() === range.start.valueOf(),
+        isEnd: segRange.end.valueOf() === range.end.valueOf(),
+        col
+      })
+    }
+  }
+
+  return segs
+}

+ 115 - 60
src/agenda/TimeGrid.ts

@@ -1,19 +1,19 @@
 import { htmlEscape } from '../util/html'
-import { htmlToElement, findElements, createElement, removeElement, applyStyle } from '../util/dom-manip'
+import { htmlToElement, findElements, removeElement, applyStyle, createElement } from '../util/dom-manip'
 import PositionCache from '../common/PositionCache'
 import TimeGridEventRenderer from './TimeGridEventRenderer'
 import TimeGridMirrorRenderer from './TimeGridMirrorRenderer'
 import TimeGridFillRenderer from './TimeGridFillRenderer'
 import { Duration, createDuration, addDurations, multiplyDuration, wholeDivideDurations, asRoughMs } from '../datelib/duration'
-import { startOfDay, DateMarker, addMs } from '../datelib/marker'
+import { startOfDay, DateMarker } from '../datelib/marker'
 import { DateFormatter, createFormatter, formatIsoTimeString } from '../datelib/formatting'
 import { ComponentContext } from '../component/Component'
-import { Seg } from '../component/DateComponent'
-import StandardDateComponent from '../component/StandardDateComponent'
+import DateComponent, { Seg, EventSegUiInteractionState } from '../component/DateComponent'
 import OffsetTracker from '../common/OffsetTracker'
 import { Hit } from '../interactions/HitDragging'
 import DayBgRow from '../basic/DayBgRow'
-import TimeGridSlicer from './TimeGridSlicer'
+import { DateProfile } from '../DateProfileGenerator'
+
 
 /* A component that renders one or more columns of vertical time slots
 ----------------------------------------------------------------------------------------------------------------------*/
@@ -33,7 +33,29 @@ export interface RenderProps {
   renderIntroHtml: () => string
 }
 
-export default class TimeGrid extends StandardDateComponent {
+export interface TimeGridSeg extends Seg {
+  col: number
+  start: DateMarker
+  end: DateMarker
+}
+
+export interface TimeGridCell {
+  date: DateMarker
+  htmlAttrs?: string
+}
+
+export interface TimeGridProps {
+  dateProfile: DateProfile
+  cells: TimeGridCell[]
+  businessHourSegs: TimeGridSeg[]
+  eventSegs: TimeGridSeg[]
+  dateSelectionSegs: TimeGridSeg[]
+  eventSelection: string
+  eventDrag: EventSegUiInteractionState | null
+  eventResize: EventSegUiInteractionState | null
+}
+
+export default class TimeGrid extends DateComponent<TimeGridProps> {
 
   renderProps: RenderProps
 
@@ -43,6 +65,7 @@ export default class TimeGrid extends StandardDateComponent {
   labelFormat: DateFormatter // formatting string for times running along vertical axis
   labelInterval: Duration // duration of how often a label should be displayed for a slot
 
+  colCnt: number
   colEls: HTMLElement[] // cells elements in the day-row background
   slatContainerEl: HTMLElement // div that wraps all the slat rows
   slatEls: HTMLElement[] // elements running horizontally across all columns
@@ -71,7 +94,6 @@ export default class TimeGrid extends StandardDateComponent {
     this.eventRenderer = new TimeGridEventRenderer(this)
     this.mirrorRenderer = new TimeGridMirrorRenderer(this)
     this.fillRenderer = new TimeGridFillRenderer(this)
-    this.slicingType = 'timed'
 
     this.processOptions()
 
@@ -153,29 +175,80 @@ export default class TimeGrid extends StandardDateComponent {
   }
 
 
-  /* Date Rendering
+  /* Rendering
   ------------------------------------------------------------------------------------------------------------------*/
 
 
-  renderDates() {
-    this.renderSlats()
-    this.renderColumns()
+  render(props: TimeGridProps) {
+    let cells = props.cells
+    this.colCnt = cells.length
+
+    this.subrender('renderSlats', [ props.dateProfile ], null, true)
+    let dateId = this.subrender('renderColumns', [ props.cells, props.dateProfile ], 'unrenderColumns', true)
+    this.subrender('renderBusinessHourSegs', [ props.businessHourSegs, props.dateProfile, dateId ], 'unrenderBusinessHours', true)
+    this.subrender('renderDateSelectionSegs', [ props.dateSelectionSegs, dateId ], 'unrenderDateSelection', true)
+    let evId = this.subrender('renderEventSegs', [ props.eventSegs, dateId ], 'unrenderEvents', true)
+    this.subrender('renderEventSelection', [ props.eventSelection, evId ], 'unrenderEventSelection', true)
+    this.subrender('renderEventDragSegs', [ props.eventDrag, dateId ], 'unrenderEventDragSegs', true)
+    this.subrender('renderEventResizeSegs', [ props.eventResize, dateId ], 'unrenderEventResizeSegs', true)
   }
 
+  // TODO: kill
+  updateSize(viewHeight: number, isAuto: boolean, isResize: boolean) {
+    let map = this.dirtySizeMethodNames
+
+    if (isResize || map.has('renderSlats')) {
+      this.buildSlatPositions()
+    }
+
+    if (isResize || map.has('renderColumns')) {
+      this.buildColPositions()
+    }
+
+    if (isResize || map.has('renderBusinessHourSegs')) {
+      this.computeBusinessHoursSize()
+    }
+
+    if (isResize || map.has('renderDateSelectionSegs') || map.has('renderEventDragSegs') || map.has('renderEventResizeSegs')) {
+      if (this.mirrorRenderer) {
+        this.mirrorRenderer.computeSizes()
+      }
+      if (this.fillRenderer) {
+        this.fillRenderer.computeSizes('highlight')
+      }
+    }
+
+    if (isResize || map.has('renderEventSegs')) {
+      this.computeEventsSize()
+    }
 
-  unrenderDates() {
-    this.unrenderColumns()
-    // we don't unrender slats because won't change between date navigation,
-    // and if slat-related settings are changed, the whole component will be rerendered.
+    if (isResize || map.has('renderBusinessHourSegs')) {
+      this.assignBusinessHoursSize()
+    }
+
+    if (isResize || map.has('renderDateSelectionSegs') || map.has('renderEventDragSegs') || map.has('renderEventResizeSegs')) {
+      if (this.mirrorRenderer) {
+        this.mirrorRenderer.assignSizes()
+      }
+      if (this.fillRenderer) {
+        this.fillRenderer.assignSizes('highlight')
+      }
+    }
+
+    if (isResize || map.has('renderEventSegs')) {
+      this.assignEventsSize()
+    }
+
+    this.dirtySizeMethodNames = new Map()
   }
 
 
-  renderSlats() {
+  renderSlats(dateProfile: DateProfile) {
     let { theme } = this
 
     this.slatContainerEl.innerHTML =
       '<table class="' + theme.getClass('tableGrid') + '">' +
-        this.renderSlatRowHtml() +
+        this.renderSlatRowHtml(dateProfile) +
       '</table>'
 
     this.slatEls = findElements(this.slatContainerEl, 'tr')
@@ -190,9 +263,8 @@ export default class TimeGrid extends StandardDateComponent {
 
 
   // Generates the HTML for the horizontal "slats" that run width-wise. Has a time axis on a side. Depends on RTL.
-  renderSlatRowHtml() {
+  renderSlatRowHtml(dateProfile: DateProfile) {
     let { dateEnv, theme, isRtl } = this
-    let dateProfile = this.props.dateProfile
     let html = ''
     let dayStart = startOfDay(dateProfile.renderRange.start)
     let slotTime = dateProfile.minTime
@@ -233,23 +305,14 @@ export default class TimeGrid extends StandardDateComponent {
   }
 
 
-  renderColumns() {
+  renderColumns(cells: TimeGridCell[], dateProfile: DateProfile) {
     let { theme } = this
-    let slicer = (this.props as any).slicer as TimeGridSlicer
-    let dateProfile = this.props.dateProfile
-
-    let dates = []
-    for (let col = 0; col < slicer.colCnt; col++) {
-      dates.push(
-        slicer.getColDate(col)
-      )
-    }
 
     let bgRow = new DayBgRow(this.context)
     this.rootBgContainerEl.innerHTML =
       '<table class="' + theme.getClass('tableGrid') + '">' +
         bgRow.renderHtml({
-          dates, // TODO: pass over cell objects
+          cells,
           dateProfile,
           renderIntroHtml: this.renderProps.renderBgIntroHtml
         }) +
@@ -283,7 +346,6 @@ export default class TimeGrid extends StandardDateComponent {
 
   // Renders the DOM that the view's content will live in
   renderContentSkeleton() {
-    let slicer = (this.props as any).slicer as TimeGridSlicer
     let parts = []
     let skeletonEl: HTMLElement
 
@@ -291,7 +353,7 @@ export default class TimeGrid extends StandardDateComponent {
       this.renderProps.renderIntroHtml()
     )
 
-    for (let i = 0; i < slicer.colCnt; i++) {
+    for (let i = 0; i < this.colCnt; i++) {
       parts.push(
         '<td>' +
           '<div class="fc-content-col">' +
@@ -344,11 +406,10 @@ export default class TimeGrid extends StandardDateComponent {
 
   // Given a flat array of segments, return an array of sub-arrays, grouped by each segment's col
   groupSegsByCol(segs) {
-    let slicer = (this.props as any).slicer as TimeGridSlicer
     let segsByCol = []
     let i
 
-    for (i = 0; i < slicer.colCnt; i++) {
+    for (i = 0; i < this.colCnt; i++) {
       segsByCol.push([])
     }
 
@@ -363,12 +424,11 @@ export default class TimeGrid extends StandardDateComponent {
   // Given segments grouped by column, insert the segments' elements into a parallel array of container
   // elements, each living within a column.
   attachSegsByCol(segsByCol, containerEls: HTMLElement[]) {
-    let slicer = (this.props as any).slicer as TimeGridSlicer
     let col
     let segs
     let i
 
-    for (col = 0; col < slicer.colCnt; col++) { // iterate each column grouping
+    for (col = 0; col < this.colCnt; col++) { // iterate each column grouping
       segs = segsByCol[col]
 
       for (i = 0; i < segs.length; i++) {
@@ -387,23 +447,13 @@ export default class TimeGrid extends StandardDateComponent {
   }
 
 
-  renderNowIndicator(date) {
-    let slicer = (this.props as any).slicer as TimeGridSlicer
+  renderNowIndicator(segs: TimeGridSeg[], date) {
 
     // HACK: if date columns not ready for some reason (scheduler)
     if (!this.colContainerEls) {
       return
     }
 
-    // seg system might be overkill, but it handles scenario where line needs to be rendered
-    //  more than once because of columns with the same date (resources columns for example)
-    let segs = slicer.dateSpanToSegs({
-      range: {
-        start: date,
-        end: addMs(date, 1) // protect against null range
-      },
-      allDay: false
-    }, this)
     let top = this.computeDateTop(date)
     let nodes = []
     let i
@@ -485,7 +535,6 @@ export default class TimeGrid extends StandardDateComponent {
 
   // For each segment in an array, computes and assigns its top and bottom properties
   computeSegVerticals(segs) {
-    let slicer = (this.props as any).slicer as TimeGridSlicer
     let eventMinHeight = this.opt('agendaEventMinHeight')
     let i
     let seg
@@ -493,7 +542,7 @@ export default class TimeGrid extends StandardDateComponent {
 
     for (i = 0; i < segs.length; i++) {
       seg = segs[i]
-      dayDate = slicer.getColDate(seg.col)
+      dayDate = this.props.cells[seg.col].date
 
       seg.top = this.computeDateTop(seg.start, dayDate)
       seg.bottom = Math.max(
@@ -530,8 +579,12 @@ export default class TimeGrid extends StandardDateComponent {
   ------------------------------------------------------------------------------------------------------------------*/
 
 
-  buildPositionCaches() {
+  buildColPositions() {
     this.colPositions.build()
+  }
+
+
+  buildSlatPositions() {
     this.slatPositions.build()
   }
 
@@ -552,7 +605,6 @@ export default class TimeGrid extends StandardDateComponent {
 
   queryHit(leftOffset, topOffset): Hit {
     let { dateEnv, snapsPerSlot, slatPositions, colPositions, offsetTracker } = this
-    let slicer = (this.props as any).slicer as TimeGridSlicer
 
     if (offsetTracker.isWithinClipping(leftOffset, topOffset)) {
       let leftOrigin = offsetTracker.computeLeft()
@@ -567,7 +619,7 @@ export default class TimeGrid extends StandardDateComponent {
         let localSnapIndex = Math.floor(partial * snapsPerSlot) // the snap # relative to start of slat
         let snapIndex = slatIndex * snapsPerSlot + localSnapIndex
 
-        let dayDate = slicer.getColDate(colIndex)
+        let dayDate = this.props.cells[colIndex].date
         let time = addDurations(
           this.props.dateProfile.minTime,
           multiplyDuration(this.snapDuration, snapIndex)
@@ -600,10 +652,11 @@ export default class TimeGrid extends StandardDateComponent {
   ------------------------------------------------------------------------------------------------------------------*/
 
 
-  renderEventResizeSegs(segs: Seg[], sourceSeg, affectedInstances) {
-    super.renderEventResizeSegs(segs, sourceSeg, affectedInstances)
-
-    this.mirrorRenderer.renderSegs(segs, { isResizing: true, sourceSeg })
+  renderEventResizeSegs(state: EventSegUiInteractionState) {
+    if (state) {
+      this.eventRenderer.hideByHash(state.affectedInstances)
+      this.mirrorRenderer.renderSegs(state.segs, { isResizing: true, sourceSeg: state.sourceSeg })
+    }
   }
 
 
@@ -613,10 +666,12 @@ export default class TimeGrid extends StandardDateComponent {
 
   // Renders a visual indication of a selection. Overrides the default, which was to simply render a highlight.
   renderDateSelectionSegs(segs: Seg[]) {
-    if (this.opt('selectMirror')) {
-      this.mirrorRenderer.renderSegs(segs, { isSelecting: true })
-    } else {
-      this.fillRenderer.renderSegs('highlight', segs)
+    if (segs) {
+      if (this.opt('selectMirror')) {
+        this.mirrorRenderer.renderSegs(segs, { isSelecting: true })
+      } else {
+        this.fillRenderer.renderSegs('highlight', segs)
+      }
     }
   }
 

+ 2 - 5
src/agenda/TimeGridEventRenderer.ts

@@ -5,7 +5,6 @@ import FgEventRenderer, { buildSegCompareObj } from '../component/renderers/FgEv
 import { Seg } from '../component/DateComponent'
 import { isMultiDayRange, compareByFieldSpecs } from '../util/misc'
 import TimeGrid from './TimeGrid'
-import TimeGridSlicer from './TimeGridSlicer';
 
 /*
 Only handles foreground segs.
@@ -50,8 +49,7 @@ export default class TimeGridEventRenderer extends FgEventRenderer {
 
   computeSizes() {
     let { timeGrid, segsByCol } = this
-    let slicer = (timeGrid.props as any).slicer as TimeGridSlicer
-    let colCnt = slicer.colCnt
+    let colCnt = timeGrid.colCnt
 
     if (segsByCol) {
       for (let col = 0; col < colCnt; col++) {
@@ -66,8 +64,7 @@ export default class TimeGridEventRenderer extends FgEventRenderer {
 
   assignSizes() {
     let { timeGrid, segsByCol } = this
-    let slicer = (timeGrid.props as any).slicer as TimeGridSlicer
-    let colCnt = slicer.colCnt
+    let colCnt = timeGrid.colCnt
 
     if (segsByCol) {
       for (let col = 0; col < colCnt; col++) {

+ 0 - 92
src/agenda/TimeGridSlicer.ts

@@ -1,92 +0,0 @@
-import DateProfileGenerator, { DateProfile } from '../DateProfileGenerator'
-import { DateRange, intersectRanges } from '../datelib/date-range'
-import DaySeries from '../common/DaySeries'
-import { Seg } from '../component/DateComponent'
-import { DateEnv } from '../datelib/env'
-import { EventRenderRange } from '../component/event-rendering';
-import { DateSpan } from '../structs/date-span'
-
-export default class TimeGridSlicer {
-
-  dateEnv: DateEnv
-  dateProfile: DateProfile
-  daySeries: DaySeries // TODO: make private!
-  colCnt: number
-
-
-  constructor(dateProfile: DateProfile, dateProfileGenerator: DateProfileGenerator, dateEnv: DateEnv) {
-    this.dateEnv = dateEnv
-    this.dateProfile = dateProfile
-    this.daySeries = new DaySeries(dateProfile.renderRange, dateProfileGenerator) // should pass this in
-    this.colCnt = this.daySeries.dates.length
-  }
-
-
-  eventRangeToSegs(eventRange: EventRenderRange, component) {
-    let range = intersectRanges(eventRange.range, component.props.dateProfile.validRange)
-
-    if (range) {
-      return this.rangeToSegs(range).map(function(seg) {
-        seg.component = component
-        return seg
-      })
-    }
-
-    return []
-  }
-
-
-  dateSpanToSegs(dateSpan: DateSpan, component) {
-    let range = intersectRanges(dateSpan.range, component.props.dateProfile.validRange)
-
-    if (range) {
-      return this.rangeToSegs(range).map(function(seg) {
-        seg.component = component
-        return seg
-      })
-    }
-
-    return []
-  }
-
-
-  // Slices up the given span (unzoned start/end with other misc data) into an array of segments
-  private rangeToSegs(range: DateRange): Seg[] {
-    let segs = []
-
-    // important to do ALL cols (tho can be optimized)
-    // because of extended minTime/maxTime
-    for (let col = 0; col < this.colCnt; col++) {
-      let segRange = intersectRanges(range, this.getColRange(col))
-
-      if (segRange) {
-        segs.push({
-          start: segRange.start,
-          end: segRange.end,
-          isStart: segRange.start.valueOf() === range.start.valueOf(),
-          isEnd: segRange.end.valueOf() === range.end.valueOf(),
-          col
-        })
-      }
-    }
-
-    return segs
-  }
-
-
-  getColDate(col: number) {
-    return this.daySeries.dates[col]
-  }
-
-
-  getColRange(col: number): DateRange { // TODO: cache these
-    let { dateEnv, dateProfile } = this
-    let date = this.getColDate(col)
-
-    return {
-      start: dateEnv.add(date, dateProfile.minTime),
-      end: dateEnv.add(date, dateProfile.maxTime)
-    }
-  }
-
-}

+ 44 - 48
src/basic/BasicView.ts

@@ -10,20 +10,18 @@ import {
 } from '../util/misc'
 import { createFormatter } from '../datelib/formatting'
 import ScrollComponent from '../common/ScrollComponent'
-import View from '../View'
+import View, { ViewProps } from '../View'
 import BasicViewDateProfileGenerator from './BasicViewDateProfileGenerator'
-import DayGrid from './DayGrid'
 import { buildGotoAnchorHtml } from '../component/date-rendering'
-import { StandardDateComponentProps } from '../component/StandardDateComponent'
-import { assignTo } from '../util/object'
 import { ComponentContext } from '../component/Component'
 import { ViewSpec } from '../structs/view-spec'
 import DateProfileGenerator, { DateProfile } from '../DateProfileGenerator'
-import reselector from '../util/reselector'
 import DayHeader from '../common/DayHeader'
-import DayGridSlicer from './DayGridSlicer'
-import DayTable from '../common/DayTable'
 import DaySeries from '../common/DaySeries'
+import DayTable from '../common/DayTable'
+import DayGrid from './DayGrid'
+import SimpleDayGrid from './SimpleDayGrid'
+import reselector from '../util/reselector'
 
 const WEEK_NUM_FORMAT = createFormatter({ week: 'numeric' })
 
@@ -38,7 +36,12 @@ export default class BasicView extends View {
   scroller: ScrollComponent
   header: DayHeader
   dayGrid: DayGrid // the main subcomponent that does most of the heavy lifting
+  simpleDayGrid: SimpleDayGrid
+
   colWeekNumbersVisible: boolean
+  dayTable: DayTable
+
+  buildDayTable = reselector(buildDayTable)
 
 
   constructor(context: ComponentContext, viewSpec: ViewSpec, dateProfileGenerator: DateProfileGenerator, parentEl: HTMLElement) {
@@ -91,7 +94,8 @@ export default class BasicView extends View {
         cellWeekNumbersVisible
       }
     )
-    this.dayGrid.isRigid = this.hasRigidRows()
+
+    this.simpleDayGrid = new SimpleDayGrid(context, this.dayGrid)
   }
 
 
@@ -107,53 +111,37 @@ export default class BasicView extends View {
   }
 
 
-  render(props: StandardDateComponentProps) {
+  render(props: ViewProps) {
     super.render(props)
 
-    let slicer = this.buildSlicer(props.dateProfile)
+    let { dateProfile } = this.props
+
+    let dayTable = this.dayTable = this.buildDayTable(dateProfile, this.dateProfileGenerator)
 
     if (this.header) {
       this.header.receiveProps({
-        dateProfile: props.dateProfile,
-        dates: this.sliceDayDates(slicer), // get just the first row
-        datesRepDistinctDays: slicer.rowCnt === 1,
+        dateProfile,
+        dates: dayTable.headerDates,
+        datesRepDistinctDays: dayTable.rowCnt === 1,
         renderIntroHtml: this.renderHeadIntroHtml
       })
     }
 
-    this.dayGrid.receiveProps(
-      assignTo({}, props, {
-        slicer
-      })
-    )
+    this.simpleDayGrid.receiveProps({
+      dateProfile,
+      dayTable,
+      businessHours: props.businessHours,
+      dateSelection: props.dateSelection,
+      eventStore: props.eventStore,
+      eventUis: props.eventUis,
+      eventSelection: props.eventSelection,
+      eventDrag: props.eventDrag,
+      eventResize: props.eventResize,
+      isRigid: this.hasRigidRows(),
+      nextDayThreshold: this.nextDayThreshold
+    })
   }
 
-
-  buildSlicer = reselector(function(this: BasicView, dateProfile: DateProfile) {
-    return new DayGridSlicer(
-      new DaySeries(
-        this.props.dateProfile.renderRange,
-        this.dateProfileGenerator
-      ),
-      this.isRtl,
-      /year|month|week/.test(dateProfile.currentRangeUnit)
-    )
-  })
-
-
-  sliceDayDates = reselector(function(slicer: DayGridSlicer) { // TODO: put into slicer
-    let dates = []
-
-    for (let col = 0; col < slicer.colCnt; col++) {
-      dates.push(
-        slicer.getCellDate(0, col)
-      )
-    }
-
-    return dates
-  })
-
-
   // Builds the HTML skeleton for the view.
   // The day-grid component will render inside of a container defined by this HTML.
   renderSkeletonHtml() {
@@ -330,16 +318,15 @@ export default class BasicView extends View {
 
   // Generates the HTML that will go before content-skeleton cells that display the day/week numbers
   renderDayGridNumberIntroHtml = (row) => {
-    let { dateEnv } = this
-    let slicer = (this.dayGrid.props as any).slicer as DayGridSlicer
-    let weekStart = slicer.getCellDate(row, 0)
+    let { dateEnv, dayTable } = this
+    let weekStart = dayTable.cells[row][0].date
 
     if (this.colWeekNumbersVisible) {
       return '' +
         '<td class="fc-week-number">' +
           buildGotoAnchorHtml( // aside from link, important for matchCellWidths
             this,
-            { date: weekStart, type: 'week', forceOff: slicer.colCnt === 1 },
+            { date: weekStart, type: 'week', forceOff: dayTable.colCnt === 1 },
             dateEnv.format(weekStart, WEEK_NUM_FORMAT) // inner HTML
           ) +
         '</td>'
@@ -376,3 +363,12 @@ export default class BasicView extends View {
 
 BasicView.prototype.dateProfileGeneratorClass = BasicViewDateProfileGenerator
 
+
+function buildDayTable(dateProfile: DateProfile, dateProfileGenerator: DateProfileGenerator) {
+  let daySeries = new DaySeries(dateProfile.renderRange, dateProfileGenerator)
+
+  return new DayTable(
+    daySeries,
+    /year|month|week/.test(dateProfile.currentRangeUnit)
+  )
+}

+ 13 - 11
src/basic/DayBgRow.ts

@@ -4,8 +4,13 @@ import { getDayClasses } from "../component/date-rendering";
 import { rangeContainsMarker } from "../datelib/date-range";
 import { DateProfile } from "../DateProfileGenerator";
 
+export interface DayBgCell {
+  date: DateMarker
+  htmlAttrs?: string
+}
+
 export interface DayBgRowProps {
-  dates: DateMarker[]
+  cells: DayBgCell[]
   dateProfile: DateProfile
   renderIntroHtml?: () => string
 }
@@ -25,9 +30,14 @@ export default class DayBgRow {
       parts.push(props.renderIntroHtml())
     }
 
-    for (let date of props.dates) {
+    for (let cell of props.cells) {
       parts.push(
-        this.renderCellHtml(date, props.dateProfile)
+        renderCellHtml(
+          cell.date,
+          props.dateProfile,
+          this.context,
+          cell.htmlAttrs
+        )
       )
     }
 
@@ -38,14 +48,6 @@ export default class DayBgRow {
     return '<tr>' + parts.join('') + '</tr>'
   }
 
-  renderCellHtml(date: DateMarker, dateProfile: DateProfile) {
-    return renderCellHtml(
-      date,
-      dateProfile,
-      this.context
-    )
-  }
-
 }
 
 function renderCellHtml(date: DateMarker, dateProfile: DateProfile, context: ComponentContext, otherAttrs?) {

+ 133 - 63
src/basic/DayGrid.ts

@@ -12,11 +12,10 @@ import Popover from '../common/Popover'
 import DayGridEventRenderer from './DayGridEventRenderer'
 import DayGridMirrorRenderer from './DayGridMirrorRenderer'
 import DayGridFillRenderer from './DayGridFillRenderer'
-import { addDays } from '../datelib/marker'
+import { addDays, DateMarker } from '../datelib/marker'
 import { createFormatter } from '../datelib/formatting'
-import { StandardDateComponentProps } from '../component/StandardDateComponent'
+import DateComponentProps, { EventSegUiInteractionState } from '../component/DateComponent'
 import { Seg } from '../component/DateComponent'
-import StandardDateComponent from '../component/StandardDateComponent'
 import DayTile from './DayTile'
 import { Hit } from '../interactions/HitDragging'
 import { rangeContainsMarker, intersectRanges } from '../datelib/date-range'
@@ -24,7 +23,7 @@ import OffsetTracker from '../common/OffsetTracker'
 import { EventRenderRange } from '../component/event-rendering'
 import { buildGotoAnchorHtml, getDayClasses } from '../component/date-rendering'
 import DayBgRow from './DayBgRow'
-import DayGridSlicer from './DayGridSlicer'
+import { DateProfile } from '../DateProfileGenerator'
 
 const DAY_NUM_FORMAT = createFormatter({ day: 'numeric' })
 const WEEK_NUM_FORMAT = createFormatter({ week: 'numeric' })
@@ -33,11 +32,7 @@ const WEEK_NUM_FORMAT = createFormatter({ week: 'numeric' })
 /* A component that renders a grid of whole-days that runs horizontally. There can be multiple rows, one per week.
 ----------------------------------------------------------------------------------------------------------------------*/
 
-export interface DayGridProps extends StandardDateComponent {
-  breakOnWeeks: boolean
-}
-
-export interface RenderProps {
+export interface RenderProps { // TODO: combine with DayGridProps
   renderNumberIntroHtml: (row: number) => string
   renderBgIntroHtml: () => string
   renderIntroHtml: () => string
@@ -45,11 +40,39 @@ export interface RenderProps {
   cellWeekNumbersVisible: boolean // display week numbers in day cell?
 }
 
-export default class DayGrid extends StandardDateComponent {
+export interface DayGridSeg extends Seg {
+  row: number
+  leftCol: number
+  rightCol: number
+}
+
+export interface DayGridCell {
+  date: DateMarker
+  htmlAttrs?: string
+}
+
+export interface DayGridProps {
+  dateProfile: DateProfile
+  cells: DayGridCell[][]
+  businessHourSegs: DayGridSeg[]
+  eventSegs: DayGridSeg[]
+  dateSelectionSegs: DayGridSeg[]
+  eventSelection: string
+  eventDrag: EventSegUiInteractionState | null
+  eventResize: EventSegUiInteractionState | null
+
+  // isRigid determines whether the individual rows should ignore the contents and be a constant height.
+  // Relies on the view's colCnt and rowCnt. In the future, this component should probably be self-sufficient.
+  isRigid: boolean
+}
+
+export default class DayGrid extends DateComponentProps<DayGridProps> {
 
   eventRenderer: DayGridEventRenderer
   renderProps: RenderProps
-  slicer: DayGridSlicer
+
+  rowCnt: number
+  colCnt: number
 
   bottomCoordPadding: number = 0 // hack for extending the hit area for the last row of the coordinate grid
 
@@ -60,10 +83,6 @@ export default class DayGrid extends StandardDateComponent {
   colPositions: PositionCache
   offsetTracker: OffsetTracker
 
-  // isRigid determines whether the individual rows should ignore the contents and be a constant height.
-  // Relies on the view's colCnt and rowCnt. In the future, this component should probably be self-sufficient.
-  isRigid: boolean = false
-
   segPopover: Popover // the Popover that holds events that can't fit in a cell. null when not visible
   segPopoverTile: DayTile
 
@@ -74,14 +93,23 @@ export default class DayGrid extends StandardDateComponent {
     this.eventRenderer = new DayGridEventRenderer(this)
     this.mirrorRenderer = new DayGridMirrorRenderer(this)
     this.fillRenderer = new DayGridFillRenderer(this)
-    this.slicingType = 'all-day' // for eventStoreToRanges
 
     this.renderProps = renderProps
   }
 
 
-  render(props: StandardDateComponentProps) {
-    super.render(props)
+  render(props: DayGridProps) {
+    let cells = props.cells
+    this.rowCnt = cells.length
+    this.colCnt = cells[0].length
+
+    let dateId = this.subrender('renderCells', [ props.cells, props.isRigid ], 'unrenderCells', true)
+    this.subrender('renderBusinessHourSegs', [ props.businessHourSegs, props.dateProfile, dateId ], 'unrenderBusinessHours', true)
+    this.subrender('renderDateSelectionSegs', [ props.dateSelectionSegs, dateId ], 'unrenderDateSelection', true)
+    let evId = this.subrender('renderEventSegs', [ props.eventSegs, dateId ], 'unrenderEvents', true)
+    this.subrender('renderEventSelection', [ props.eventSelection, evId ], 'unrenderEventSelection', true)
+    this.subrender('renderEventDragSegs', [ props.eventDrag, dateId ], 'unrenderEventDragSegs', true)
+    this.subrender('renderEventResizeSegs', [ props.eventResize, dateId ], 'unrenderEventResizeSegs', true)
 
     if (this.segPopoverTile) {
       this.updateSegPopoverTile()
@@ -89,13 +117,23 @@ export default class DayGrid extends StandardDateComponent {
   }
 
 
+  getCellRange(row, col) {
+    let start = this.props.cells[row][col].date
+    let end = addDays(start, 1)
+
+    return { start, end }
+  }
+
+
   updateSegPopoverTile(date?, segs?) {
+    let ownProps = this.props
+
     this.segPopoverTile.receiveProps({
       date: date || (this.segPopoverTile.props as any).date,
       segs: segs || (this.segPopoverTile.props as any).segs,
-      eventSelection: this.props.eventSelection,
-      eventDrag: this.props.eventDrag,
-      eventResize: this.props.eventResize
+      eventSelection: ownProps.eventSelection,
+      eventDragInstances: ownProps.eventDrag ? ownProps.eventDrag.affectedInstances : null,
+      eventResizeInstances: ownProps.eventResize ? ownProps.eventResize.affectedInstances : null
     })
   }
 
@@ -104,16 +142,15 @@ export default class DayGrid extends StandardDateComponent {
   ------------------------------------------------------------------------------------------------------------------*/
 
 
-  renderDates(dateProfile) {
+  renderCells(cells: DayGridCell[][], isRigid: boolean) {
     let { view, dateEnv } = this
-    let slicer = (this.props as any).slicer as DayGridSlicer
-    let { rowCnt, colCnt } = slicer
+    let { rowCnt, colCnt } = this
     let html = ''
     let row
     let col
 
     for (row = 0; row < rowCnt; row++) {
-      html += this.renderDayRowHtml(row, this.isRigid)
+      html += this.renderDayRowHtml(row, isRigid)
     }
     this.el.innerHTML = html
 
@@ -133,7 +170,7 @@ export default class DayGrid extends StandardDateComponent {
 
     this.colPositions = new PositionCache(
       this.el,
-      this.cellEls.slice(0, slicer.colCnt), // only the first row
+      this.cellEls.slice(0, colCnt), // only the first row
       true,
       false // horizontal
     )
@@ -143,7 +180,7 @@ export default class DayGrid extends StandardDateComponent {
       for (col = 0; col < colCnt; col++) {
         this.publiclyTrigger('dayRender', [
           {
-            date: dateEnv.toDate(slicer.getCellDate(row, col)),
+            date: dateEnv.toDate(cells[row][col].date),
             el: this.getCellEl(row, col),
             view
           }
@@ -153,7 +190,7 @@ export default class DayGrid extends StandardDateComponent {
   }
 
 
-  unrenderDates() {
+  unrenderCells() {
     this.removeSegPopover()
   }
 
@@ -162,20 +199,12 @@ export default class DayGrid extends StandardDateComponent {
   // `row` is the row number.
   renderDayRowHtml(row, isRigid) {
     let { theme } = this
-    let slicer = (this.props as any).slicer as DayGridSlicer
     let classes = [ 'fc-row', 'fc-week', theme.getClass('dayRow') ]
 
     if (isRigid) {
       classes.push('fc-rigid')
     }
 
-    let dates = []
-    for (let col = 0; col < slicer.colCnt; col++) {
-      dates.push(
-        slicer.getCellDate(row, col)
-      )
-    }
-
     let bgRow = new DayBgRow(this.context)
 
     return '' +
@@ -183,7 +212,7 @@ export default class DayGrid extends StandardDateComponent {
         '<div class="fc-bg">' +
           '<table class="' + theme.getClass('tableGrid') + '">' +
             bgRow.renderHtml({
-              dates,
+              cells: this.props.cells[row],
               dateProfile: this.props.dateProfile,
               renderIntroHtml: this.renderProps.renderBgIntroHtml
             }) +
@@ -211,9 +240,7 @@ export default class DayGrid extends StandardDateComponent {
 
 
   getIsDayNumbersVisible() {
-    let slicer = (this.props as any).slicer as DayGridSlicer
-
-    return slicer.rowCnt > 1
+    return this.rowCnt > 1
   }
 
 
@@ -234,13 +261,12 @@ export default class DayGrid extends StandardDateComponent {
 
 
   renderNumberCellsHtml(row) {
-    let slicer = (this.props as any).slicer as DayGridSlicer
     let htmls = []
     let col
     let date
 
-    for (col = 0; col < slicer.colCnt; col++) {
-      date = slicer.getCellDate(row, col)
+    for (col = 0; col < this.colCnt; col++) {
+      date = this.props.cells[row][col].date
       htmls.push(this.renderNumberCellHtml(date))
     }
 
@@ -309,12 +335,61 @@ export default class DayGrid extends StandardDateComponent {
   ------------------------------------------------------------------------------------------------------------------*/
 
 
-  buildPositionCaches() {
-    let slicer = (this.props as any).slicer as DayGridSlicer
+  // TODO: kill
+  updateSize(viewHeight: number, isAuto: boolean, isResize: boolean) {
+    let map = this.dirtySizeMethodNames
+
+    if (isResize || map.has('renderCells')) {
+      this.buildColPositions()
+      this.buildRowPositions()
+    }
+
+    if (isResize || map.has('renderBusinessHourSegs')) {
+      this.computeBusinessHoursSize()
+    }
+
+    if (isResize || map.has('renderDateSelectionSegs') || map.has('renderEventDragSegs') || map.has('renderEventResizeSegs')) {
+      if (this.mirrorRenderer) {
+        this.mirrorRenderer.computeSizes()
+      }
+      if (this.fillRenderer) {
+        this.fillRenderer.computeSizes('highlight')
+      }
+    }
+
+    if (isResize || map.has('renderEventSegs')) {
+      this.computeEventsSize()
+    }
+
+    if (isResize || map.has('renderBusinessHourSegs')) {
+      this.assignBusinessHoursSize()
+    }
+
+    if (isResize || map.has('renderDateSelectionSegs') || map.has('renderEventDragSegs') || map.has('renderEventResizeSegs')) {
+      if (this.mirrorRenderer) {
+        this.mirrorRenderer.assignSizes()
+      }
+      if (this.fillRenderer) {
+        this.fillRenderer.assignSizes('highlight')
+      }
+    }
+
+    if (isResize || map.has('renderEventSegs')) {
+      this.assignEventsSize()
+    }
+
+    this.dirtySizeMethodNames = new Map()
+  }
+
 
+  buildColPositions() {
     this.colPositions.build()
+  }
+
+
+  buildRowPositions() {
     this.rowPositions.build()
-    this.rowPositions.bottoms[slicer.rowCnt - 1] += this.bottomCoordPadding // hack
+    this.rowPositions.bottoms[this.rowCnt - 1] += this.bottomCoordPadding // hack
   }
 
 
@@ -334,7 +409,6 @@ export default class DayGrid extends StandardDateComponent {
 
   queryHit(leftOffset, topOffset): Hit {
     let { colPositions, rowPositions, offsetTracker } = this
-    let slicer = (this.props as any).slicer as DayGridSlicer
 
     if (offsetTracker.isWithinClipping(leftOffset, topOffset)) {
       let leftOrigin = offsetTracker.computeLeft()
@@ -346,7 +420,7 @@ export default class DayGrid extends StandardDateComponent {
         return {
           component: this,
           dateSpan: {
-            range: slicer.getCellRange(row, col),
+            range: this.getCellRange(row, col),
             allDay: true
           },
           dayEl: this.getCellEl(row, col),
@@ -369,9 +443,7 @@ export default class DayGrid extends StandardDateComponent {
 
 
   getCellEl(row, col) {
-    let slicer = (this.props as any).slicer as DayGridSlicer
-
-    return this.cellEls[row * slicer.colCnt + col]
+    return this.cellEls[row * this.colCnt + col]
   }
 
 
@@ -400,11 +472,12 @@ export default class DayGrid extends StandardDateComponent {
   ------------------------------------------------------------------------------------------------------------------*/
 
 
-  renderEventResizeSegs(segs: Seg[], sourceSeg, affectedInstances) {
-    super.renderEventResizeSegs(segs, sourceSeg, affectedInstances)
-
-    this.fillRenderer.renderSegs('highlight', segs)
-    this.mirrorRenderer.renderSegs(segs, { isResizing: true, sourceSeg })
+  renderEventResizeSegs(state: EventSegUiInteractionState) {
+    if (state) {
+      this.eventRenderer.hideByHash(state.affectedInstances)
+      this.fillRenderer.renderSegs('highlight', state.segs)
+      this.mirrorRenderer.renderSegs(state.segs, { isResizing: true, sourceSeg: state.sourceSeg })
+    }
   }
 
 
@@ -472,7 +545,6 @@ export default class DayGrid extends StandardDateComponent {
   // `row` is the row number.
   // `levelLimit` is a number for the maximum (inclusive) number of levels allowed.
   limitRow(row, levelLimit) {
-    let slicer = (this.props as any).slicer as DayGridSlicer
     let rowStruct = this.eventRenderer.rowStructs[row]
     let moreNodes = [] // array of "more" <a> links and <td> DOM nodes
     let col = 0 // col #, left-to-right (not chronologically)
@@ -558,7 +630,7 @@ export default class DayGrid extends StandardDateComponent {
         }
       }
 
-      emptyCellsUntil(slicer.colCnt) // finish off the level
+      emptyCellsUntil(this.colCnt) // finish off the level
       rowStruct.moreEls = moreNodes // for easy undoing later
       rowStruct.limitedEls = limitedNodes // for easy undoing later
     }
@@ -588,13 +660,12 @@ export default class DayGrid extends StandardDateComponent {
   // Responsible for attaching click handler as well.
   renderMoreLink(row, col, hiddenSegs) {
     let { view, dateEnv } = this
-    let slicer = (this.props as any).slicer as DayGridSlicer
 
     let a = createElement('a', { className: 'fc-more' })
     a.innerText = this.getMoreLinkText(hiddenSegs.length)
     a.addEventListener('click', (ev) => {
       let clickOption = this.opt('eventLimitClick')
-      let date = slicer.getCellDate(row, col)
+      let date = this.props.cells[row][col].date
       let moreEl = ev.currentTarget as HTMLElement
       let dayEl = this.getCellEl(row, col)
       let allSegs = this.getCellSegs(row, col)
@@ -633,12 +704,11 @@ export default class DayGrid extends StandardDateComponent {
   // Reveals the popover that displays all events within a cell
   showSegPopover(row, col, moreLink: HTMLElement, segs) {
     let { calendar, view, theme } = this
-    let slicer = (this.props as any).slicer as DayGridSlicer
     let moreWrap = moreLink.parentNode as HTMLElement // the <div> wrapper around the <a>
     let topEl: HTMLElement // the element we want to match the top coordinate of
     let options
 
-    if (slicer.rowCnt === 1) {
+    if (this.rowCnt === 1) {
       topEl = view.el // will cause the popover to cover any sort of header
     } else {
       topEl = this.rowEls[row] // will align with top of row
@@ -655,7 +725,7 @@ export default class DayGrid extends StandardDateComponent {
           el
         )
         this.updateSegPopoverTile(
-          slicer.getCellDate(row, col),
+          this.props.cells[row][col].date,
           segs
         )
       },

+ 3 - 8
src/basic/DayGridEventRenderer.ts

@@ -2,7 +2,6 @@ import { createElement, removeElement, appendToElement, prependToElement } from
 import DayGrid from './DayGrid'
 import { Seg } from '../component/DateComponent'
 import SimpleDayGridEventRenderer from './SimpleDayGridEventRenderer'
-import DayGridSlicer from './DayGridSlicer'
 
 
 /* Event-rendering methods for the DayGrid class
@@ -73,8 +72,7 @@ export default class DayGridEventRenderer extends SimpleDayGridEventRenderer {
   // NOTE: modifies rowSegs
   renderSegRow(row, rowSegs) {
     let { dayGrid } = this
-    let slicer = (dayGrid.props as any).slicer as DayGridSlicer
-    let colCnt = slicer.colCnt
+    let colCnt = dayGrid.colCnt
     let segLevels = this.buildSegLevels(rowSegs) // group into sub-arrays of levels
     let levelCnt = Math.max(1, segLevels.length) // ensure at least one level
     let tbody = document.createElement('tbody')
@@ -205,11 +203,10 @@ export default class DayGridEventRenderer extends SimpleDayGridEventRenderer {
 
   // Given a flat array of segments, return an array of sub-arrays, grouped by each segment's row
   groupSegRows(segs: Seg[]) {
-    let slicer = (this.dayGrid.props as any).slicer as DayGridSlicer
     let segRows = []
     let i
 
-    for (i = 0; i < slicer.rowCnt; i++) {
+    for (i = 0; i < this.dayGrid.rowCnt; i++) {
       segRows.push([])
     }
 
@@ -223,9 +220,7 @@ export default class DayGridEventRenderer extends SimpleDayGridEventRenderer {
 
   // Computes a default `displayEventEnd` value if one is not expliclty defined
   computeDisplayEventEnd() {
-    let slicer = (this.dayGrid.props as any).slicer as DayGridSlicer
-
-    return slicer.colCnt === 1 // we'll likely have space if there's only one day
+    return this.dayGrid.colCnt === 1 // we'll likely have space if there's only one day
   }
 
 }

+ 1 - 3
src/basic/DayGridFillRenderer.ts

@@ -2,7 +2,6 @@ import { htmlToElement, createElement, appendToElement, prependToElement } from
 import FillRenderer from '../component/renderers/FillRenderer'
 import DayGrid from './DayGrid'
 import { Seg } from '../component/DateComponent'
-import DayGridSlicer from './DayGridSlicer';
 
 
 export default class DayGridFillRenderer extends FillRenderer {
@@ -35,8 +34,7 @@ export default class DayGridFillRenderer extends FillRenderer {
   // Generates the HTML needed for one row of a fill. Requires the seg's el to be rendered.
   renderFillRow(type, seg: Seg): HTMLElement {
     let { dayGrid } = this
-    let slicer = (dayGrid.props as any).slicer as DayGridSlicer
-    let colCnt = slicer.colCnt
+    let colCnt = dayGrid.colCnt
     let startCol = seg.leftCol
     let endCol = seg.rightCol + 1
     let className

+ 0 - 87
src/basic/DayGridSlicer.ts

@@ -1,87 +0,0 @@
-import { DateRange, intersectRanges } from '../datelib/date-range'
-import { Seg } from '../component/DateComponent'
-import { addDays, DateMarker } from '../datelib/marker'
-import DayTable from '../common/DayTable'
-import { EventRenderRange } from '../component/event-rendering'
-import { DateSpan } from '../structs/date-span'
-import DaySeries from '../common/DaySeries'
-
-export default class DayGridSlicer {
-
-  rowCnt: number
-  colCnt: number
-
-  private dayTable: DayTable
-  private isRtl: boolean
-
-
-  constructor(daySeries: DaySeries, isRtl: boolean, breakOnWeeks: boolean) {
-    let dayTable = new DayTable(daySeries, breakOnWeeks)
-
-    this.rowCnt = dayTable.rowCnt
-    this.colCnt = dayTable.colCnt
-    this.isRtl = isRtl
-    this.dayTable = dayTable
-  }
-
-
-  eventRangeToSegs(eventRange: EventRenderRange, component) {
-    let range = intersectRanges(eventRange.range, component.props.dateProfile.validRange)
-
-    if (range) {
-      return this.rangeToSegs(range).map(function(seg) {
-        seg.component = component
-        return seg
-      })
-    }
-
-    return []
-  }
-
-
-  dateSpanToSegs(dateSpan: DateSpan, component) {
-    let range = intersectRanges(dateSpan.range, component.props.dateProfile.validRange)
-
-    if (range) {
-      return this.rangeToSegs(range).map(function(seg) {
-        seg.component = component
-        return seg
-      })
-    }
-
-    return []
-  }
-
-
-  // Slices up the given span (unzoned start/end with other misc data) into an array of segments
-  private rangeToSegs(range: DateRange): Seg[] {
-    let colCnt = this.dayTable.colCnt
-
-    return this.dayTable.sliceRange(range)
-      .map((dayTableSeg) => {
-        return {
-          isStart: dayTableSeg.isStart,
-          isEnd: dayTableSeg.isEnd,
-          row: dayTableSeg.row,
-          leftCol: this.isRtl ? (colCnt - 1 - dayTableSeg.lastCol) : dayTableSeg.firstCol,
-          rightCol: this.isRtl ? (colCnt - 1 - dayTableSeg.firstCol) : dayTableSeg.lastCol
-        }
-      })
-  }
-
-
-  // Computes the DateMarker for the given cell
-  getCellDate(row, col): DateMarker {
-    return this.dayTable.getDate(row, col)
-  }
-
-
-  // Computes the ambiguously-timed date range for the given cell
-  getCellRange(row, col): DateRange {
-    let start = this.getCellDate(row, col)
-    let end = addDays(start, 1)
-
-    return { start, end }
-  }
-
-}

+ 18 - 18
src/basic/DayTile.ts

@@ -8,15 +8,15 @@ import { computeRect } from '../util/dom-geom'
 import { Rect, pointInsideRect } from '../util/geom'
 import { addDays, DateMarker } from '../datelib/marker'
 import { removeElement } from '../util/dom-manip'
-import { EventInteractionUiState } from '../interactions/event-interaction-state'
 import { ComponentContext } from '../component/Component'
+import { EventInstanceHash } from '../structs/event'
 
 export interface DayTileProps {
   date: DateMarker
   segs: Seg[]
   eventSelection: string
-  eventDrag: EventInteractionUiState
-  eventResize: EventInteractionUiState
+  eventDragInstances: EventInstanceHash
+  eventResizeInstances: EventInstanceHash
 }
 
 export default class DayTile extends DateComponent<DayTileProps> {
@@ -36,10 +36,10 @@ export default class DayTile extends DateComponent<DayTileProps> {
 
   render(props: DayTileProps) {
     let dateId = this.subrender('renderFrame', [ props.date ])
-    let evId = this.subrender('renderFgEventSegs', [ props.segs, dateId ], 'unrenderEvents')
+    let evId = this.subrender('renderEventSegs', [ props.segs, dateId ], 'unrenderEvents')
     this.subrender('renderEventSelection', [ props.eventSelection, evId ], 'unrenderEventSelection')
-    this.subrender('renderEventDragState', [ props.eventDrag, dateId ], 'unrenderEventDragState')
-    this.subrender('renderEventResizeState', [ props.eventResize, dateId ], 'unrenderEventResizeState')
+    this.subrender('renderEventDrag', [ props.eventDragInstances, dateId ], 'unrenderEventDrag')
+    this.subrender('renderEventResize', [ props.eventResizeInstances, dateId ], 'unrenderEventResize')
   }
 
   renderFrame(date: DateMarker) {
@@ -65,27 +65,27 @@ export default class DayTile extends DateComponent<DayTileProps> {
     this.segContainerEl = this.el.querySelector('.fc-event-container')
   }
 
-  renderEventDragState(state: EventInteractionUiState) {
-    if (state) {
-      this.eventRenderer.hideByHash(state.affectedEvents.instances)
+  renderEventDrag(affectedInstances: EventInstanceHash) {
+    if (affectedInstances) {
+      this.eventRenderer.hideByHash(affectedInstances)
     }
   }
 
-  unrenderEventDragState(state: EventInteractionUiState) {
-    if (state) {
-      this.eventRenderer.showByHash(state.affectedEvents.instances)
+  unrenderEventDrag(affectedInstances: EventInstanceHash) {
+    if (affectedInstances) {
+      this.eventRenderer.showByHash(affectedInstances)
     }
   }
 
-  renderEventResizeState(state: EventInteractionUiState) {
-    if (state) {
-      this.eventRenderer.hideByHash(state.affectedEvents.instances)
+  renderEventResize(affectedInstances: EventInstanceHash) {
+    if (affectedInstances) {
+      this.eventRenderer.hideByHash(affectedInstances)
     }
   }
 
-  unrenderEventResizeState(state: EventInteractionUiState) {
-    if (state) {
-      this.eventRenderer.showByHash(state.affectedEvents.instances)
+  unrenderEventResize(affectedInstances: EventInstanceHash) {
+    if (affectedInstances) {
+      this.eventRenderer.showByHash(affectedInstances)
     }
   }
 

+ 1 - 3
src/basic/MonthView.ts

@@ -2,7 +2,6 @@ import { distributeHeight } from '../util/misc'
 import BasicView from './BasicView'
 import MonthViewDateProfileGenerator from './MonthViewDateProfileGenerator'
 import { DateMarker } from '../datelib/marker'
-import DayGridSlicer from './DayGridSlicer'
 
 
 /* A month view with day cells running in rows (one-per-week) and columns
@@ -12,11 +11,10 @@ export default class MonthView extends BasicView {
 
   // Overrides the default BasicView behavior to have special multi-week auto-height logic
   setGridHeight(height, isAuto) {
-    let slicer = (this.dayGrid.props as any).slicer as DayGridSlicer
 
     // if auto, make the height of each row the height that it would be if there were 6 weeks
     if (isAuto) {
-      height *= slicer.rowCnt / 6
+      height *= this.dayTable.rowCnt / 6
     }
 
     distributeHeight(this.dayGrid.rowEls, height, !isAuto) // if auto, don't compensate for height-hogging rows

+ 137 - 0
src/basic/SimpleDayGrid.ts

@@ -0,0 +1,137 @@
+import DayGrid, { DayGridSeg } from './DayGrid'
+import { DateProfile } from '../DateProfileGenerator'
+import { EventStore } from '../structs/event-store'
+import { EventUiHash, sliceEventStore, EventRenderRange } from '../component/event-rendering'
+import { DateSpan, fabricateEventRange } from '../structs/date-span'
+import { EventInteractionUiState } from '../interactions/event-interaction-state'
+import DayTable from '../common/DayTable'
+import { Duration } from '../datelib/duration'
+import reselector from '../util/reselector'
+import { sliceBusinessHours } from '../structs/business-hours'
+import Component from '../component/Component'
+
+export interface SimpleDayGridProps {
+  dateProfile: DateProfile | null
+  dayTable: DayTable
+  nextDayThreshold: Duration
+  businessHours: EventStore
+  eventStore: EventStore
+  eventUis: EventUiHash
+  dateSelection: DateSpan | null
+  eventSelection: string
+  eventDrag: EventInteractionUiState | null
+  eventResize: EventInteractionUiState | null
+  isRigid: boolean
+}
+
+export default class SimpleDayGrid extends Component<SimpleDayGridProps> {
+
+  dayGrid: DayGrid
+
+  businessHoursToSegs = reselector(businessHoursToSegs)
+  eventStoreToSegs = reselector(eventStoreToSegs)
+  dateSpanToSegs = reselector(dateSpanToSegs)
+  buildEventDrag = reselector(buildSegInteraction)
+  buildEventResize = reselector(buildSegInteraction)
+
+  constructor(context, dayGrid: DayGrid) {
+    super(context)
+
+    this.dayGrid = dayGrid
+  }
+
+  render(props: SimpleDayGridProps) {
+    let { dayGrid } = this
+    let { dateProfile, dayTable, nextDayThreshold } = props
+
+    dayGrid.receiveProps({
+      dateProfile,
+      cells: dayTable.cells,
+      businessHourSegs: this.businessHoursToSegs(props.businessHours, dateProfile, dayTable, nextDayThreshold, dayGrid),
+      eventSegs: this.eventStoreToSegs(props.eventStore, props.eventUis, dateProfile, dayTable, nextDayThreshold, dayGrid),
+      dateSelectionSegs: this.dateSpanToSegs(props.dateSelection, dayTable, dayGrid),
+      eventSelection: props.eventSelection,
+      eventDrag: this.buildEventDrag(props.eventDrag, dateProfile, dayTable, nextDayThreshold, dayGrid),
+      eventResize: this.buildEventResize(props.eventResize, dateProfile, dayTable, nextDayThreshold, dayGrid),
+      isRigid: props.isRigid
+    })
+  }
+
+}
+
+function eventStoreToSegs(eventStore: EventStore, eventUis: EventUiHash, dateProfile: DateProfile, dayTable: DayTable, nextDayThreshold: Duration, dayGrid: DayGrid) {
+  return eventRangesToSegs(
+    sliceEventStore(eventStore, eventUis, dateProfile.activeRange, nextDayThreshold),
+    dayTable,
+    dayGrid
+  )
+}
+
+function businessHoursToSegs(businessHours: EventStore, dateProfile: DateProfile, dayTable: DayTable, nextDayThreshold: Duration, dayGrid: DayGrid) {
+  return eventRangesToSegs(
+    sliceBusinessHours(businessHours, dateProfile.activeRange, nextDayThreshold, dayGrid.calendar),
+    dayTable,
+    dayGrid
+  )
+}
+
+function dateSpanToSegs(dateSpan: DateSpan, dayTable: DayTable, dayGrid: DayGrid) {
+  return dateSpan ? sliceDateSpan(dateSpan, dayTable, dayGrid) : null
+}
+
+function buildSegInteraction(interaction: EventInteractionUiState, dateProfile: DateProfile, dayTable: DayTable, nextDayThreshold: Duration, dayGrid: DayGrid) {
+  if (!interaction) {
+    return null
+  }
+
+  return {
+    segs: eventRangesToSegs(
+      sliceEventStore(interaction.mutatedEvents, interaction.eventUis, dateProfile.activeRange, nextDayThreshold),
+      dayTable,
+      dayGrid
+    ),
+    affectedInstances: interaction.affectedEvents.instances,
+    isEvent: interaction.isEvent,
+    sourceSeg: interaction.origSeg
+  }
+}
+
+function eventRangesToSegs(eventRanges: EventRenderRange[], dayTable: DayTable, dayGrid: DayGrid): DayGridSeg[] {
+  let segs = []
+
+  for (let eventRange of eventRanges) {
+    segs.push(...eventRangeToSegs(eventRange, dayTable, dayGrid))
+  }
+
+  return segs
+}
+
+function eventRangeToSegs(eventRange: EventRenderRange, dayTable: DayTable, dayGrid: DayGrid): DayGridSeg[] {
+  return dayTable.sliceRange(eventRange.range).map(function(seg) {
+    return {
+      eventRange,
+      component: dayGrid,
+      isStart: seg.isStart,
+      isEnd: seg.isEnd,
+      row: seg.row,
+      leftCol: dayGrid.isRtl ? (dayTable.colCnt - 1 - seg.lastCol) : seg.firstCol,
+      rightCol: dayGrid.isRtl ? (dayTable.colCnt - 1 - seg.firstCol) : seg.lastCol
+    }
+  })
+}
+
+function sliceDateSpan(dateSpan: DateSpan, dayTable: DayTable, dayGrid: DayGrid): DayGridSeg[] {
+  let eventRange = fabricateEventRange(dateSpan)
+
+  return dayTable.sliceRange(dateSpan.range).map(function(seg) {
+    return {
+      component: dayGrid,
+      eventRange,
+      isStart: seg.isStart,
+      isEnd: seg.isEnd,
+      row: seg.row,
+      leftCol: dayGrid.isRtl ? (dayTable.colCnt - 1 - seg.lastCol) : seg.firstCol,
+      rightCol: dayGrid.isRtl ? (dayTable.colCnt - 1 - seg.firstCol) : seg.lastCol
+    }
+  })
+}

+ 43 - 2
src/common/DayTable.ts

@@ -1,5 +1,6 @@
 import DaySeries from './DaySeries'
 import { DateRange } from '../datelib/date-range'
+import { DateMarker } from '../datelib/marker'
 
 export interface DayTableSeg {
   row: number
@@ -9,10 +10,18 @@ export interface DayTableSeg {
   isEnd: boolean
 }
 
+export interface DayTableCell {
+  date: DateMarker
+  htmlAttrs?: string
+}
+
 export default class DayTable {
 
   rowCnt: number
   colCnt: number
+  cells: DayTableCell[][]
+  headerDates: DateMarker[]
+
   private daySeries: DaySeries
 
   constructor(daySeries: DaySeries, breakOnWeeks: boolean) {
@@ -38,10 +47,42 @@ export default class DayTable {
     this.rowCnt = rowCnt
     this.colCnt = daysPerRow
     this.daySeries = daySeries
+    this.cells = this.buildCells()
+    this.headerDates = this.buildHeaderDates()
   }
 
-  getDate(row: number, col: number) {
-    return this.daySeries.dates[row * this.colCnt + col]
+  private buildCells() {
+    let rows = []
+
+    for (let row = 0; row < this.rowCnt; row++) {
+      let cells = []
+
+      for (let col = 0; col < this.colCnt; col++) {
+        cells.push(
+          this.buildCell(row, col)
+        )
+      }
+
+      rows.push(cells)
+    }
+
+    return rows
+  }
+
+  private buildCell(row, col) {
+    return {
+      date: this.daySeries.dates[row * this.colCnt + col]
+    }
+  }
+
+  private buildHeaderDates() {
+    let dates = []
+
+    for (let col = 0; col < this.colCnt; col++) {
+      dates.push(this.cells[0][col].date)
+    }
+
+    return dates
   }
 
   sliceRange(range: DateRange): DayTableSeg[] {

+ 78 - 28
src/component/DateComponent.ts

@@ -1,8 +1,7 @@
 import Component, { ComponentContext } from './Component'
 import { EventStore } from '../structs/event-store'
-import { EventRenderRange } from './event-rendering'
+import { EventRenderRange, hasBgRendering } from './event-rendering'
 import { DateSpan } from '../structs/date-span'
-import { EventInteractionUiState } from '../interactions/event-interaction-state'
 import { EventInstanceHash } from '../structs/event'
 import { rangeContainsRange } from '../datelib/date-range'
 import { Hit } from '../interactions/HitDragging'
@@ -18,7 +17,7 @@ export type DateComponentHash = { [uid: string]: DateComponent<any> }
 // NOTE: for fg-events, eventRange.range is NOT sliced,
 // thus, we need isStart/isEnd
 export interface Seg {
-  component?: DateComponent<any>
+  component: DateComponent<any>
   isStart: boolean
   isEnd: boolean
   eventRange?: EventRenderRange
@@ -26,6 +25,13 @@ export interface Seg {
   [otherProp: string]: any
 }
 
+export interface EventSegUiInteractionState {
+  affectedInstances: EventInstanceHash
+  segs: Seg[]
+  isEvent: boolean
+  sourceSeg: any
+}
+
 /*
 PURPOSES:
 - hook up to fg, fill, and mirror renderers
@@ -107,12 +113,18 @@ export default class DateComponent<PropsType> extends Component<PropsType> {
   // ---------------------------------------------------------------------------------------------------------------
 
   renderDateSelectionSegs(segs: Seg[]) {
-    if (this.fillRenderer) {
-      this.fillRenderer.renderSegs('highlight', segs)
+    if (segs) {
+      if (this.fillRenderer) {
+        this.fillRenderer.renderSegs('highlight', segs)
+      }
     }
   }
 
-  unrenderDateSelection(selection: DateSpan) {
+  unrenderDateSelection() {
+    if (this.mirrorRenderer) {
+      this.mirrorRenderer.unrender()
+    }
+
     if (this.fillRenderer) {
       this.fillRenderer.unrender('highlight')
     }
@@ -122,6 +134,22 @@ export default class DateComponent<PropsType> extends Component<PropsType> {
   // Events
   // -----------------------------------------------------------------------------------------------------------------
 
+  renderEventSegs(segs: Seg[]) {
+    let bgSegs = []
+    let fgSegs = []
+
+    for (let seg of segs) {
+      if (hasBgRendering(seg.eventRange.ui)) {
+        bgSegs.push(seg)
+      } else {
+        fgSegs.push(seg)
+      }
+    }
+
+    this.renderFgEventSegs(fgSegs)
+    this.renderBgEventSegs(bgSegs)
+  }
+
   renderFgEventSegs(segs: Seg[]) {
     if (this.eventRenderer) {
       this.eventRenderer.renderSegs(segs)
@@ -135,10 +163,17 @@ export default class DateComponent<PropsType> extends Component<PropsType> {
   }
 
   unrenderEvents() {
+    this.unrenderFgEventSegs()
+    this.unrenderBgEventSegs()
+  }
+
+  unrenderFgEventSegs() {
     if (this.eventRenderer) {
       this.eventRenderer.unrender()
     }
+  }
 
+  unrenderBgEventSegs() {
     if (this.fillRenderer) {
       this.fillRenderer.unrender('bgEvent')
     }
@@ -185,33 +220,42 @@ export default class DateComponent<PropsType> extends Component<PropsType> {
   // Event Drag-n-Drop Rendering (for both events and external elements)
   // ---------------------------------------------------------------------------------------------------------------
 
-  renderEventDragSegs(segs: Seg[], isEvent: boolean, sourceSeg: Seg | null, affectedInstances: EventInstanceHash) {
+  renderEventDragSegs(state: EventSegUiInteractionState) {
+    if (state) {
+      let { isEvent, segs, sourceSeg } = state
 
-    if (this.eventRenderer) {
-      this.eventRenderer.hideByHash(affectedInstances)
-    }
+      if (this.eventRenderer) {
+        this.eventRenderer.hideByHash(state.affectedInstances)
+      }
 
-    // if the user is dragging something that is considered an event with real event data,
-    // and this component likes to do drag mirrors OR the component where the seg came from
-    // likes to do drag mirrors, then render a drag mirror.
-    if (isEvent && (this.doesDragMirror || sourceSeg && sourceSeg.component.doesDragMirror)) {
-      if (this.mirrorRenderer) {
-        this.mirrorRenderer.renderSegs(segs, { isDragging: true, sourceSeg })
+      // if the user is dragging something that is considered an event with real event data,
+      // and this component likes to do drag mirrors OR the component where the seg came from
+      // likes to do drag mirrors, then render a drag mirror.
+      if (isEvent && (this.doesDragMirror || sourceSeg && sourceSeg.component.doesDragMirror)) {
+        if (this.mirrorRenderer) {
+          this.mirrorRenderer.renderSegs(segs, { isDragging: true, sourceSeg })
+        }
       }
-    }
 
-    // if it would be impossible to render a drag mirror OR this component likes to render
-    // highlights, then render a highlight.
-    if (!isEvent || this.doesDragHighlight) {
-      if (this.fillRenderer) {
-        this.fillRenderer.renderSegs('highlight', segs)
+      // if it would be impossible to render a drag mirror OR this component likes to render
+      // highlights, then render a highlight.
+      if (!isEvent || this.doesDragHighlight) {
+        if (this.fillRenderer) {
+          this.fillRenderer.renderSegs('highlight', segs)
+        }
       }
     }
   }
 
-  unrenderEventDrag(state: EventInteractionUiState) {
+  unrenderEventDragSegs(state: EventSegUiInteractionState) {
+    if (state) {
+      this.unrenderEventDrag(state.affectedInstances)
+    }
+  }
+
+  unrenderEventDrag(affectedInstances: EventInstanceHash) {
     if (this.eventRenderer) {
-      this.eventRenderer.showByHash(state.affectedEvents.instances)
+      this.eventRenderer.showByHash(affectedInstances)
     }
 
     if (this.mirrorRenderer) {
@@ -227,17 +271,23 @@ export default class DateComponent<PropsType> extends Component<PropsType> {
   // Event Resizing
   // ---------------------------------------------------------------------------------------------------------------
 
-  renderEventResizeSegs(segs: Seg[], sourceSeg, affectedInstances: EventInstanceHash) {
+  renderEventResizeSegs(state: EventSegUiInteractionState) {
     if (this.eventRenderer) {
-      this.eventRenderer.hideByHash(affectedInstances)
+      this.eventRenderer.hideByHash(state.affectedInstances)
     }
 
     // subclasses can override and do something with segs
   }
 
-  unrenderEventResize(state: EventInteractionUiState) {
+  unrenderEventResizeSegs(state: EventSegUiInteractionState) {
+    if (state) {
+      this.unrenderEventResize(state.affectedInstances)
+    }
+  }
+
+  unrenderEventResize(affectedInstances: EventInstanceHash) {
     if (this.eventRenderer) {
-      this.eventRenderer.showByHash(state.affectedEvents.instances)
+      this.eventRenderer.showByHash(affectedInstances)
     }
 
     if (this.mirrorRenderer) {

+ 0 - 348
src/component/StandardDateComponent.ts

@@ -1,348 +0,0 @@
-import DateComponent, { Seg } from './DateComponent'
-import { DateProfile } from '../DateProfileGenerator'
-import { EventStore, expandRecurring } from '../structs/event-store'
-import { EventUiHash, computeEventDefUis, EventRenderRange, hasBgRendering, computeEventDefUi, sliceEventStore } from './event-rendering'
-import { DateSpan } from '../structs/date-span'
-import { EventInteractionUiState } from '../interactions/event-interaction-state'
-import { Duration, createDuration } from '../datelib/duration'
-import { ComponentContext } from './Component';
-import { parseEventDef, createEventInstance } from '../structs/event'
-
-export interface StandardDateComponentProps {
-  dateProfile: DateProfile | null
-  businessHours: EventStore
-  eventStore: EventStore
-  eventUis: EventUiHash
-  dateSelection: DateSpan | null
-  eventSelection: string
-  eventDrag: EventInteractionUiState | null
-  eventResize: EventInteractionUiState | null
-}
-
-/*
-PURPOSES:
-- accepts a common form of props
-- slices prop data into segs, for StandardDateComponent's renderers
-*/
-export default class StandardDateComponent extends DateComponent<StandardDateComponentProps> {
-
-  slicingType: 'timed' | 'all-day' | null = null
-
-  // derived from options
-  nextDayThreshold: Duration
-
-
-  constructor(context: ComponentContext, el: HTMLElement) {
-    super(context, el)
-
-    this.nextDayThreshold = createDuration(this.opt('nextDayThreshold'))
-  }
-
-
-  eventRangeToSegs(eventRange: EventRenderRange): Seg[] {
-    let slicer = (this.props as any).slicer
-
-    if (slicer) {
-      return slicer.eventRangeToSegs(eventRange, this)
-    }
-
-    return []
-  }
-
-
-  dateSpanToSegs(selection: DateSpan): Seg[] {
-    let slicer = (this.props as any).slicer
-
-    if (slicer) {
-      return slicer.dateSpanToSegs(selection, this)
-    }
-
-    return []
-  }
-
-
-  render(props: StandardDateComponentProps) {
-    this.subrender('afterSkeletonRender', [], 'beforeSkeletonUnrender', true)
-    let dateId = this.subrender('_renderDates', [ props.dateProfile ], '_unrenderDates', true)
-    this.subrender('renderBusinessHours', [ props.businessHours, props.dateProfile, dateId ], 'unrenderBusinessHours', true)
-    this.subrender('renderDateSelectionState', [ props.dateSelection, dateId ], 'unrenderDateSelectionState', true)
-    let evId = this.subrender('renderEvents', [ props.eventStore, props.eventUis, dateId ], 'unrenderEvents', true)
-    this.subrender('renderEventSelection', [ props.eventSelection, evId ], 'unrenderEventSelection', true)
-    this.subrender('renderEventDragState', [ props.eventDrag, dateId ], 'unrenderEventDragState', true)
-    this.subrender('renderEventResizeState', [ props.eventResize, dateId ], 'unrenderEventResizeState', true)
-  }
-
-
-  // Sizing
-  // -----------------------------------------------------------------------------------------------------------------
-
-  updateSize(viewHeight: number, isAuto: boolean, isResize: boolean) {
-    let map = this.dirtySizeMethodNames
-
-    if (isResize || map.has('afterSkeletonRender') || map.has('_renderDates') || map.has('renderEvents')) {
-      // sort of the catch-all sizing
-      // anything that might cause dimension changes
-      this.updateBaseSize(viewHeight, isAuto, isResize)
-      this.buildPositionCaches()
-    }
-
-    if (isResize || map.has('renderBusinessHours')) {
-      this.computeBusinessHoursSize()
-    }
-
-    if (isResize || map.has('renderDateSelectionState') || map.has('renderEventDragState') || map.has('renderEventResizeState')) {
-      if (this.mirrorRenderer) {
-        this.mirrorRenderer.computeSizes()
-      }
-      if (this.fillRenderer) {
-        this.fillRenderer.computeSizes('highlight')
-      }
-    }
-
-    if (isResize || map.has('renderEvents')) {
-      this.computeEventsSize()
-    }
-
-    if (isResize || map.has('renderBusinessHours')) {
-      this.assignBusinessHoursSize()
-    }
-
-    if (isResize || map.has('renderDateSelectionState') || map.has('renderEventDragState') || map.has('renderEventResizeState')) {
-      if (this.mirrorRenderer) {
-        this.mirrorRenderer.assignSizes()
-      }
-      if (this.fillRenderer) {
-        this.fillRenderer.assignSizes('highlight')
-      }
-    }
-
-    if (isResize || map.has('renderEvents')) {
-      this.assignEventsSize()
-    }
-
-    this.dirtySizeMethodNames = new Map()
-  }
-
-  updateBaseSize(viewHeight: number, isAuto: boolean, isResize: boolean) {
-  }
-
-  buildPositionCaches() {
-  }
-
-
-  // Skeleton
-  // -----------------------------------------------------------------------------------------------------------------
-
-
-  afterSkeletonRender() {
-  }
-
-
-  beforeSkeletonUnrender() {
-  }
-
-
-  // Date
-  // -----------------------------------------------------------------------------------------------------------------
-
-  _renderDates(dateProfile: DateProfile) {
-    this.renderDates(dateProfile)
-    this.afterDatesRender()
-  }
-
-  _unrenderDates() {
-    this.beforeDatesUnrender()
-    this.unrenderDates()
-  }
-
-  renderDates(dateProfile: DateProfile) {
-  }
-
-  unrenderDates() {
-  }
-
-  afterDatesRender() {
-  }
-
-  beforeDatesUnrender() {
-  }
-
-
-  // Business Hours
-  // ---------------------------------------------------------------------------------------------------------------
-
-  renderBusinessHours(businessHours: EventStore, dateProfile: DateProfile) {
-    let expandedStore = expandRecurring(businessHours, dateProfile.activeRange, this.calendar)
-
-    this.renderBusinessHourRanges(
-      this.eventStoreToRanges(
-        expandedStore,
-        computeEventDefUis(expandedStore.defs, {}, {})
-      )
-    )
-  }
-
-  renderBusinessHourRanges(eventRanges: EventRenderRange[]) {
-    this.renderBusinessHourSegs(this.eventRangesToSegs(eventRanges))
-  }
-
-
-  // Date Selection
-  // ---------------------------------------------------------------------------------------------------------------
-
-  renderDateSelectionState(selection: DateSpan | null) {
-    if (selection) {
-      this.renderDateSelection(selection)
-    }
-  }
-
-  unrenderDateSelectionState(selection: DateSpan | null) {
-    if (selection) {
-      this.unrenderDateSelection(selection)
-    }
-  }
-
-  renderDateSelection(selection: DateSpan) {
-    this.renderDateSelectionSegs(this.selectionToSegs(selection))
-  }
-
-
-  // Events
-  // -----------------------------------------------------------------------------------------------------------------
-
-  renderEvents(eventStore: EventStore, eventUis: EventUiHash) {
-    this.renderEventRanges(
-      this.eventStoreToRanges(eventStore, eventUis)
-    )
-  }
-
-  renderEventRanges(eventRanges: EventRenderRange[]) {
-    let bgRanges = []
-    let fgRanges = []
-
-    for (let eventRange of eventRanges) {
-      if (hasBgRendering(eventRange.ui)) {
-        bgRanges.push(eventRange)
-      } else {
-        fgRanges.push(eventRange)
-      }
-    }
-
-    this.renderFgEventSegs(
-      this.eventRangesToSegs(fgRanges)
-    )
-
-    this.renderBgEventSegs(
-      this.eventRangesToSegs(bgRanges)
-    )
-  }
-
-
-  // Event Drag-n-Drop Rendering (for both events and external elements)
-  // ---------------------------------------------------------------------------------------------------------------
-
-  renderEventDragState(state: EventInteractionUiState | null) {
-    if (state) {
-      this.renderEventDrag(state)
-    }
-  }
-
-  unrenderEventDragState(state: EventInteractionUiState | null) {
-    if (state) {
-      this.unrenderEventDrag(state)
-    }
-  }
-
-  renderEventDrag(state: EventInteractionUiState) {
-    let segs = this.eventRangesToSegs(
-      this.eventStoreToRanges(state.mutatedEvents, state.eventUis)
-    )
-
-    this.renderEventDragSegs(segs, state.isEvent, state.origSeg, state.affectedEvents.instances)
-  }
-
-
-  // Event Resizing
-  // ---------------------------------------------------------------------------------------------------------------
-
-  renderEventResizeState(state: EventInteractionUiState | null) {
-    if (state) {
-      this.renderEventResize(state)
-    }
-  }
-
-  unrenderEventResizeState(state: EventInteractionUiState | null) {
-    if (state) {
-      this.unrenderEventResize(state)
-    }
-  }
-
-  renderEventResize(state: EventInteractionUiState) {
-    let segs = this.eventRangesToSegs(
-      this.eventStoreToRanges(state.mutatedEvents, state.eventUis)
-    )
-
-    this.renderEventResizeSegs(segs, state.origSeg, state.affectedEvents.instances)
-  }
-
-
-  // Converting selection/eventRanges -> segs
-  // ---------------------------------------------------------------------------------------------------------------
-
-  eventStoreToRanges(eventStore: EventStore, eventUis: EventUiHash): EventRenderRange[] {
-    return sliceEventStore(
-      eventStore,
-      eventUis,
-      this.props.dateProfile.activeRange,
-      this.slicingType === 'all-day' ? this.nextDayThreshold : null
-    )
-  }
-
-  eventRangesToSegs(eventRenderRanges: EventRenderRange[]): Seg[] {
-    let allSegs: Seg[] = []
-
-    for (let eventRenderRange of eventRenderRanges) {
-      let segs = this.eventRangeToSegs(eventRenderRange)
-
-      for (let seg of segs) {
-        seg.eventRange = eventRenderRange
-        seg.isStart = seg.isStart && eventRenderRange.isStart
-        seg.isEnd = seg.isEnd && eventRenderRange.isEnd
-
-        allSegs.push(seg)
-      }
-    }
-
-    return allSegs
-  }
-
-  selectionToSegs(selection: DateSpan): Seg[] {
-    let segs = this.dateSpanToSegs(selection)
-
-    // fabricate an eventRange. important for mirror
-    // TODO: make a separate utility for this?
-    let def = parseEventDef(
-      { editable: false },
-      '', // sourceId
-      selection.allDay,
-      true, // hasEnd
-      this.calendar
-    )
-
-    let eventRange = {
-      def,
-      ui: computeEventDefUi(def, {}, {}),
-      instance: createEventInstance(def.defId, selection.range),
-      range: selection.range,
-      isStart: true,
-      isEnd: true
-    }
-
-    for (let seg of segs) {
-      seg.eventRange = eventRange
-    }
-
-    return segs
-  }
-
-}

+ 0 - 3
src/exports.ts

@@ -89,7 +89,6 @@ export { default as ScrollComponent, ScrollbarWidths } from './common/ScrollComp
 export { default as Theme } from './theme/Theme'
 export { default as Component, ComponentContext } from './component/Component'
 export { default as DateComponent, Seg } from './component/DateComponent'
-export { default as StandardDateComponent, StandardDateComponentProps } from './component/StandardDateComponent'
 export { default as Calendar } from './Calendar'
 export { default as View } from './View'
 export { defineView, getViewConfig } from './ViewRegistry'
@@ -98,9 +97,7 @@ export { default as FillRenderer } from './component/renderers/FillRenderer'
 export { default as AgendaView } from './agenda/AgendaView'
 export { default as AbstractAgendaView} from './agenda/AbstractAgendaView'
 export { default as TimeGrid } from './agenda/TimeGrid'
-export { default as TimeGridSlicer } from './agenda/TimeGridSlicer'
 export { default as DayGrid } from './basic/DayGrid'
-export { default as DayGridSlicer } from './basic/DayGridSlicer'
 export { default as BasicView } from './basic/BasicView'
 export { default as MonthView } from './basic/MonthView'
 export { default as ListView } from './list/ListView'

+ 30 - 4
src/list/ListView.ts

@@ -11,7 +11,8 @@ import DateProfileGenerator, { DateProfile } from '../DateProfileGenerator'
 import { buildGotoAnchorHtml } from '../component/date-rendering'
 import { ComponentContext } from '../component/Component'
 import { ViewSpec } from '../structs/view-spec'
-import { EventRenderRange } from '../component/event-rendering';
+import { EventRenderRange, EventUiHash, sliceEventStore } from '../component/event-rendering'
+import { EventStore } from 'src/structs/event-store';
 
 /*
 Responsible for the scroller, and forwarding event-related actions into the "grid".
@@ -29,7 +30,6 @@ export default class ListView extends View {
     super(context, viewSpec, dateProfileGenerator, parentEl)
 
     this.eventRenderer = new ListEventRenderer(this)
-    this.slicingType = 'all-day'
 
     this.el.classList.add('fc-list-view')
 
@@ -93,11 +93,36 @@ export default class ListView extends View {
     this.dayDates = dayDates
     this.dayRanges = dayRanges
 
-    // all real rendering happens in FgEventRenderer
+    // all real rendering happens in ListEventRenderer
+  }
+
+
+  renderEvents(eventStore: EventStore, eventUis: EventUiHash) {
+    this.renderEventSegs(
+      this.eventRangesToSegs(
+        sliceEventStore(
+          eventStore,
+          eventUis,
+          this.props.dateProfile.activeRange,
+          this.nextDayThreshold
+        )
+      )
+    )
+  }
+
+
+  eventRangesToSegs(eventRanges: EventRenderRange[]) {
+    let segs = []
+
+    // TODO: util for doing this
+    for (let eventRange of eventRanges) {
+      segs.push(...this.eventRangeToSegs(eventRange))
+    }
+
+    return segs
   }
 
 
-  // slices by day
   eventRangeToSegs(eventRange: EventRenderRange) {
     let range = eventRange.range
     let allDay = eventRange.def.allDay
@@ -114,6 +139,7 @@ export default class ListView extends View {
       if (segRange) {
         seg = {
           component: this,
+          eventRange,
           start: segRange.start,
           end: segRange.end,
           isStart: segRange.start.valueOf() === range.start.valueOf(),

+ 16 - 1
src/structs/business-hours.ts

@@ -1,7 +1,10 @@
 import Calendar from '../Calendar'
 import { assignTo } from '../util/object'
 import { EventInput } from './event'
-import { EventStore, parseEvents } from './event-store'
+import { EventStore, parseEvents, expandRecurring } from './event-store'
+import { DateRange } from '../datelib/date-range'
+import { Duration } from '../datelib/duration'
+import { EventRenderRange, sliceEventStore, computeEventDefUis } from '../component/event-rendering'
 
 /*
 Utils for converting raw business hour input into an EventStore,
@@ -49,3 +52,15 @@ function refineInputs(input: BusinessHoursInput): EventInput[] {
 
   return rawDefs
 }
+
+
+export function sliceBusinessHours(businessHours: EventStore, range: DateRange, nextDayThreshold: Duration, calendar: Calendar): EventRenderRange[] {
+  let expandedStore = expandRecurring(businessHours, range, calendar)
+
+  return sliceEventStore(
+    expandedStore,
+    computeEventDefUis(expandedStore.defs, {}, {}),
+    range,
+    nextDayThreshold
+  )
+}

+ 21 - 0
src/structs/date-span.ts

@@ -3,6 +3,8 @@ import { DateInput, DateEnv } from '../datelib/env'
 import { refineProps } from '../util/misc'
 import { Duration } from '../datelib/duration'
 import { assignTo } from '../util/object'
+import { parseEventDef, createEventInstance } from './event';
+import { computeEventDefUi, EventRenderRange } from '../component/event-rendering';
 
 /*
 A data-structure for a date-range that will be visually displayed.
@@ -142,3 +144,22 @@ export function buildDateSpanApi(span: DateSpan, dateEnv: DateEnv): DateSpanApi
 
   return props
 }
+
+export function fabricateEventRange(dateSpan: DateSpan): EventRenderRange {
+  let def = parseEventDef(
+    { editable: false },
+    '', // sourceId
+    dateSpan.allDay,
+    true, // hasEnd
+    this.calendar
+  )
+
+  return {
+    def,
+    ui: computeEventDefUi(def, {}, {}),
+    instance: createEventInstance(def.defId, dateSpan.range),
+    range: dateSpan.range,
+    isStart: true,
+    isEnd: true
+  }
+}