Explorar o código

fix more link causing infinite render loop

Adam Shaw %!s(int64=5) %!d(string=hai) anos
pai
achega
2ff85e2ce6

+ 2 - 2
packages/daygrid/src/TableCell.tsx

@@ -39,7 +39,7 @@ export interface TableCellProps extends TableCellModel {
   buildMoreLinkText: (num: number) => string
   onMoreClick?: (arg: MoreLinkArg) => void
   allFgSegs: TableSeg[] // for more-popover. includes segs that aren't rooted in this cell but that pass over it
-  segIsNoDisplay: { [instanceId: string]: boolean } // for more-popover
+  segIsHidden: { [instanceId: string]: boolean } // for more-popover. TODO: rename to be about selected instances
 }
 
 export interface TableCellModel { // combine with DayTableCell?
@@ -154,7 +154,7 @@ export default class TableCell extends DateComponent<TableCellProps> {
     if (props.onMoreClick) {
       let allSegs = resliceDaySegs(props.allFgSegs, props.date)
       let hiddenSegs = allSegs.filter(
-        (seg: TableSeg) => props.segIsNoDisplay[seg.eventRange.instance.instanceId]
+        (seg: TableSeg) => props.segIsHidden[seg.eventRange.instance.instanceId]
       )
 
       props.onMoreClick({

+ 65 - 50
packages/daygrid/src/TableRow.tsx

@@ -13,7 +13,8 @@ import {
   DateProfile,
   Fragment,
   BgEvent,
-  renderFill
+  renderFill,
+  isPropsEqual
 } from '@fullcalendar/core'
 import TableSeg, { splitSegsByFirstCol } from './TableSeg'
 import TableCell, { TableCellModel, MoreLinkArg } from './TableCell'
@@ -60,6 +61,13 @@ export default class TableRow extends DateComponent<TableRowProps, TableRowState
   private cellContentElRefs = new RefMap<HTMLDivElement>()
   private segHarnessRefs = new RefMap<HTMLDivElement>()
 
+  state: TableRowState = {
+    cellInnerPositions: null,
+    cellContentPositions: null,
+    maxContentHeight: null,
+    segHeights: {}
+  }
+
 
   render(props: TableRowProps, state: TableRowState, context: ComponentContext) {
     let colCnt = props.cells.length
@@ -69,7 +77,7 @@ export default class TableRow extends DateComponent<TableRowProps, TableRowState
     let highlightSegsByCol = splitSegsByFirstCol(this.getHighlightSegs(), colCnt)
     let mirrorSegsByCol = splitSegsByFirstCol(this.getMirrorSegs(), colCnt)
 
-    let { paddingBottoms, finalSegsByCol, segsByFirstCol, segIsNoDisplay, segTops, segMarginTops, moreCnts, moreTops } = computeFgSegPlacement(
+    let { paddingBottoms, finalSegsByCol, segsByFirstCol, segIsHidden, segTops, segMarginTops, moreCnts, moreTops } = computeFgSegPlacement(
       props.fgEventSegs,
       props.dayMaxEvents,
       props.dayMaxEventRows,
@@ -79,7 +87,7 @@ export default class TableRow extends DateComponent<TableRowProps, TableRowState
       context.eventOrderSpecs
     )
 
-    let interactionAffectedInstances = // TODO: messy way to compute this
+    let selectedInstanceHash = // TODO: messy way to compute this
       (props.eventDrag ? props.eventDrag.affectedInstances : null) ||
       (props.eventResize ? props.eventResize.affectedInstances : null) ||
       {}
@@ -90,10 +98,10 @@ export default class TableRow extends DateComponent<TableRowProps, TableRowState
         {props.cells.map((cell, col) => {
           let normalFgNodes = this.renderFgSegs(
             segsByFirstCol[col],
-            segIsNoDisplay,
+            segIsHidden,
             segTops,
             segMarginTops,
-            interactionAffectedInstances,
+            selectedInstanceHash,
             props.todayRange
           )
 
@@ -129,7 +137,7 @@ export default class TableRow extends DateComponent<TableRowProps, TableRowState
               onMoreClick={props.onMoreClick}
               hasEvents={Boolean(normalFgNodes.length)}
               allFgSegs={finalSegsByCol[col]}
-              segIsNoDisplay={segIsNoDisplay}
+              segIsHidden={segIsHidden}
               fgPaddingBottom={paddingBottoms[col]}
               fgContentElRef={this.cellContentElRefs.createRef(col)}
               fgContent={[
@@ -150,7 +158,7 @@ export default class TableRow extends DateComponent<TableRowProps, TableRowState
 
 
   componentDidMount() {
-    this.updateSizing(true, false)
+    this.updateSizing(true)
   }
 
 
@@ -166,8 +174,7 @@ export default class TableRow extends DateComponent<TableRowProps, TableRowState
         prevProps.dayMaxEventRows !== currentProps.dayMaxEventRows ||
         prevProps.clientWidth !== currentProps.clientWidth ||
         prevProps.showDayNumbers !== currentProps.showDayNumbers ||
-        prevProps.showWeekNumbers !== currentProps.showWeekNumbers,
-      prevState.cellContentPositions !== this.state.cellContentPositions
+        prevProps.showWeekNumbers !== currentProps.showWeekNumbers
     )
   }
 
@@ -201,10 +208,10 @@ export default class TableRow extends DateComponent<TableRowProps, TableRowState
 
   renderFgSegs(
     segs: TableSeg[],
-    segIsNoDisplay: { [instanceId: string]: boolean },
+    segIsHidden: { [instanceId: string]: boolean }, // does NOT mean display:hidden
     segTops: { [instanceId: string]: number },
     segMarginTops: { [instanceId: string]: number },
-    segIsInvisible: { [instanceId: string]: any },
+    selectedInstanceHash: { [instanceId: string]: any },
     todayRange: DateRange,
     isDragging?: boolean,
     isResizing?: boolean,
@@ -221,16 +228,15 @@ export default class TableRow extends DateComponent<TableRowProps, TableRowState
         let { eventRange } = seg
         let instanceId = eventRange.instance.instanceId
         let isMirror = isDragging || isResizing || isDateSelecting
-        let isAbsolute = isMirror || seg.firstCol !== seg.lastCol || !seg.isStart || !seg.isEnd // TODO: simpler way? NOT DRY
+        let isSelected = selectedInstanceHash[instanceId]
+        let isInvisible = segIsHidden[instanceId] || isSelected
+        let isAbsolute = isMirror || isInvisible || seg.firstCol !== seg.lastCol || !seg.isStart || !seg.isEnd // TODO: simpler way? NOT DRY
         let marginTop: CssDimValue
         let top: CssDimValue
         let left: CssDimValue
         let right: CssDimValue
 
-        if (!isAbsolute) {
-          marginTop = segMarginTops[instanceId]
-
-        } else {
+        if (isAbsolute) {
           top = segTops[instanceId]
 
           // TODO: cache these left/rights so that when vertical coords come around, don't need to recompute?
@@ -243,6 +249,9 @@ export default class TableRow extends DateComponent<TableRowProps, TableRowState
             right = cellContentPositions.rights[seg.firstCol]
               - (seg.isEnd ? cellContentPositions.rights[seg.lastCol] : cellInnerPositions.rights[seg.lastCol])
           }
+
+        } else {
+          marginTop = segMarginTops[instanceId]
         }
 
         nodes.push(
@@ -251,8 +260,7 @@ export default class TableRow extends DateComponent<TableRowProps, TableRowState
             key={instanceId}
             ref={isMirror ? null : this.segHarnessRefs.createRef(instanceId)}
             style={{
-              display: segIsNoDisplay[instanceId] ? 'none' : '',
-              visibility: segIsInvisible[instanceId] ? 'hidden' : '',
+              visibility: isInvisible ? 'hidden' : '',
               marginTop: marginTop || '',
               top: top || '',
               left: left || '',
@@ -318,50 +326,57 @@ export default class TableRow extends DateComponent<TableRowProps, TableRowState
   }
 
 
-  updateSizing(isExternalSizingChange, isCellPositionsChanged) {
-    if (
-      isExternalSizingChange &&
-      this.props.clientWidth !== null // positioning ready?
-    ) {
-      let cellInnerEls = this.cellInnerElRefs.collect()
-      let cellContentEls = this.cellContentElRefs.collect()
-
-      if (cellContentEls.length) {
-        let originEl = this.base as HTMLElement // BAD
-
-        this.setState({ // will trigger isCellPositionsChanged...
-          cellInnerPositions: new PositionCache(
-            originEl,
-            cellInnerEls,
-            true, // isHorizontal
-            false
-          ),
-          cellContentPositions: new PositionCache(
-            originEl,
-            cellContentEls,
-            true, // isHorizontal (for computeFgSegPlacement)
-            false
-          ),
-          segHeights: null
-        })
+  updateSizing(isExternalSizingChange) {
+    if (this.props.clientWidth !== null) { // positioning ready?
+
+      if (isExternalSizingChange) {
+        let cellInnerEls = this.cellInnerElRefs.collect()
+        let cellContentEls = this.cellContentElRefs.collect()
+
+        if (cellContentEls.length) {
+          let originEl = this.base as HTMLElement // BAD
+
+          this.setState({ // will trigger isCellPositionsChanged...
+            cellInnerPositions: new PositionCache(
+              originEl,
+              cellInnerEls,
+              true, // isHorizontal
+              false
+            ),
+            cellContentPositions: new PositionCache(
+              originEl,
+              cellContentEls,
+              true, // isHorizontal (for computeFgSegPlacement)
+              false
+            )
+          })
+        }
       }
 
-    } else if (isCellPositionsChanged) {
-      let segHeights = mapHash(this.segHarnessRefs.currentMap, (eventHarnessEl) => (
-        eventHarnessEl.getBoundingClientRect().height
-      ))
+      let limitByContentHeight = this.props.dayMaxEvents === true || this.props.dayMaxEventRows === true
 
       this.setState({
-        maxContentHeight: (this.props.dayMaxEvents === true || this.props.dayMaxEventRows === true) ? this.computeMaxContentHeight() : null,
-        segHeights
+        segHeights: this.computeSegHeights(),
+        maxContentHeight: limitByContentHeight ? this.computeMaxContentHeight() : null
       })
     }
   }
 
 
+  computeSegHeights() { // query
+    return mapHash(this.segHarnessRefs.currentMap, (eventHarnessEl, instanceId) => (
+      eventHarnessEl.getBoundingClientRect().height
+    ))
+  }
+
+
   computeMaxContentHeight() {
     let contentEl = this.cellContentElRefs.currentMap[0]
     return contentEl.getBoundingClientRect().height
   }
 
 }
+
+TableRow.addStateEquality({
+  segHeights: isPropsEqual
+})

+ 73 - 66
packages/daygrid/src/event-placement.ts

@@ -1,4 +1,4 @@
-import TableSeg, { splitSegsByFirstCol } from './TableSeg'
+import TableSeg from './TableSeg'
 import { sortEventSegs } from '@fullcalendar/core'
 
 
@@ -13,14 +13,14 @@ export function computeFgSegPlacement( // for one row. TODO: print mode?
   segs: TableSeg[],
   dayMaxEvents: boolean | number,
   dayMaxEventRows: boolean | number,
-  eventHeights: { [instanceId: string]: number } | null,
+  eventHeights: { [instanceId: string]: number },
   maxContentHeight: number | null,
   colCnt: number,
   eventOrderSpecs: any
 ) {
   let colPlacements: TableSegPlacement[][] = [] // if event spans multiple cols, its present in each col
   let moreCnts: number[] = [] // by-col
-  let segIsNoDisplay: { [instanceId: string]: boolean } = {}
+  let segIsHidden: { [instanceId: string]: boolean } = {}
   let segTops: { [instanceId: string]: number } = {} // always populated for each seg
   let segMarginTops: { [instanceId: string]: number } = {} // simetimes populated for each seg
   let moreTops: { [col: string]: number } = {}
@@ -36,74 +36,71 @@ export function computeFgSegPlacement( // for one row. TODO: print mode?
 
   segs = sortEventSegs(segs, eventOrderSpecs) as TableSeg[]
 
-  if (eventHeights) {
-
-    // TODO: try all seg placements and choose the topmost! dont quit after first
-    // SOLUTION: when placed, insert into colPlacements
-    for (let seg of segs) {
-      placeSeg(seg, eventHeights[seg.eventRange.instance.instanceId])
-    }
+  // TODO: try all seg placements and choose the topmost! dont quit after first
+  // SOLUTION: when placed, insert into colPlacements
+  for (let seg of segs) {
+    let { instanceId } = seg.eventRange.instance
+    let eventHeight = eventHeights[instanceId]
 
-    // sort. for dayMaxEvents and segTops computation
-    for (let placements of colPlacements) {
-      placements.sort(cmpPlacements) // sorts in-place
-    }
+    placeSeg(seg, eventHeight || 0)
+  }
 
-    if (dayMaxEvents === true || dayMaxEventRows === true) {
-      limitByMaxHeight(moreCnts, segIsNoDisplay, colPlacements, maxContentHeight) // populates moreCnts/segIsNoDisplay
+  // sort. for dayMaxEvents and segTops computation
+  for (let placements of colPlacements) {
+    placements.sort(cmpPlacements) // sorts in-place
+  }
 
-    } else if (typeof dayMaxEvents === 'number') {
-      limitByMaxEvents(moreCnts, segIsNoDisplay, colPlacements, dayMaxEvents) // populates moreCnts/segIsNoDisplay
+  if (dayMaxEvents === true || dayMaxEventRows === true) {
+    limitByMaxHeight(moreCnts, segIsHidden, colPlacements, maxContentHeight) // populates moreCnts/segIsHidden
 
-    } else if (typeof dayMaxEventRows === 'number') {
-      limitByMaxRows(moreCnts, segIsNoDisplay, colPlacements, dayMaxEventRows) // populates moreCnts/segIsNoDisplay
-    }
+  } else if (typeof dayMaxEvents === 'number') {
+    limitByMaxEvents(moreCnts, segIsHidden, colPlacements, dayMaxEvents) // populates moreCnts/segIsHidden
 
-    // computes segTops/segMarginTops/moreTops/paddingBottoms
-    for (let col = 0; col < colCnt; col++) {
-      let placements = colPlacements[col]
-      let currentBottom = 0
-      let currentExtraSpace = 0
+  } else if (typeof dayMaxEventRows === 'number') {
+    limitByMaxRows(moreCnts, segIsHidden, colPlacements, dayMaxEventRows) // populates moreCnts/segIsHidden
+  }
 
-      for (let placement of placements) {
-        let seg = placement.seg
+  // computes segTops/segMarginTops/moreTops/paddingBottoms
+  for (let col = 0; col < colCnt; col++) {
+    let placements = colPlacements[col]
+    let currentBottom = 0
+    let currentExtraSpace = 0
 
-        if (!segIsNoDisplay[seg.eventRange.instance.instanceId]) {
+    for (let placement of placements) {
+      let seg = placement.seg
 
-          segTops[seg.eventRange.instance.instanceId] = placement.top // from top of container
+      if (!segIsHidden[seg.eventRange.instance.instanceId]) {
 
-          if (seg.firstCol === seg.lastCol && seg.isStart && seg.isEnd) { // TODO: simpler way? NOT DRY
+        segTops[seg.eventRange.instance.instanceId] = placement.top // from top of container
 
-            segMarginTops[seg.eventRange.instance.instanceId] =
-              placement.top - currentBottom // from previous seg bottom
-              + currentExtraSpace
+        if (seg.firstCol === seg.lastCol && seg.isStart && seg.isEnd) { // TODO: simpler way? NOT DRY
 
-            currentExtraSpace = 0
+          segMarginTops[seg.eventRange.instance.instanceId] =
+            placement.top - currentBottom // from previous seg bottom
+            + currentExtraSpace
 
-          } else { // multi-col event, abs positioned
-            currentExtraSpace += placement.bottom - placement.top // for future non-abs segs
-          }
+          currentExtraSpace = 0
 
-          currentBottom = placement.bottom
+        } else { // multi-col event, abs positioned
+          currentExtraSpace += placement.bottom - placement.top // for future non-abs segs
         }
-      }
 
-      if (currentExtraSpace) {
-        if (moreCnts[col]) {
-          moreTops[col] = currentExtraSpace
-        } else {
-          paddingBottoms[col] = currentExtraSpace
-        }
+        currentBottom = placement.bottom
       }
     }
 
-    segsByFirstCol = colPlacements.map(extractFirstColSegs) // operates on the sorted cols
-    finalSegsByCol = colPlacements.map(extractAllColSegs)
-
-  } else {
-    segsByFirstCol = splitSegsByFirstCol(segs, colCnt) // unsorted. that's ok
+    if (currentExtraSpace) {
+      if (moreCnts[col]) {
+        moreTops[col] = currentExtraSpace
+      } else {
+        paddingBottoms[col] = currentExtraSpace
+      }
+    }
   }
 
+  segsByFirstCol = colPlacements.map(extractFirstColSegs) // operates on the sorted cols
+  finalSegsByCol = colPlacements.map(extractAllColSegs)
+
   function placeSeg(seg, segHeight) {
     if (!tryPlaceSegAt(seg, segHeight, 0)) {
       for (let col = seg.firstCol; col <= seg.lastCol; col++) {
@@ -142,10 +139,16 @@ export function computeFgSegPlacement( // for one row. TODO: print mode?
     return true
   }
 
+  for (let instanceId in eventHeights) {
+    if (!eventHeights[instanceId]) {
+      segIsHidden[instanceId] = true
+    }
+  }
+
   return {
     finalSegsByCol,
     segsByFirstCol,
-    segIsNoDisplay,
+    segIsHidden,
     segTops,
     segMarginTops,
     moreCnts,
@@ -184,34 +187,34 @@ function cmpPlacements(placement0, placement1) {
 }
 
 
-function limitByMaxHeight(hiddenCnts, segIsNoDisplay, colPlacements, maxContentHeight) {
-  limitEvents(hiddenCnts, segIsNoDisplay, colPlacements, true, (placement) => {
+function limitByMaxHeight(hiddenCnts, segIsHidden, colPlacements, maxContentHeight) {
+  limitEvents(hiddenCnts, segIsHidden, colPlacements, true, (placement) => {
     return placement.bottom <= maxContentHeight
   })
 }
 
 
-function limitByMaxEvents(hiddenCnts, segIsNoDisplay, colPlacements, dayMaxEvents) {
-  limitEvents(hiddenCnts, segIsNoDisplay, colPlacements, false, (placement, levelIndex) => {
+function limitByMaxEvents(hiddenCnts, segIsHidden, colPlacements, dayMaxEvents) {
+  limitEvents(hiddenCnts, segIsHidden, colPlacements, false, (placement, levelIndex) => {
     return levelIndex < dayMaxEvents
   })
 }
 
 
-function limitByMaxRows(hiddenCnts, segIsNoDisplay, colPlacements, dayMaxEventRows) {
-  limitEvents(hiddenCnts, segIsNoDisplay, colPlacements, true, (placement, levelIndex) => {
+function limitByMaxRows(hiddenCnts, segIsHidden, colPlacements, dayMaxEventRows) {
+  limitEvents(hiddenCnts, segIsHidden, colPlacements, true, (placement, levelIndex) => {
     return levelIndex < dayMaxEventRows
   })
 }
 
 
 /*
-populates the given hiddenCnts/segIsNoDisplay, which are supplied empty.
+populates the given hiddenCnts/segIsHidden, which are supplied empty.
 TODO: return them instead
 */
-function limitEvents(hiddenCnts, segIsNoDisplay, colPlacements, moreLinkConsumesLevel, isPlacementInBounds) {
+function limitEvents(hiddenCnts, segIsHidden, colPlacements, moreLinkConsumesLevel, isPlacementInBounds) {
   let colCnt = hiddenCnts.length
-  let segIsVisible = {} as any // TODO: instead, use segIsNoDisplay with true/false?
+  let segIsVisible = {} as any // TODO: instead, use segIsHidden with true/false?
   let visibleColPlacements = [] // will mirror colPlacements
 
   for (let col = 0; col < colCnt; col++) {
@@ -220,16 +223,20 @@ function limitEvents(hiddenCnts, segIsNoDisplay, colPlacements, moreLinkConsumes
 
   for (let col = 0; col < colCnt; col++) {
     let placements = colPlacements[col]
+    let level = 0
 
-    for (let level = 0; level < placements.length; level++) {
-      let placement = placements[level]
+    for (let placement of placements) {
 
       if (isPlacementInBounds(placement, level)) {
         recordVisible(placement)
-
       } else {
         recordHidden(placement)
       }
+
+      // only considered a level if the seg had height
+      if (placement.top !== placement.bottom) {
+        level++
+      }
     }
   }
 
@@ -250,8 +257,8 @@ function limitEvents(hiddenCnts, segIsNoDisplay, colPlacements, moreLinkConsumes
     let { seg } = placement
     let { instanceId } = seg.eventRange.instance
 
-    if (!segIsNoDisplay[instanceId]) {
-      segIsNoDisplay[instanceId] = true
+    if (!segIsHidden[instanceId]) {
+      segIsHidden[instanceId] = true
 
       for (let col = seg.firstCol; col <= seg.lastCol; col++) {
         let hiddenCnt = ++hiddenCnts[col]