@fullcalendar/timeline 7.0.0-beta.4 → 7.0.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/internal.js CHANGED
@@ -1,4 +1,4 @@
1
- import { config, createFormatter, greatestDurationDenominator, asCleanDays, createDuration, wholeDivideDurations, asRoughMs, addDays, startOfDay, asRoughSeconds, asRoughMinutes, diffWholeDays, isInt, computeVisibleDayRange, padStart, BaseComponent, getDateMeta, ContentContainer, joinClassNames, getDayClassName, getSlotClassName, watchWidth, setRef, RefMap, afterSize, getEventKey, SegHierarchy, groupIntersectingSegs, buildEventRangeKey, BgEvent, getEventRangeMeta, renderFill, Slicer, intersectRanges, addMs, StandardEvent, MoreLinkContainer, watchHeight, memoize, sortEventSegs, memoizeObjArg, buildNavLinkAttrs, watchSize, NowIndicatorContainer, DateComponent, getIsHeightAuto, getStickyHeaderDates, getStickyFooterScrollbar, NowTimer, rangeContainsMarker, ViewContainer, Scroller, multiplyDuration, injectStyles } from '@fullcalendar/core/internal.js';
1
+ import { config, createFormatter, greatestDurationDenominator, asCleanDays, createDuration, wholeDivideDurations, asRoughMs, addDays, startOfDay, asRoughSeconds, asRoughMinutes, diffWholeDays, isInt, computeVisibleDayRange, padStart, BaseComponent, memoize, getDateMeta, ContentContainer, joinClassNames, getDayClassName, getSlotClassName, watchWidth, setRef, RefMap, afterSize, memoizeObjArg, buildNavLinkAttrs, watchSize, NowIndicatorContainer, Slicer, intersectRanges, addMs, StandardEvent, MoreLinkContainer, getEventRangeMeta, getEventKey, SegHierarchy, groupIntersectingSegs, watchHeight, sortEventSegs, buildEventRangeKey, BgEvent, renderFill, DateComponent, getIsHeightAuto, getStickyHeaderDates, getStickyFooterScrollbar, NowTimer, rangeContainsMarker, ViewContainer, Scroller, FooterScrollbar, Ruler, multiplyDuration, injectStyles } from '@fullcalendar/core/internal.js';
2
2
  import { createRef, createElement, Fragment, Component } from '@fullcalendar/core/preact.js';
3
3
  import { ScrollerSyncer } from '@fullcalendar/scrollgrid/internal.js';
4
4
 
@@ -443,6 +443,8 @@ function buildCellObject(date, text, rowUnit) {
443
443
  class TimelineSlatCell extends BaseComponent {
444
444
  constructor() {
445
445
  super(...arguments);
446
+ // memo
447
+ this.getPublicDate = memoize((dateEnv, date) => dateEnv.toDate(date));
446
448
  // ref
447
449
  this.innerElRef = createRef();
448
450
  }
@@ -451,7 +453,7 @@ class TimelineSlatCell extends BaseComponent {
451
453
  let { dateEnv, options } = context;
452
454
  let { date, tDateProfile, isEm } = props;
453
455
  let dateMeta = getDateMeta(props.date, props.todayRange, props.nowDate, props.dateProfile);
454
- let renderProps = Object.assign(Object.assign({ date: dateEnv.toDate(props.date) }, dateMeta), { view: context.viewApi });
456
+ let renderProps = Object.assign(Object.assign({ date: this.getPublicDate(dateEnv, props.date) }, dateMeta), { view: context.viewApi });
455
457
  return (createElement(ContentContainer, { tag: "div",
456
458
  // fc-align-start shrinks width of InnerContent
457
459
  // TODO: document this semantic className fc-timeline-slot-em
@@ -465,7 +467,11 @@ class TimelineSlatCell extends BaseComponent {
465
467
  omitTime: !tDateProfile.isTimeScale,
466
468
  }) }, (dateMeta.isToday ? { 'aria-current': 'date' } : {})), style: {
467
469
  width: props.width,
468
- }, renderProps: renderProps, generatorName: "slotLaneContent", customGenerator: options.slotLaneContent, classNameGenerator: options.slotLaneClassNames, didMount: options.slotLaneDidMount, willUnmount: options.slotLaneWillUnmount }, (InnerContent) => (createElement(InnerContent, { tag: "div", className: 'fc-cell-inner', elRef: this.innerElRef }))));
470
+ }, renderProps: renderProps, generatorName: "slotLaneContent", customGenerator: options.slotLaneContent, classNameGenerator: options.slotLaneClassNames, didMount: options.slotLaneDidMount, willUnmount: options.slotLaneWillUnmount }, (InnerContent) => (createElement(InnerContent, { tag: "div", className: 'fc-cell-inner', style: {
471
+ // HACK for Safari 16.4,
472
+ // which can't use ResizeObserver on elements with natural width 0
473
+ minWidth: 1,
474
+ }, elRef: this.innerElRef }))));
469
475
  }
470
476
  componentDidMount() {
471
477
  const innerEl = this.innerElRef.current;
@@ -500,7 +506,7 @@ class TimelineSlats extends BaseComponent {
500
506
  let { tDateProfile, slotWidth } = props;
501
507
  let { slotDates, isWeekStarts } = tDateProfile;
502
508
  let isDay = !tDateProfile.isTimeScale && !tDateProfile.largeUnit;
503
- return (createElement("div", { "aria-hidden": true, className: "fc-timeline-slots fc-fill fc-flex-row", style: { height: props.height } }, slotDates.map((slotDate, i) => {
509
+ return (createElement("div", { "aria-hidden": true, className: "fc-timeline-slots fc-flex-row fc-fill", style: { height: props.height } }, slotDates.map((slotDate, i) => {
504
510
  let key = slotDate.toISOString();
505
511
  return (createElement(TimelineSlatCell, { key: key, date: slotDate, dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: props.nowDate, todayRange: props.todayRange, isEm: isWeekStarts[i], isDay: isDay, borderStart: Boolean(i),
506
512
  // ref
@@ -511,50 +517,132 @@ class TimelineSlats extends BaseComponent {
511
517
  }
512
518
  }
513
519
 
514
- /*
515
- TODO: rename this file!
516
- */
517
- // returned value is between 0 and the number of snaps
518
- function computeDateSnapCoverage$1(date, tDateProfile, dateEnv) {
519
- let snapDiff = dateEnv.countDurationsBetween(tDateProfile.normalizedRange.start, date, tDateProfile.snapDuration);
520
- if (snapDiff < 0) {
521
- return 0;
520
+ class TimelineHeaderCell extends BaseComponent {
521
+ constructor() {
522
+ super(...arguments);
523
+ // memo
524
+ this.refineRenderProps = memoizeObjArg(refineRenderProps);
525
+ // ref
526
+ this.innerElRef = createRef();
522
527
  }
523
- if (snapDiff >= tDateProfile.snapDiffToIndex.length) {
524
- return tDateProfile.snapCnt;
528
+ render() {
529
+ let { props, context } = this;
530
+ let { dateEnv, options } = context;
531
+ let { cell, dateProfile, tDateProfile } = props;
532
+ // the cell.rowUnit is f'd
533
+ // giving 'month' for a 3-day view
534
+ // workaround: to infer day, do NOT time
535
+ let dateMeta = getDateMeta(cell.date, props.todayRange, props.nowDate, dateProfile);
536
+ let renderProps = this.refineRenderProps({
537
+ level: props.rowLevel,
538
+ dateMarker: cell.date,
539
+ text: cell.text,
540
+ dateEnv: context.dateEnv,
541
+ viewApi: context.viewApi,
542
+ });
543
+ let isNavLink = !dateMeta.isDisabled && (cell.rowUnit && cell.rowUnit !== 'time');
544
+ return (createElement(ContentContainer, { tag: "div", className: joinClassNames('fc-timeline-slot-label fc-timeline-slot', cell.isWeekStart && 'fc-timeline-slot-em', // TODO: document this semantic className
545
+ 'fc-header-cell fc-cell fc-flex-col fc-justify-center', props.borderStart && 'fc-border-s', props.isCentered ? 'fc-align-center' : 'fc-align-start',
546
+ // TODO: so slot classnames for week/month/bigger. see note above about rowUnit
547
+ cell.rowUnit === 'time' ?
548
+ getSlotClassName(dateMeta) :
549
+ getDayClassName(dateMeta)), attrs: Object.assign({ 'data-date': dateEnv.formatIso(cell.date, {
550
+ omitTime: !tDateProfile.isTimeScale,
551
+ omitTimeZoneOffset: true,
552
+ }) }, (dateMeta.isToday ? { 'aria-current': 'date' } : {})), style: {
553
+ width: props.slotWidth != null
554
+ ? props.slotWidth * cell.colspan
555
+ : undefined,
556
+ }, renderProps: renderProps, generatorName: "slotLabelContent", customGenerator: options.slotLabelContent, defaultGenerator: renderInnerContent, classNameGenerator: options.slotLabelClassNames, didMount: options.slotLabelDidMount, willUnmount: options.slotLabelWillUnmount }, (InnerContent) => (createElement(InnerContent, { tag: 'div', attrs: isNavLink
557
+ // not tabbable because parent is aria-hidden
558
+ ? buildNavLinkAttrs(context, cell.date, cell.rowUnit, undefined, /* isTabbable = */ false)
559
+ : {} // don't bother with aria-hidden because parent already hidden
560
+ , className: joinClassNames('fc-cell-inner fc-padding-md', props.isSticky && 'fc-sticky-s'), elRef: this.innerElRef }))));
525
561
  }
526
- let snapDiffInt = Math.floor(snapDiff);
527
- let snapCoverage = tDateProfile.snapDiffToIndex[snapDiffInt];
528
- if (isInt(snapCoverage)) { // not an in-between value
529
- snapCoverage += snapDiff - snapDiffInt; // add the remainder
562
+ componentDidMount() {
563
+ const { props } = this;
564
+ const innerEl = this.innerElRef.current; // TODO: make dynamic with useEffect
565
+ this.detachSize = watchSize(innerEl, (width, height) => {
566
+ setRef(props.innerWidthRef, width);
567
+ setRef(props.innerHeightRef, height);
568
+ // HACK for sticky-centering
569
+ innerEl.style.left = innerEl.style.right =
570
+ (props.isCentered && props.isSticky)
571
+ ? `calc(50% - ${width / 2}px)`
572
+ : '';
573
+ });
530
574
  }
531
- else {
532
- // a fractional value, meaning the date is not visible
533
- // always round up in this case. works for start AND end dates in a range.
534
- snapCoverage = Math.ceil(snapCoverage);
575
+ componentWillUnmount() {
576
+ const { props } = this;
577
+ this.detachSize();
578
+ setRef(props.innerWidthRef, null);
579
+ setRef(props.innerHeightRef, null);
535
580
  }
536
- return snapCoverage;
537
581
  }
538
- /*
539
- TODO: DRY up with elsewhere?
540
- */
541
- function horizontalsToCss(hcoord, isRtl) {
542
- if (!hcoord) {
543
- return {};
544
- }
545
- if (isRtl) {
546
- return { right: hcoord.start, width: hcoord.size };
547
- }
548
- else {
549
- return { left: hcoord.start, width: hcoord.size };
550
- }
582
+ // Utils
583
+ // -------------------------------------------------------------------------------------------------
584
+ function renderInnerContent(renderProps) {
585
+ return renderProps.text;
551
586
  }
552
- function horizontalCoordToCss(start, isRtl) {
553
- if (isRtl) {
554
- return { right: start };
587
+ function refineRenderProps(input) {
588
+ return {
589
+ level: input.level,
590
+ date: input.dateEnv.toDate(input.dateMarker),
591
+ view: input.viewApi,
592
+ text: input.text,
593
+ };
594
+ }
595
+
596
+ class TimelineHeaderRow extends BaseComponent {
597
+ constructor() {
598
+ super(...arguments);
599
+ // refs
600
+ this.innerWidthRefMap = new RefMap(() => {
601
+ afterSize(this.handleInnerWidths);
602
+ });
603
+ this.innerHeightRefMap = new RefMap(() => {
604
+ afterSize(this.handleInnerHeights);
605
+ });
606
+ this.handleInnerWidths = () => {
607
+ const innerWidthMap = this.innerWidthRefMap.current;
608
+ let max = 0;
609
+ for (const innerWidth of innerWidthMap.values()) {
610
+ max = Math.max(max, innerWidth);
611
+ }
612
+ // TODO: ensure not equal?
613
+ setRef(this.props.innerWidthRef, max);
614
+ };
615
+ this.handleInnerHeights = () => {
616
+ const innerHeightMap = this.innerHeightRefMap.current;
617
+ let max = 0;
618
+ for (const innerHeight of innerHeightMap.values()) {
619
+ max = Math.max(max, innerHeight);
620
+ }
621
+ // TODO: ensure not equal?
622
+ setRef(this.props.innerHeighRef, max);
623
+ };
555
624
  }
556
- else {
557
- return { left: start };
625
+ render() {
626
+ const { props, innerWidthRefMap, innerHeightRefMap } = this;
627
+ const isCentered = !(props.tDateProfile.isTimeScale && props.isLastRow);
628
+ const isSticky = !props.isLastRow;
629
+ return (createElement("div", { className: joinClassNames('fc-flex-row fc-grow', // TODO: move fc-grow to parent?
630
+ !props.isLastRow && 'fc-border-b') }, props.cells.map((cell, cellI) => {
631
+ // TODO: make this part of the cell obj?
632
+ // TODO: rowUnit seems wrong sometimes. says 'month' when it should be day
633
+ // TODO: rowUnit is relevant to whole row. put it on a row object, not the cells
634
+ // TODO: use rowUnit to key the Row itself?
635
+ const key = cell.rowUnit + ':' + cell.date.toISOString();
636
+ return (createElement(TimelineHeaderCell, { key: key, cell: cell, rowLevel: props.rowLevel, dateProfile: props.dateProfile, tDateProfile: props.tDateProfile, todayRange: props.todayRange, nowDate: props.nowDate, isCentered: isCentered, isSticky: isSticky, borderStart: Boolean(cellI),
637
+ // refs
638
+ innerWidthRef: innerWidthRefMap.createRef(key), innerHeightRef: innerHeightRefMap.createRef(key),
639
+ // dimensions
640
+ slotWidth: props.slotWidth }));
641
+ })));
642
+ }
643
+ componentWillUnmount() {
644
+ setRef(this.props.innerWidthRef, null);
645
+ setRef(this.props.innerHeighRef, null);
558
646
  }
559
647
  }
560
648
 
@@ -609,13 +697,38 @@ time, dateEnv, dateProfile, tDateProfile, slowWidth) {
609
697
  }
610
698
  function dateToCoord(// pixels
611
699
  date, dateEnv, tDateProfile, slotWidth) {
612
- let snapCoverage = computeDateSnapCoverage(date, tDateProfile, dateEnv);
700
+ let snapCoverage = computeDateSnapCoverage$1(date, tDateProfile, dateEnv);
613
701
  let slotCoverage = snapCoverage / tDateProfile.snapsPerSlot;
614
702
  return slotCoverage * slotWidth;
615
703
  }
616
704
  /*
617
705
  returned value is between 0 and the number of snaps
618
706
  */
707
+ function computeDateSnapCoverage$1(date, tDateProfile, dateEnv) {
708
+ let snapDiff = dateEnv.countDurationsBetween(tDateProfile.normalizedRange.start, date, tDateProfile.snapDuration);
709
+ if (snapDiff < 0) {
710
+ return 0;
711
+ }
712
+ if (snapDiff >= tDateProfile.snapDiffToIndex.length) {
713
+ return tDateProfile.snapCnt;
714
+ }
715
+ let snapDiffInt = Math.floor(snapDiff);
716
+ let snapCoverage = tDateProfile.snapDiffToIndex[snapDiffInt];
717
+ if (isInt(snapCoverage)) { // not an in-between value
718
+ snapCoverage += snapDiff - snapDiffInt; // add the remainder
719
+ }
720
+ else {
721
+ // a fractional value, meaning the date is not visible
722
+ // always round up in this case. works for start AND end dates in a range.
723
+ snapCoverage = Math.ceil(snapCoverage);
724
+ }
725
+ return snapCoverage;
726
+ }
727
+
728
+ /*
729
+ TODO: rename this file!
730
+ */
731
+ // returned value is between 0 and the number of snaps
619
732
  function computeDateSnapCoverage(date, tDateProfile, dateEnv) {
620
733
  let snapDiff = dateEnv.countDurationsBetween(tDateProfile.normalizedRange.start, date, tDateProfile.snapDuration);
621
734
  if (snapDiff < 0) {
@@ -634,7 +747,107 @@ function computeDateSnapCoverage(date, tDateProfile, dateEnv) {
634
747
  // always round up in this case. works for start AND end dates in a range.
635
748
  snapCoverage = Math.ceil(snapCoverage);
636
749
  }
637
- return snapCoverage;
750
+ return snapCoverage;
751
+ }
752
+ /*
753
+ TODO: DRY up with elsewhere?
754
+ */
755
+ function horizontalsToCss(hcoord, isRtl) {
756
+ if (!hcoord) {
757
+ return {};
758
+ }
759
+ if (isRtl) {
760
+ return { right: hcoord.start, width: hcoord.size };
761
+ }
762
+ else {
763
+ return { left: hcoord.start, width: hcoord.size };
764
+ }
765
+ }
766
+ function horizontalCoordToCss(start, isRtl) {
767
+ if (isRtl) {
768
+ return { right: start };
769
+ }
770
+ else {
771
+ return { left: start };
772
+ }
773
+ }
774
+
775
+ class TimelineNowIndicatorLine extends BaseComponent {
776
+ render() {
777
+ const { props, context } = this;
778
+ return (createElement("div", { className: "fc-timeline-now-indicator-container" },
779
+ createElement(NowIndicatorContainer // TODO: make separate component?
780
+ , { className: 'fc-timeline-now-indicator-line', style: props.slotWidth != null
781
+ ? horizontalCoordToCss(dateToCoord(props.nowDate, context.dateEnv, props.tDateProfile, props.slotWidth), context.isRtl)
782
+ : {}, isAxis: false, date: props.nowDate })));
783
+ }
784
+ }
785
+
786
+ class TimelineNowIndicatorArrow extends BaseComponent {
787
+ render() {
788
+ const { props, context } = this;
789
+ return (createElement("div", { className: "fc-timeline-now-indicator-container" },
790
+ createElement(NowIndicatorContainer, { className: 'fc-timeline-now-indicator-arrow', style: props.slotWidth != null
791
+ ? horizontalCoordToCss(dateToCoord(props.nowDate, context.dateEnv, props.tDateProfile, props.slotWidth), context.isRtl)
792
+ : {}, isAxis: true, date: props.nowDate })));
793
+ }
794
+ }
795
+
796
+ function getTimelineSlotEl(parentEl, index) {
797
+ return parentEl.querySelectorAll('.fc-timeline-slot')[index];
798
+ }
799
+
800
+ class TimelineLaneSlicer extends Slicer {
801
+ sliceRange(origRange, dateProfile, dateProfileGenerator, tDateProfile, dateEnv) {
802
+ let normalRange = normalizeRange(origRange, tDateProfile, dateEnv);
803
+ let segs = [];
804
+ // protect against when the span is entirely in an invalid date region
805
+ if (computeDateSnapCoverage(normalRange.start, tDateProfile, dateEnv)
806
+ < computeDateSnapCoverage(normalRange.end, tDateProfile, dateEnv)) {
807
+ // intersect the footprint's range with the grid's range
808
+ let slicedRange = intersectRanges(normalRange, tDateProfile.normalizedRange);
809
+ if (slicedRange) {
810
+ segs.push({
811
+ startDate: slicedRange.start,
812
+ endDate: slicedRange.end,
813
+ isStart: slicedRange.start.valueOf() === normalRange.start.valueOf()
814
+ && isValidDate(slicedRange.start, tDateProfile, dateProfile, dateProfileGenerator),
815
+ isEnd: slicedRange.end.valueOf() === normalRange.end.valueOf()
816
+ && isValidDate(addMs(slicedRange.end, -1), tDateProfile, dateProfile, dateProfileGenerator),
817
+ });
818
+ }
819
+ }
820
+ return segs;
821
+ }
822
+ }
823
+
824
+ const DEFAULT_TIME_FORMAT = createFormatter({
825
+ hour: 'numeric',
826
+ minute: '2-digit',
827
+ omitZeroMinute: true,
828
+ meridiem: 'narrow',
829
+ });
830
+ class TimelineEvent extends BaseComponent {
831
+ render() {
832
+ let { props, context } = this;
833
+ let { options } = context;
834
+ return (createElement(StandardEvent, Object.assign({}, props, { className: joinClassNames('fc-timeline-event', options.eventOverlap === false // TODO: fix bad default
835
+ && 'fc-timeline-event-spacious', 'fc-h-event'), defaultTimeFormat: DEFAULT_TIME_FORMAT, defaultDisplayEventTime: !props.isTimeScale })));
836
+ }
837
+ }
838
+
839
+ class TimelineLaneMoreLink extends BaseComponent {
840
+ render() {
841
+ let { props } = this;
842
+ let { hiddenSegs, resourceId, forcedInvisibleMap } = props;
843
+ let dateSpanProps = resourceId ? { resourceId } : {};
844
+ return (createElement(MoreLinkContainer, { className: 'fc-timeline-more-link', allDayDate: null, segs: hiddenSegs, hiddenSegs: hiddenSegs, dateProfile: props.dateProfile, todayRange: props.todayRange, dateSpanProps: dateSpanProps, popoverContent: () => (createElement(Fragment, null, hiddenSegs.map((seg) => {
845
+ let { eventRange } = seg;
846
+ let instanceId = eventRange.instance.instanceId;
847
+ return (createElement("div", { key: instanceId, style: { visibility: forcedInvisibleMap[instanceId] ? 'hidden' : '' } },
848
+ createElement(TimelineEvent, Object.assign({ isTimeScale: props.isTimeScale, eventRange: eventRange, isStart: seg.isStart, isEnd: seg.isEnd, isDragging: false, isResizing: false, isDateSelecting: false, isSelected: instanceId === props.eventSelection }, getEventRangeMeta(eventRange, props.todayRange, props.nowDate)))));
849
+ }))) }, (InnerContent) => (createElement(InnerContent, { tag: "div", className: 'fc-timeline-more-link-inner fc-sticky-s' }))));
850
+ }
638
851
  }
639
852
 
640
853
  function computeManySegHorizontals(segs, segMinWidth, dateEnv, tDateProfile, slotWidth) {
@@ -701,83 +914,6 @@ hiddenGroupHeights, strictOrder, maxDepth) {
701
914
  ];
702
915
  }
703
916
 
704
- class TimelineLaneBg extends BaseComponent {
705
- render() {
706
- let { props } = this;
707
- let highlightSeg = [].concat(props.eventResizeSegs, props.dateSelectionSegs);
708
- return (createElement(Fragment, null,
709
- this.renderSegs(props.businessHourSegs || [], 'non-business'),
710
- this.renderSegs(props.bgEventSegs || [], 'bg-event'),
711
- this.renderSegs(highlightSeg, 'highlight')));
712
- }
713
- renderSegs(segs, fillType) {
714
- let { tDateProfile, todayRange, nowDate, slotWidth } = this.props;
715
- let { dateEnv, isRtl } = this.context;
716
- return (createElement(Fragment, null, segs.map((seg) => {
717
- let hStyle; // TODO
718
- if (slotWidth != null) {
719
- let segHorizontal = computeSegHorizontals(seg, undefined, dateEnv, tDateProfile, slotWidth);
720
- hStyle = horizontalsToCss(segHorizontal, isRtl);
721
- }
722
- return (createElement("div", { key: buildEventRangeKey(seg.eventRange), className: "fc-fill-y", style: hStyle }, fillType === 'bg-event' ?
723
- createElement(BgEvent, Object.assign({ eventRange: seg.eventRange, isStart: seg.isStart, isEnd: seg.isEnd }, getEventRangeMeta(seg.eventRange, todayRange, nowDate))) : (renderFill(fillType))));
724
- })));
725
- }
726
- }
727
-
728
- class TimelineLaneSlicer extends Slicer {
729
- sliceRange(origRange, dateProfile, dateProfileGenerator, tDateProfile, dateEnv) {
730
- let normalRange = normalizeRange(origRange, tDateProfile, dateEnv);
731
- let segs = [];
732
- // protect against when the span is entirely in an invalid date region
733
- if (computeDateSnapCoverage$1(normalRange.start, tDateProfile, dateEnv)
734
- < computeDateSnapCoverage$1(normalRange.end, tDateProfile, dateEnv)) {
735
- // intersect the footprint's range with the grid's range
736
- let slicedRange = intersectRanges(normalRange, tDateProfile.normalizedRange);
737
- if (slicedRange) {
738
- segs.push({
739
- startDate: slicedRange.start,
740
- endDate: slicedRange.end,
741
- isStart: slicedRange.start.valueOf() === normalRange.start.valueOf()
742
- && isValidDate(slicedRange.start, tDateProfile, dateProfile, dateProfileGenerator),
743
- isEnd: slicedRange.end.valueOf() === normalRange.end.valueOf()
744
- && isValidDate(addMs(slicedRange.end, -1), tDateProfile, dateProfile, dateProfileGenerator),
745
- });
746
- }
747
- }
748
- return segs;
749
- }
750
- }
751
-
752
- const DEFAULT_TIME_FORMAT = createFormatter({
753
- hour: 'numeric',
754
- minute: '2-digit',
755
- omitZeroMinute: true,
756
- meridiem: 'narrow',
757
- });
758
- class TimelineEvent extends BaseComponent {
759
- render() {
760
- let { props, context } = this;
761
- let { options } = context;
762
- return (createElement(StandardEvent, Object.assign({}, props, { className: joinClassNames('fc-timeline-event', options.eventOverlap === false // TODO: fix bad default
763
- && 'fc-timeline-event-spacious', 'fc-h-event'), defaultTimeFormat: DEFAULT_TIME_FORMAT, defaultDisplayEventTime: !props.isTimeScale })));
764
- }
765
- }
766
-
767
- class TimelineLaneMoreLink extends BaseComponent {
768
- render() {
769
- let { props } = this;
770
- let { hiddenSegs, resourceId, forcedInvisibleMap } = props;
771
- let dateSpanProps = resourceId ? { resourceId } : {};
772
- return (createElement(MoreLinkContainer, { className: 'fc-timeline-more-link', allDayDate: null, segs: hiddenSegs, hiddenSegs: hiddenSegs, dateProfile: props.dateProfile, todayRange: props.todayRange, dateSpanProps: dateSpanProps, popoverContent: () => (createElement(Fragment, null, hiddenSegs.map((seg) => {
773
- let { eventRange } = seg;
774
- let instanceId = eventRange.instance.instanceId;
775
- return (createElement("div", { key: instanceId, style: { visibility: forcedInvisibleMap[instanceId] ? 'hidden' : '' } },
776
- createElement(TimelineEvent, Object.assign({ isTimeScale: props.isTimeScale, eventRange: eventRange, isStart: seg.isStart, isEnd: seg.isEnd, isDragging: false, isResizing: false, isDateSelecting: false, isSelected: instanceId === props.eventSelection }, getEventRangeMeta(eventRange, props.todayRange, props.nowDate)))));
777
- }))) }, (InnerContent) => (createElement(InnerContent, { tag: "div", className: 'fc-timeline-more-link-inner fc-sticky-s' }))));
778
- }
779
- }
780
-
781
917
  /*
782
918
  TODO: make DRY with other Event Harnesses
783
919
  */
@@ -803,10 +939,7 @@ class TimelineEventHarness extends Component {
803
939
  }
804
940
  }
805
941
 
806
- /*
807
- TODO: split TimelineLaneBg and TimelineLaneFg?
808
- */
809
- class TimelineLane extends BaseComponent {
942
+ class TimelineFg extends BaseComponent {
810
943
  constructor() {
811
944
  super(...arguments);
812
945
  // memo
@@ -818,8 +951,6 @@ class TimelineLane extends BaseComponent {
818
951
  this.moreLinkHeightRefMap = new RefMap(() => {
819
952
  afterSize(this.handleMoreLinkHeights);
820
953
  });
821
- // internal
822
- this.slicer = new TimelineLaneSlicer();
823
954
  this.handleMoreLinkHeights = () => {
824
955
  this.setState({ moreLinkHeightRev: this.moreLinkHeightRefMap.rev }); // will trigger rerender
825
956
  };
@@ -833,39 +964,30 @@ class TimelineLane extends BaseComponent {
833
964
  render() {
834
965
  let { props, context, segHeightRefMap, moreLinkHeightRefMap } = this;
835
966
  let { options } = context;
836
- let { dateProfile, tDateProfile } = props;
837
- let slicedProps = this.slicer.sliceProps(props, dateProfile, tDateProfile.isTimeScale ? null : props.nextDayThreshold, context, // wish we didn't have to pass in the rest of the args...
838
- dateProfile, context.dateProfileGenerator, tDateProfile, context.dateEnv);
839
- let mirrorSegs = (slicedProps.eventDrag ? slicedProps.eventDrag.segs : null) ||
840
- (slicedProps.eventResize ? slicedProps.eventResize.segs : null) ||
967
+ let { tDateProfile } = props;
968
+ let mirrorSegs = (props.eventDrag ? props.eventDrag.segs : null) ||
969
+ (props.eventResize ? props.eventResize.segs : null) ||
841
970
  [];
842
- let fgSegs = this.sortEventSegs(slicedProps.fgEventSegs, options.eventOrder);
971
+ let fgSegs = this.sortEventSegs(props.fgEventSegs, options.eventOrder);
843
972
  let fgSegHorizontals = props.slotWidth != null
844
973
  ? computeManySegHorizontals(fgSegs, options.eventMinWidth, context.dateEnv, tDateProfile, props.slotWidth)
845
974
  : {};
846
975
  let [fgSegTops, hiddenGroups, hiddenGroupTops, totalHeight] = computeFgSegPlacements(fgSegs, fgSegHorizontals, segHeightRefMap.current, moreLinkHeightRefMap.current, options.eventOrderStrict, options.eventMaxStack);
976
+ this.totalHeight = totalHeight;
847
977
  let forcedInvisibleMap = // TODO: more convenient/DRY
848
- (slicedProps.eventDrag ? slicedProps.eventDrag.affectedInstances : null) ||
849
- (slicedProps.eventResize ? slicedProps.eventResize.affectedInstances : null) ||
978
+ (props.eventDrag ? props.eventDrag.affectedInstances : null) ||
979
+ (props.eventResize ? props.eventResize.affectedInstances : null) ||
850
980
  {};
851
- return (createElement(Fragment, null,
852
- createElement(TimelineLaneBg, { tDateProfile: tDateProfile, nowDate: props.nowDate, todayRange: props.todayRange,
853
- // content
854
- bgEventSegs: slicedProps.bgEventSegs, businessHourSegs: slicedProps.businessHourSegs, dateSelectionSegs: slicedProps.dateSelectionSegs, eventResizeSegs: slicedProps.eventResize ? slicedProps.eventResize.segs : [] /* bad new empty array? */,
855
- // dimensions
856
- slotWidth: props.slotWidth }),
857
- createElement("div", { className: joinClassNames('fc-timeline-events', options.eventOverlap === false // TODO: fix bad default
858
- ? 'fc-timeline-events-overlap-disabled'
859
- : 'fc-timeline-events-overlap-enabled', 'fc-content-box'), style: { height: totalHeight } },
860
- this.renderFgSegs(fgSegs, fgSegHorizontals, fgSegTops, forcedInvisibleMap, hiddenGroups, hiddenGroupTops, false, // isDragging
861
- false, // isResizing
862
- false),
863
- this.renderFgSegs(mirrorSegs, props.slotWidth // TODO: memoize
864
- ? computeManySegHorizontals(mirrorSegs, options.eventMinWidth, context.dateEnv, tDateProfile, props.slotWidth)
865
- : {}, fgSegTops, {}, // forcedInvisibleMap
866
- [], // hiddenGroups
867
- new Map(), // hiddenGroupTops
868
- Boolean(slicedProps.eventDrag), Boolean(slicedProps.eventResize), false))));
981
+ return (createElement("div", { className: 'fc-timeline-events fc-rel', style: { height: totalHeight } },
982
+ this.renderFgSegs(fgSegs, fgSegHorizontals, fgSegTops, forcedInvisibleMap, hiddenGroups, hiddenGroupTops, false, // isDragging
983
+ false, // isResizing
984
+ false),
985
+ this.renderFgSegs(mirrorSegs, props.slotWidth // TODO: memoize
986
+ ? computeManySegHorizontals(mirrorSegs, options.eventMinWidth, context.dateEnv, tDateProfile, props.slotWidth)
987
+ : {}, fgSegTops, {}, // forcedInvisibleMap
988
+ [], // hiddenGroups
989
+ new Map(), // hiddenGroupTops
990
+ Boolean(props.eventDrag), Boolean(props.eventResize), false)));
869
991
  }
870
992
  renderFgSegs(segs, segHorizontals, segTops, forcedInvisibleMap, hiddenGroups, hiddenGroupTops, isDragging, isResizing, isDateSelecting) {
871
993
  let { props, context, segHeightRefMap, moreLinkHeightRefMap } = this;
@@ -887,155 +1009,43 @@ class TimelineLane extends BaseComponent {
887
1009
  }, context.isRtl)), heightRef: moreLinkHeightRefMap.createRef(hiddenGroup.key) },
888
1010
  createElement(TimelineLaneMoreLink, { hiddenSegs: hiddenGroup.segs, dateProfile: props.dateProfile, nowDate: props.nowDate, todayRange: props.todayRange, isTimeScale: props.tDateProfile.isTimeScale, eventSelection: props.eventSelection, resourceId: props.resourceId, forcedInvisibleMap: forcedInvisibleMap }))))));
889
1011
  }
890
- }
891
-
892
- class TimelineHeaderCell extends BaseComponent {
893
- constructor() {
894
- super(...arguments);
895
- // memo
896
- this.refineRenderProps = memoizeObjArg(refineRenderProps);
897
- // ref
898
- this.innerElRef = createRef();
899
- }
900
- render() {
901
- let { props, context } = this;
902
- let { dateEnv, options } = context;
903
- let { cell, dateProfile, tDateProfile } = props;
904
- // the cell.rowUnit is f'd
905
- // giving 'month' for a 3-day view
906
- // workaround: to infer day, do NOT time
907
- let dateMeta = getDateMeta(cell.date, props.todayRange, props.nowDate, dateProfile);
908
- let renderProps = this.refineRenderProps({
909
- level: props.rowLevel,
910
- dateMarker: cell.date,
911
- text: cell.text,
912
- dateEnv: context.dateEnv,
913
- viewApi: context.viewApi,
914
- });
915
- let isNavLink = !dateMeta.isDisabled && (cell.rowUnit && cell.rowUnit !== 'time');
916
- return (createElement(ContentContainer, { tag: "div", className: joinClassNames('fc-timeline-slot-label fc-timeline-slot', cell.isWeekStart && 'fc-timeline-slot-em', // TODO: document this semantic className
917
- 'fc-header-cell fc-cell fc-flex-col fc-justify-center', props.borderStart && 'fc-border-s', props.isCentered ? 'fc-align-center' : 'fc-align-start',
918
- // TODO: so slot classnames for week/month/bigger. see note above about rowUnit
919
- cell.rowUnit === 'time' ?
920
- getSlotClassName(dateMeta) :
921
- getDayClassName(dateMeta)), attrs: Object.assign({ 'data-date': dateEnv.formatIso(cell.date, {
922
- omitTime: !tDateProfile.isTimeScale,
923
- omitTimeZoneOffset: true,
924
- }) }, (dateMeta.isToday ? { 'aria-current': 'date' } : {})), style: {
925
- width: props.slotWidth != null
926
- ? props.slotWidth * cell.colspan
927
- : undefined,
928
- }, renderProps: renderProps, generatorName: "slotLabelContent", customGenerator: options.slotLabelContent, defaultGenerator: renderInnerContent, classNameGenerator: options.slotLabelClassNames, didMount: options.slotLabelDidMount, willUnmount: options.slotLabelWillUnmount }, (InnerContent) => (createElement(InnerContent, { tag: 'div', attrs: isNavLink
929
- // not tabbable because parent is aria-hidden
930
- ? buildNavLinkAttrs(context, cell.date, cell.rowUnit, undefined, /* isTabbable = */ false)
931
- : {} // don't bother with aria-hidden because parent already hidden
932
- , className: joinClassNames('fc-cell-inner fc-padding-md', props.isSticky && 'fc-sticky-s'), elRef: this.innerElRef }))));
933
- }
934
- componentDidMount() {
935
- const { props } = this;
936
- const innerEl = this.innerElRef.current; // TODO: make dynamic with useEffect
937
- this.detachSize = watchSize(innerEl, (width, height) => {
938
- setRef(props.innerWidthRef, width);
939
- setRef(props.innerHeightRef, height);
940
- // HACK for sticky-centering
941
- innerEl.style.left = innerEl.style.right =
942
- (props.isCentered && props.isSticky)
943
- ? `calc(50% - ${width / 2}px)`
944
- : '';
945
- });
946
- }
947
- componentWillUnmount() {
948
- const { props } = this;
949
- this.detachSize();
950
- setRef(props.innerWidthRef, null);
951
- setRef(props.innerHeightRef, null);
952
- }
953
- }
954
- // Utils
955
- // -------------------------------------------------------------------------------------------------
956
- function renderInnerContent(renderProps) {
957
- return renderProps.text;
958
- }
959
- function refineRenderProps(input) {
960
- return {
961
- level: input.level,
962
- date: input.dateEnv.toDate(input.dateMarker),
963
- view: input.viewApi,
964
- text: input.text,
965
- };
966
- }
967
-
968
- class TimelineHeaderRow extends BaseComponent {
969
- constructor() {
970
- super(...arguments);
971
- // refs
972
- this.innerWidthRefMap = new RefMap(() => {
973
- afterSize(this.handleInnerWidths);
974
- });
975
- this.innerHeightRefMap = new RefMap(() => {
976
- afterSize(this.handleInnerHeights);
977
- });
978
- this.handleInnerWidths = () => {
979
- const innerWidthMap = this.innerWidthRefMap.current;
980
- let max = 0;
981
- for (const innerWidth of innerWidthMap.values()) {
982
- max = Math.max(max, innerWidth);
983
- }
984
- // TODO: ensure not equal?
985
- setRef(this.props.innerWidthRef, max);
986
- };
987
- this.handleInnerHeights = () => {
988
- const innerHeightMap = this.innerHeightRefMap.current;
989
- let max = 0;
990
- for (const innerHeight of innerHeightMap.values()) {
991
- max = Math.max(max, innerHeight);
992
- }
993
- // TODO: ensure not equal?
994
- setRef(this.props.innerHeighRef, max);
995
- };
1012
+ /*
1013
+ componentDidMount(): void {
1014
+ // might want to do firedTotalHeight, but won't be ready on first render
996
1015
  }
997
- render() {
998
- const { props, innerWidthRefMap, innerHeightRefMap } = this;
999
- const isCentered = !(props.tDateProfile.isTimeScale && props.isLastRow);
1000
- const isSticky = !props.isLastRow;
1001
- return (createElement("div", { className: joinClassNames('fc-flex-row fc-grow', // TODO: move fc-grow to parent?
1002
- !props.isLastRow && 'fc-border-b') }, props.cells.map((cell, cellI) => {
1003
- // TODO: make this part of the cell obj?
1004
- // TODO: rowUnit seems wrong sometimes. says 'month' when it should be day
1005
- // TODO: rowUnit is relevant to whole row. put it on a row object, not the cells
1006
- // TODO: use rowUnit to key the Row itself?
1007
- const key = cell.rowUnit + ':' + cell.date.toISOString();
1008
- return (createElement(TimelineHeaderCell, { key: key, cell: cell, rowLevel: props.rowLevel, dateProfile: props.dateProfile, tDateProfile: props.tDateProfile, todayRange: props.todayRange, nowDate: props.nowDate, isCentered: isCentered, isSticky: isSticky, borderStart: Boolean(cellI),
1009
- // refs
1010
- innerWidthRef: innerWidthRefMap.createRef(key), innerHeightRef: innerHeightRefMap.createRef(key),
1011
- // dimensions
1012
- slotWidth: props.slotWidth }));
1013
- })));
1016
+ */
1017
+ componentDidUpdate() {
1018
+ if (this.totalHeight !== this.firedTotalHeight) {
1019
+ this.firedTotalHeight = this.totalHeight;
1020
+ setRef(this.props.heightRef, this.totalHeight);
1021
+ }
1014
1022
  }
1015
1023
  componentWillUnmount() {
1016
- setRef(this.props.innerWidthRef, null);
1017
- setRef(this.props.innerHeighRef, null);
1024
+ setRef(this.props.heightRef, null);
1018
1025
  }
1019
1026
  }
1020
1027
 
1021
- class TimelineNowIndicatorLine extends BaseComponent {
1028
+ class TimelineBg extends BaseComponent {
1022
1029
  render() {
1023
- const { props, context } = this;
1024
- return (createElement("div", { className: "fc-timeline-now-indicator-container" },
1025
- createElement(NowIndicatorContainer // TODO: make separate component?
1026
- , { className: 'fc-timeline-now-indicator-line', style: props.slotWidth != null
1027
- ? horizontalCoordToCss(dateToCoord(props.nowDate, context.dateEnv, props.tDateProfile, props.slotWidth), context.isRtl)
1028
- : {}, isAxis: false, date: props.nowDate })));
1030
+ let { props } = this;
1031
+ let highlightSeg = [].concat(props.eventResizeSegs || [], props.dateSelectionSegs);
1032
+ return (createElement(Fragment, null,
1033
+ this.renderSegs(props.businessHourSegs || [], 'non-business'),
1034
+ this.renderSegs(props.bgEventSegs || [], 'bg-event'),
1035
+ this.renderSegs(highlightSeg, 'highlight')));
1029
1036
  }
1030
- }
1031
-
1032
- class TimelineNowIndicatorArrow extends BaseComponent {
1033
- render() {
1034
- const { props, context } = this;
1035
- return (createElement("div", { className: "fc-timeline-now-indicator-container" },
1036
- createElement(NowIndicatorContainer, { className: 'fc-timeline-now-indicator-arrow', style: props.slotWidth != null
1037
- ? horizontalCoordToCss(dateToCoord(props.nowDate, context.dateEnv, props.tDateProfile, props.slotWidth), context.isRtl)
1038
- : {}, isAxis: true, date: props.nowDate })));
1037
+ renderSegs(segs, fillType) {
1038
+ let { tDateProfile, todayRange, nowDate, slotWidth } = this.props;
1039
+ let { dateEnv, isRtl } = this.context;
1040
+ return (createElement(Fragment, null, segs.map((seg) => {
1041
+ let hStyle; // TODO
1042
+ if (slotWidth != null) {
1043
+ let segHorizontal = computeSegHorizontals(seg, undefined, dateEnv, tDateProfile, slotWidth);
1044
+ hStyle = horizontalsToCss(segHorizontal, isRtl);
1045
+ }
1046
+ return (createElement("div", { key: buildEventRangeKey(seg.eventRange), className: "fc-fill-y", style: hStyle }, fillType === 'bg-event' ?
1047
+ createElement(BgEvent, Object.assign({ eventRange: seg.eventRange, isStart: seg.isStart, isEnd: seg.isEnd }, getEventRangeMeta(seg.eventRange, todayRange, nowDate))) : (renderFill(fillType))));
1048
+ })));
1039
1049
  }
1040
1050
  }
1041
1051
 
@@ -1053,6 +1063,7 @@ class TimelineView extends DateComponent {
1053
1063
  afterSize(this.handleSlotInnerWidths);
1054
1064
  });
1055
1065
  this.scrollTime = null;
1066
+ this.slicer = new TimelineLaneSlicer();
1056
1067
  // Sizing
1057
1068
  // -----------------------------------------------------------------------------------------------
1058
1069
  this.handleBodySlotInnerWidth = (innerWidth) => {
@@ -1060,27 +1071,30 @@ class TimelineView extends DateComponent {
1060
1071
  afterSize(this.handleSlotInnerWidths);
1061
1072
  };
1062
1073
  this.handleSlotInnerWidths = () => {
1063
- const { state } = this;
1064
- const slotInnerWidth = Math.max(this.headerRowInnerWidthMap.current.get(this.tDateProfile.cellRows.length - 1) || 0, this.bodySlotInnerWidth);
1065
- if (state.slotInnerWidth !== slotInnerWidth) {
1066
- this.setState({ slotInnerWidth });
1074
+ const headerSlotInnerWidth = this.headerRowInnerWidthMap.current.get(this.tDateProfile.cellRows.length - 1);
1075
+ const { bodySlotInnerWidth } = this;
1076
+ if (headerSlotInnerWidth != null && bodySlotInnerWidth != null) {
1077
+ const slotInnerWidth = Math.max(headerSlotInnerWidth, bodySlotInnerWidth);
1078
+ if (slotInnerWidth !== this.state.slotInnerWidth) {
1079
+ this.setState({ slotInnerWidth });
1080
+ }
1067
1081
  }
1068
1082
  };
1069
- this.handleClientWidth = (clientWidth) => {
1083
+ this.handleTotalWidth = (totalWidth) => {
1070
1084
  this.setState({
1071
- clientWidth,
1085
+ totalWidth,
1072
1086
  });
1073
1087
  };
1074
- this.handleEndScrollbarWidth = (endScrollbarWidth) => {
1088
+ this.handleClientWidth = (clientWidth) => {
1075
1089
  this.setState({
1076
- endScrollbarWidth
1090
+ clientWidth,
1077
1091
  });
1078
1092
  };
1079
1093
  this.handleTimeScrollRequest = (scrollTime) => {
1080
1094
  this.scrollTime = scrollTime;
1081
1095
  this.applyTimeScroll();
1082
1096
  };
1083
- this.handleTimeScrollEnd = ({ isUser }) => {
1097
+ this.handleTimeScrollEnd = (isUser) => {
1084
1098
  if (isUser) {
1085
1099
  this.scrollTime = null;
1086
1100
  }
@@ -1100,6 +1114,10 @@ class TimelineView extends DateComponent {
1100
1114
  render() {
1101
1115
  const { props, state, context } = this;
1102
1116
  const { options } = context;
1117
+ const { totalWidth, clientWidth } = state;
1118
+ const endScrollbarWidth = (totalWidth != null && clientWidth != null)
1119
+ ? totalWidth - clientWidth
1120
+ : undefined;
1103
1121
  /* date */
1104
1122
  const tDateProfile = this.tDateProfile = this.buildTimelineDateProfile(props.dateProfile, context.dateEnv, options, context.dateProfileGenerator);
1105
1123
  const { cellRows } = tDateProfile;
@@ -1110,8 +1128,11 @@ class TimelineView extends DateComponent {
1110
1128
  const stickyFooterScrollbar = !props.forPrint && getStickyFooterScrollbar(options);
1111
1129
  /* table positions */
1112
1130
  const [canvasWidth, slotWidth] = this.computeSlotWidth(tDateProfile.slotCnt, tDateProfile.slotsPerLabel, options.slotMinWidth, state.slotInnerWidth, // is ACTUALLY the label width. rename?
1113
- state.clientWidth);
1131
+ clientWidth);
1114
1132
  this.slotWidth = slotWidth;
1133
+ /* sliced */
1134
+ let slicedProps = this.slicer.sliceProps(props, props.dateProfile, tDateProfile.isTimeScale ? null : options.nextDayThreshold, context, // wish we didn't have to pass in the rest of the args...
1135
+ props.dateProfile, context.dateProfileGenerator, tDateProfile, context.dateEnv);
1115
1136
  return (createElement(NowTimer, { unit: timerUnit }, (nowDate, todayRange) => {
1116
1137
  const enableNowIndicator = // TODO: DRY
1117
1138
  options.nowIndicator &&
@@ -1131,18 +1152,30 @@ class TimelineView extends DateComponent {
1131
1152
  return (createElement(TimelineHeaderRow, { key: rowLevel, dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: nowDate, todayRange: todayRange, rowLevel: rowLevel, isLastRow: isLast, cells: cells, slotWidth: slotWidth, innerWidthRef: this.headerRowInnerWidthMap.createRef(rowLevel) }));
1132
1153
  }),
1133
1154
  enableNowIndicator && (createElement(TimelineNowIndicatorArrow, { tDateProfile: tDateProfile, nowDate: nowDate, slotWidth: slotWidth }))),
1134
- Boolean(state.endScrollbarWidth) && (createElement("div", { className: 'fc-border-s fc-filler', style: { minWidth: state.endScrollbarWidth } }))),
1135
- createElement(Scroller, { vertical: verticalScrolling, horizontal: true, hideScrollbars: props.forPrint, className: joinClassNames('fc-timeline-body fc-flex-col', verticalScrolling && 'fc-liquid'), ref: this.bodyScrollerRef, clientWidthRef: this.handleClientWidth, endScrollbarWidthRef: this.handleEndScrollbarWidth },
1155
+ Boolean(endScrollbarWidth) && (createElement("div", { className: 'fc-border-s fc-filler', style: { minWidth: endScrollbarWidth } }))),
1156
+ createElement(Scroller, { vertical: verticalScrolling, horizontal: true, hideScrollbars: stickyFooterScrollbar ||
1157
+ props.forPrint // prevents blank space in print-view on Safari
1158
+ , className: joinClassNames('fc-timeline-body fc-flex-col', verticalScrolling && 'fc-liquid'), ref: this.bodyScrollerRef, clientWidthRef: this.handleClientWidth },
1136
1159
  createElement("div", { "aria-label": options.eventsHint, className: "fc-rel fc-grow", style: { width: canvasWidth }, ref: this.handeBodyEl },
1137
1160
  createElement(TimelineSlats, { dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: nowDate, todayRange: todayRange,
1138
1161
  // ref
1139
1162
  innerWidthRef: this.handleBodySlotInnerWidth,
1140
1163
  // dimensions
1141
1164
  slotWidth: slotWidth }),
1142
- createElement(TimelineLane, { dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: nowDate, todayRange: todayRange, nextDayThreshold: options.nextDayThreshold, eventStore: props.eventStore, eventUiBases: props.eventUiBases, businessHours: props.businessHours, dateSelection: props.dateSelection, eventDrag: props.eventDrag, eventResize: props.eventResize, eventSelection: props.eventSelection, slotWidth: slotWidth }),
1165
+ createElement(TimelineBg, { tDateProfile: tDateProfile, nowDate: nowDate, todayRange: todayRange,
1166
+ // content
1167
+ bgEventSegs: slicedProps.bgEventSegs, businessHourSegs: slicedProps.businessHourSegs, dateSelectionSegs: slicedProps.dateSelectionSegs, eventResizeSegs: slicedProps.eventResize ? slicedProps.eventResize.segs : null,
1168
+ // dimensions
1169
+ slotWidth: slotWidth }),
1170
+ createElement(TimelineFg, { dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: nowDate, todayRange: todayRange,
1171
+ // content
1172
+ fgEventSegs: slicedProps.fgEventSegs, eventDrag: slicedProps.eventDrag, eventResize: slicedProps.eventResize, eventSelection: slicedProps.eventSelection,
1173
+ // dimensions
1174
+ slotWidth: slotWidth }),
1175
+ createElement("div", { className: 'fc-timeline-lane-footer' }),
1143
1176
  enableNowIndicator && (createElement(TimelineNowIndicatorLine, { tDateProfile: tDateProfile, nowDate: nowDate, slotWidth: slotWidth })))),
1144
- stickyFooterScrollbar && (createElement(Scroller, { ref: this.footerScrollerRef, horizontal: true },
1145
- createElement("div", { style: { width: canvasWidth } })))));
1177
+ Boolean(stickyFooterScrollbar) && (createElement(FooterScrollbar, { isSticky: true, canvasWidth: canvasWidth, scrollerRef: this.footerScrollerRef })),
1178
+ createElement(Ruler, { widthRef: this.handleTotalWidth })));
1146
1179
  }));
1147
1180
  }
1148
1181
  // Lifecycle
@@ -1227,8 +1260,7 @@ class TimelineView extends DateComponent {
1227
1260
  top: 0,
1228
1261
  bottom: elHeight,
1229
1262
  },
1230
- // HACK. TODO: This is expensive to do every hit-query
1231
- dayEl: this.bodyEl.querySelectorAll('.fc-timeline-slot')[slatIndex],
1263
+ getDayEl: () => getTimelineSlotEl(this.bodyEl, slatIndex),
1232
1264
  layer: 0,
1233
1265
  };
1234
1266
  }
@@ -1236,7 +1268,7 @@ class TimelineView extends DateComponent {
1236
1268
  }
1237
1269
  }
1238
1270
 
1239
- var css_248z = ".fc-timeline-slots{z-index:1}.fc-timeline-events{position:relative;z-index:2}.fc-timeline-slot-minor{border-style:dotted}.fc-timeline-events-overlap-enabled{padding-bottom:10px}.fc-timeline-event{border-radius:0;font-size:var(--fc-small-font-size);margin-bottom:1px}.fc-direction-ltr .fc-timeline-event.fc-event-end{margin-right:1px}.fc-direction-rtl .fc-timeline-event.fc-event-end{margin-left:1px}.fc-timeline-event .fc-event-inner{align-items:center;display:flex;flex-direction:row;padding:2px 1px}.fc-timeline-event-spacious .fc-event-inner{padding-bottom:5px;padding-top:5px}.fc-timeline-event .fc-event-time{font-weight:700}.fc-timeline-event .fc-event-time,.fc-timeline-event .fc-event-title{padding:0 2px}.fc-timeline-event:not(.fc-event-end) .fc-event-inner:after,.fc-timeline-event:not(.fc-event-start) .fc-event-inner:before{border-color:transparent #000;border-style:solid;border-width:5px;content:\"\";flex-grow:0;flex-shrink:0;height:0;margin:0 1px;opacity:.5;width:0}.fc-direction-ltr .fc-timeline-event:not(.fc-event-start) .fc-event-inner:before,.fc-direction-rtl .fc-timeline-event:not(.fc-event-end) .fc-event-inner:after{border-left:0}.fc-direction-ltr .fc-timeline-event:not(.fc-event-end) .fc-event-inner:after,.fc-direction-rtl .fc-timeline-event:not(.fc-event-start) .fc-event-inner:before{border-right:0}.fc-timeline-more-link{align-items:flex-start;background:var(--fc-more-link-bg-color);color:var(--fc-more-link-text-color);cursor:pointer;display:flex;flex-direction:column;font-size:var(--fc-small-font-size);padding:1px}.fc-direction-ltr .fc-timeline-more-link{margin-right:1px}.fc-direction-rtl .fc-timeline-more-link{margin-left:1px}.fc-timeline-more-link-inner{padding:2px}.fc-timeline-now-indicator-container{bottom:0;left:0;overflow:hidden;pointer-events:none;position:absolute;right:0;top:0;z-index:4}.fc-timeline-now-indicator-arrow{border-bottom-style:solid;border-bottom-width:0;border-color:var(--fc-now-indicator-color);border-left:5px solid transparent;border-right:5px solid transparent;border-top-style:solid;border-top-width:6px;height:0;margin:0 -5px;position:absolute;top:0;width:0}.fc-timeline-now-indicator-line{border-left:1px solid var(--fc-now-indicator-color);bottom:0;position:absolute;top:0}";
1271
+ var css_248z = ".fc-timeline-lane-footer{padding-bottom:10px}.fc-timeline-overlap-disabled .fc-timeline-lane-footer{padding-bottom:0}.fc-timeline-slot-minor{border-style:dotted}.fc-timeline-event{border-radius:0;font-size:var(--fc-small-font-size);margin-bottom:1px}.fc-direction-ltr .fc-timeline-event.fc-event-end{margin-right:1px}.fc-direction-rtl .fc-timeline-event.fc-event-end{margin-left:1px}.fc-timeline-event .fc-event-inner{align-items:center;display:flex;flex-direction:row;padding:2px 1px}.fc-timeline-event-spacious .fc-event-inner{padding-bottom:5px;padding-top:5px}.fc-timeline-event .fc-event-time{font-weight:700}.fc-timeline-event .fc-event-time,.fc-timeline-event .fc-event-title{padding:0 2px}.fc-timeline-event:not(.fc-event-end) .fc-event-inner:after,.fc-timeline-event:not(.fc-event-start) .fc-event-inner:before{border-color:transparent #000;border-style:solid;border-width:5px;content:\"\";flex-grow:0;flex-shrink:0;height:0;margin:0 1px;opacity:.5;width:0}.fc-direction-ltr .fc-timeline-event:not(.fc-event-start) .fc-event-inner:before,.fc-direction-rtl .fc-timeline-event:not(.fc-event-end) .fc-event-inner:after{border-left:0}.fc-direction-ltr .fc-timeline-event:not(.fc-event-end) .fc-event-inner:after,.fc-direction-rtl .fc-timeline-event:not(.fc-event-start) .fc-event-inner:before{border-right:0}.fc-timeline-more-link{align-items:flex-start;background:var(--fc-more-link-bg-color);color:var(--fc-more-link-text-color);cursor:pointer;display:flex;flex-direction:column;font-size:var(--fc-small-font-size);padding:1px}.fc-direction-ltr .fc-timeline-more-link{margin-right:1px}.fc-direction-rtl .fc-timeline-more-link{margin-left:1px}.fc-timeline-more-link-inner{padding:2px}.fc-timeline-now-indicator-container{bottom:0;left:0;overflow:hidden;pointer-events:none;position:absolute;right:0;top:0;z-index:2}.fc-timeline-now-indicator-arrow{border-bottom-style:solid;border-bottom-width:0;border-color:var(--fc-now-indicator-color);border-left:5px solid transparent;border-right:5px solid transparent;border-top-style:solid;border-top-width:6px;height:0;margin:0 -5px;position:absolute;top:0;width:0}.fc-timeline-now-indicator-line{border-left:1px solid var(--fc-now-indicator-color);bottom:0;position:absolute;top:0}";
1240
1272
  injectStyles(css_248z);
1241
1273
 
1242
- export { TimelineHeaderRow, TimelineLane, TimelineLaneBg, TimelineLaneSlicer, TimelineNowIndicatorArrow, TimelineNowIndicatorLine, TimelineSlats, TimelineView, buildTimelineDateProfile, computeSlotWidth, createHorizontalStyle, createVerticalStyle, timeToCoord };
1274
+ export { TimelineBg, TimelineFg, TimelineHeaderRow, TimelineLaneSlicer, TimelineNowIndicatorArrow, TimelineNowIndicatorLine, TimelineSlats, TimelineView, buildTimelineDateProfile, computeSlotWidth, createHorizontalStyle, createVerticalStyle, getTimelineSlotEl, timeToCoord };