@fullcalendar/timeline 7.0.0-beta.3 → 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, watchSize, buildNavLinkAttrs, 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
@@ -460,14 +462,16 @@ class TimelineSlatCell extends BaseComponent {
460
462
  'fc-timeline-slot-major' :
461
463
  'fc-timeline-slot-minor'), 'fc-timeline-slot-lane fc-cell fc-flex-col fc-align-start', props.borderStart && 'fc-border-s', props.isDay ?
462
464
  getDayClassName(dateMeta) :
463
- getSlotClassName(dateMeta)), attrs: {
464
- 'data-date': dateEnv.formatIso(date, {
465
+ getSlotClassName(dateMeta)), attrs: Object.assign({ 'data-date': dateEnv.formatIso(date, {
465
466
  omitTimeZoneOffset: true,
466
467
  omitTime: !tDateProfile.isTimeScale,
467
- }),
468
- }, style: {
468
+ }) }, (dateMeta.isToday ? { 'aria-current': 'date' } : {})), style: {
469
469
  width: props.width,
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', 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 }))));
471
475
  }
472
476
  componentDidMount() {
473
477
  const innerEl = this.innerElRef.current;
@@ -502,7 +506,7 @@ class TimelineSlats extends BaseComponent {
502
506
  let { tDateProfile, slotWidth } = props;
503
507
  let { slotDates, isWeekStarts } = tDateProfile;
504
508
  let isDay = !tDateProfile.isTimeScale && !tDateProfile.largeUnit;
505
- return (createElement("div", { 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) => {
506
510
  let key = slotDate.toISOString();
507
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),
508
512
  // ref
@@ -513,50 +517,132 @@ class TimelineSlats extends BaseComponent {
513
517
  }
514
518
  }
515
519
 
516
- /*
517
- TODO: rename this file!
518
- */
519
- // returned value is between 0 and the number of snaps
520
- function computeDateSnapCoverage$1(date, tDateProfile, dateEnv) {
521
- let snapDiff = dateEnv.countDurationsBetween(tDateProfile.normalizedRange.start, date, tDateProfile.snapDuration);
522
- if (snapDiff < 0) {
523
- 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();
524
527
  }
525
- if (snapDiff >= tDateProfile.snapDiffToIndex.length) {
526
- 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 }))));
527
561
  }
528
- let snapDiffInt = Math.floor(snapDiff);
529
- let snapCoverage = tDateProfile.snapDiffToIndex[snapDiffInt];
530
- if (isInt(snapCoverage)) { // not an in-between value
531
- 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
+ });
532
574
  }
533
- else {
534
- // a fractional value, meaning the date is not visible
535
- // always round up in this case. works for start AND end dates in a range.
536
- snapCoverage = Math.ceil(snapCoverage);
575
+ componentWillUnmount() {
576
+ const { props } = this;
577
+ this.detachSize();
578
+ setRef(props.innerWidthRef, null);
579
+ setRef(props.innerHeightRef, null);
537
580
  }
538
- return snapCoverage;
539
581
  }
540
- /*
541
- TODO: DRY up with elsewhere?
542
- */
543
- function horizontalsToCss(hcoord, isRtl) {
544
- if (!hcoord) {
545
- return {};
546
- }
547
- if (isRtl) {
548
- return { right: hcoord.start, width: hcoord.size };
549
- }
550
- else {
551
- return { left: hcoord.start, width: hcoord.size };
552
- }
582
+ // Utils
583
+ // -------------------------------------------------------------------------------------------------
584
+ function renderInnerContent(renderProps) {
585
+ return renderProps.text;
553
586
  }
554
- function horizontalCoordToCss(start, isRtl) {
555
- if (isRtl) {
556
- 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
+ };
557
624
  }
558
- else {
559
- 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);
560
646
  }
561
647
  }
562
648
 
@@ -611,13 +697,38 @@ time, dateEnv, dateProfile, tDateProfile, slowWidth) {
611
697
  }
612
698
  function dateToCoord(// pixels
613
699
  date, dateEnv, tDateProfile, slotWidth) {
614
- let snapCoverage = computeDateSnapCoverage(date, tDateProfile, dateEnv);
700
+ let snapCoverage = computeDateSnapCoverage$1(date, tDateProfile, dateEnv);
615
701
  let slotCoverage = snapCoverage / tDateProfile.snapsPerSlot;
616
702
  return slotCoverage * slotWidth;
617
703
  }
618
704
  /*
619
705
  returned value is between 0 and the number of snaps
620
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
621
732
  function computeDateSnapCoverage(date, tDateProfile, dateEnv) {
622
733
  let snapDiff = dateEnv.countDurationsBetween(tDateProfile.normalizedRange.start, date, tDateProfile.snapDuration);
623
734
  if (snapDiff < 0) {
@@ -636,7 +747,107 @@ function computeDateSnapCoverage(date, tDateProfile, dateEnv) {
636
747
  // always round up in this case. works for start AND end dates in a range.
637
748
  snapCoverage = Math.ceil(snapCoverage);
638
749
  }
639
- 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
+ }
640
851
  }
641
852
 
642
853
  function computeManySegHorizontals(segs, segMinWidth, dateEnv, tDateProfile, slotWidth) {
@@ -703,83 +914,6 @@ hiddenGroupHeights, strictOrder, maxDepth) {
703
914
  ];
704
915
  }
705
916
 
706
- class TimelineLaneBg extends BaseComponent {
707
- render() {
708
- let { props } = this;
709
- let highlightSeg = [].concat(props.eventResizeSegs, props.dateSelectionSegs);
710
- return (createElement(Fragment, null,
711
- this.renderSegs(props.businessHourSegs || [], 'non-business'),
712
- this.renderSegs(props.bgEventSegs || [], 'bg-event'),
713
- this.renderSegs(highlightSeg, 'highlight')));
714
- }
715
- renderSegs(segs, fillType) {
716
- let { tDateProfile, todayRange, nowDate, slotWidth } = this.props;
717
- let { dateEnv, isRtl } = this.context;
718
- return (createElement(Fragment, null, segs.map((seg) => {
719
- let hStyle; // TODO
720
- if (slotWidth != null) {
721
- let segHorizontal = computeSegHorizontals(seg, undefined, dateEnv, tDateProfile, slotWidth);
722
- hStyle = horizontalsToCss(segHorizontal, isRtl);
723
- }
724
- return (createElement("div", { key: buildEventRangeKey(seg.eventRange), className: "fc-fill-y", style: hStyle }, fillType === 'bg-event' ?
725
- createElement(BgEvent, Object.assign({ eventRange: seg.eventRange, isStart: seg.isStart, isEnd: seg.isEnd }, getEventRangeMeta(seg.eventRange, todayRange, nowDate))) : (renderFill(fillType))));
726
- })));
727
- }
728
- }
729
-
730
- class TimelineLaneSlicer extends Slicer {
731
- sliceRange(origRange, dateProfile, dateProfileGenerator, tDateProfile, dateEnv) {
732
- let normalRange = normalizeRange(origRange, tDateProfile, dateEnv);
733
- let segs = [];
734
- // protect against when the span is entirely in an invalid date region
735
- if (computeDateSnapCoverage$1(normalRange.start, tDateProfile, dateEnv)
736
- < computeDateSnapCoverage$1(normalRange.end, tDateProfile, dateEnv)) {
737
- // intersect the footprint's range with the grid's range
738
- let slicedRange = intersectRanges(normalRange, tDateProfile.normalizedRange);
739
- if (slicedRange) {
740
- segs.push({
741
- startDate: slicedRange.start,
742
- endDate: slicedRange.end,
743
- isStart: slicedRange.start.valueOf() === normalRange.start.valueOf()
744
- && isValidDate(slicedRange.start, tDateProfile, dateProfile, dateProfileGenerator),
745
- isEnd: slicedRange.end.valueOf() === normalRange.end.valueOf()
746
- && isValidDate(addMs(slicedRange.end, -1), tDateProfile, dateProfile, dateProfileGenerator),
747
- });
748
- }
749
- }
750
- return segs;
751
- }
752
- }
753
-
754
- const DEFAULT_TIME_FORMAT = createFormatter({
755
- hour: 'numeric',
756
- minute: '2-digit',
757
- omitZeroMinute: true,
758
- meridiem: 'narrow',
759
- });
760
- class TimelineEvent extends BaseComponent {
761
- render() {
762
- let { props, context } = this;
763
- let { options } = context;
764
- return (createElement(StandardEvent, Object.assign({}, props, { className: joinClassNames('fc-timeline-event', options.eventOverlap === false // TODO: fix bad default
765
- && 'fc-timeline-event-spacious', 'fc-h-event'), defaultTimeFormat: DEFAULT_TIME_FORMAT, defaultDisplayEventTime: !props.isTimeScale })));
766
- }
767
- }
768
-
769
- class TimelineLaneMoreLink extends BaseComponent {
770
- render() {
771
- let { props } = this;
772
- let { hiddenSegs, resourceId, forcedInvisibleMap } = props;
773
- let dateSpanProps = resourceId ? { resourceId } : {};
774
- 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) => {
775
- let { eventRange } = seg;
776
- let instanceId = eventRange.instance.instanceId;
777
- return (createElement("div", { key: instanceId, style: { visibility: forcedInvisibleMap[instanceId] ? 'hidden' : '' } },
778
- 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)))));
779
- }))) }, (InnerContent) => (createElement(InnerContent, { tag: "div", className: 'fc-timeline-more-link-inner fc-sticky-s' }))));
780
- }
781
- }
782
-
783
917
  /*
784
918
  TODO: make DRY with other Event Harnesses
785
919
  */
@@ -805,10 +939,7 @@ class TimelineEventHarness extends Component {
805
939
  }
806
940
  }
807
941
 
808
- /*
809
- TODO: split TimelineLaneBg and TimelineLaneFg?
810
- */
811
- class TimelineLane extends BaseComponent {
942
+ class TimelineFg extends BaseComponent {
812
943
  constructor() {
813
944
  super(...arguments);
814
945
  // memo
@@ -820,8 +951,6 @@ class TimelineLane extends BaseComponent {
820
951
  this.moreLinkHeightRefMap = new RefMap(() => {
821
952
  afterSize(this.handleMoreLinkHeights);
822
953
  });
823
- // internal
824
- this.slicer = new TimelineLaneSlicer();
825
954
  this.handleMoreLinkHeights = () => {
826
955
  this.setState({ moreLinkHeightRev: this.moreLinkHeightRefMap.rev }); // will trigger rerender
827
956
  };
@@ -835,39 +964,30 @@ class TimelineLane extends BaseComponent {
835
964
  render() {
836
965
  let { props, context, segHeightRefMap, moreLinkHeightRefMap } = this;
837
966
  let { options } = context;
838
- let { dateProfile, tDateProfile } = props;
839
- 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...
840
- dateProfile, context.dateProfileGenerator, tDateProfile, context.dateEnv);
841
- let mirrorSegs = (slicedProps.eventDrag ? slicedProps.eventDrag.segs : null) ||
842
- (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) ||
843
970
  [];
844
- let fgSegs = this.sortEventSegs(slicedProps.fgEventSegs, options.eventOrder);
971
+ let fgSegs = this.sortEventSegs(props.fgEventSegs, options.eventOrder);
845
972
  let fgSegHorizontals = props.slotWidth != null
846
973
  ? computeManySegHorizontals(fgSegs, options.eventMinWidth, context.dateEnv, tDateProfile, props.slotWidth)
847
974
  : {};
848
975
  let [fgSegTops, hiddenGroups, hiddenGroupTops, totalHeight] = computeFgSegPlacements(fgSegs, fgSegHorizontals, segHeightRefMap.current, moreLinkHeightRefMap.current, options.eventOrderStrict, options.eventMaxStack);
976
+ this.totalHeight = totalHeight;
849
977
  let forcedInvisibleMap = // TODO: more convenient/DRY
850
- (slicedProps.eventDrag ? slicedProps.eventDrag.affectedInstances : null) ||
851
- (slicedProps.eventResize ? slicedProps.eventResize.affectedInstances : null) ||
978
+ (props.eventDrag ? props.eventDrag.affectedInstances : null) ||
979
+ (props.eventResize ? props.eventResize.affectedInstances : null) ||
852
980
  {};
853
- return (createElement(Fragment, null,
854
- createElement(TimelineLaneBg, { tDateProfile: tDateProfile, nowDate: props.nowDate, todayRange: props.todayRange,
855
- // content
856
- bgEventSegs: slicedProps.bgEventSegs, businessHourSegs: slicedProps.businessHourSegs, dateSelectionSegs: slicedProps.dateSelectionSegs, eventResizeSegs: slicedProps.eventResize ? slicedProps.eventResize.segs : [] /* bad new empty array? */,
857
- // dimensions
858
- slotWidth: props.slotWidth }),
859
- createElement("div", { className: joinClassNames('fc-timeline-events', options.eventOverlap === false // TODO: fix bad default
860
- ? 'fc-timeline-events-overlap-disabled'
861
- : 'fc-timeline-events-overlap-enabled', 'fc-content-box'), style: { height: totalHeight } },
862
- this.renderFgSegs(fgSegs, fgSegHorizontals, fgSegTops, forcedInvisibleMap, hiddenGroups, hiddenGroupTops, false, // isDragging
863
- false, // isResizing
864
- false),
865
- this.renderFgSegs(mirrorSegs, props.slotWidth // TODO: memoize
866
- ? computeManySegHorizontals(mirrorSegs, options.eventMinWidth, context.dateEnv, tDateProfile, props.slotWidth)
867
- : {}, fgSegTops, {}, // forcedInvisibleMap
868
- [], // hiddenGroups
869
- new Map(), // hiddenGroupTops
870
- 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)));
871
991
  }
872
992
  renderFgSegs(segs, segHorizontals, segTops, forcedInvisibleMap, hiddenGroups, hiddenGroupTops, isDragging, isResizing, isDateSelecting) {
873
993
  let { props, context, segHeightRefMap, moreLinkHeightRefMap } = this;
@@ -889,158 +1009,43 @@ class TimelineLane extends BaseComponent {
889
1009
  }, context.isRtl)), heightRef: moreLinkHeightRefMap.createRef(hiddenGroup.key) },
890
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 }))))));
891
1011
  }
892
- }
893
-
894
- class TimelineHeaderCell extends BaseComponent {
895
- constructor() {
896
- super(...arguments);
897
- // memo
898
- this.refineRenderProps = memoizeObjArg(refineRenderProps);
899
- this.buildCellNavLinkAttrs = memoize(buildCellNavLinkAttrs);
900
- // ref
901
- this.innerElRef = createRef();
902
- }
903
- render() {
904
- let { props, context } = this;
905
- let { dateEnv, options } = context;
906
- let { cell, dateProfile, tDateProfile } = props;
907
- // the cell.rowUnit is f'd
908
- // giving 'month' for a 3-day view
909
- // workaround: to infer day, do NOT time
910
- let dateMeta = getDateMeta(cell.date, props.todayRange, props.nowDate, dateProfile);
911
- let renderProps = this.refineRenderProps({
912
- level: props.rowLevel,
913
- dateMarker: cell.date,
914
- text: cell.text,
915
- dateEnv: context.dateEnv,
916
- viewApi: context.viewApi,
917
- });
918
- 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
919
- 'fc-header-cell fc-cell fc-flex-col fc-justify-center', props.borderStart && 'fc-border-s', props.isCentered ? 'fc-align-center' : 'fc-align-start',
920
- // TODO: so slot classnames for week/month/bigger. see note above about rowUnit
921
- cell.rowUnit === 'time' ?
922
- getSlotClassName(dateMeta) :
923
- getDayClassName(dateMeta)), attrs: {
924
- 'data-date': dateEnv.formatIso(cell.date, {
925
- omitTime: !tDateProfile.isTimeScale,
926
- omitTimeZoneOffset: true,
927
- }),
928
- }, style: {
929
- width: props.slotWidth != null
930
- ? props.slotWidth * cell.colspan
931
- : undefined,
932
- }, renderProps: renderProps, generatorName: "slotLabelContent", customGenerator: options.slotLabelContent, defaultGenerator: renderInnerContent, classNameGenerator: options.slotLabelClassNames, didMount: options.slotLabelDidMount, willUnmount: options.slotLabelWillUnmount }, (InnerContent) => (createElement(InnerContent, { tag: "a", attrs: this.buildCellNavLinkAttrs(context, cell.date, cell.rowUnit), 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 buildCellNavLinkAttrs(context, cellDate, rowUnit) {
957
- return (rowUnit && rowUnit !== 'time')
958
- ? buildNavLinkAttrs(context, cellDate, rowUnit)
959
- : {};
960
- }
961
- function renderInnerContent(renderProps) {
962
- return renderProps.text;
963
- }
964
- function refineRenderProps(input) {
965
- return {
966
- level: input.level,
967
- date: input.dateEnv.toDate(input.dateMarker),
968
- view: input.viewApi,
969
- text: input.text,
970
- };
971
- }
972
-
973
- class TimelineHeaderRow extends BaseComponent {
974
- constructor() {
975
- super(...arguments);
976
- // refs
977
- this.innerWidthRefMap = new RefMap(() => {
978
- afterSize(this.handleInnerWidths);
979
- });
980
- this.innerHeightRefMap = new RefMap(() => {
981
- afterSize(this.handleInnerHeights);
982
- });
983
- this.handleInnerWidths = () => {
984
- const innerWidthMap = this.innerWidthRefMap.current;
985
- let max = 0;
986
- for (const innerWidth of innerWidthMap.values()) {
987
- max = Math.max(max, innerWidth);
988
- }
989
- // TODO: ensure not equal?
990
- setRef(this.props.innerWidthRef, max);
991
- };
992
- this.handleInnerHeights = () => {
993
- const innerHeightMap = this.innerHeightRefMap.current;
994
- let max = 0;
995
- for (const innerHeight of innerHeightMap.values()) {
996
- max = Math.max(max, innerHeight);
997
- }
998
- // TODO: ensure not equal?
999
- setRef(this.props.innerHeighRef, max);
1000
- };
1012
+ /*
1013
+ componentDidMount(): void {
1014
+ // might want to do firedTotalHeight, but won't be ready on first render
1001
1015
  }
1002
- render() {
1003
- const { props, innerWidthRefMap, innerHeightRefMap } = this;
1004
- const isCentered = !(props.tDateProfile.isTimeScale && props.isLastRow);
1005
- const isSticky = !props.isLastRow;
1006
- return (createElement("div", { className: joinClassNames('fc-flex-row fc-grow', // TODO: move fc-grow to parent?
1007
- !props.isLastRow && 'fc-border-b') }, props.cells.map((cell, cellI) => {
1008
- // TODO: make this part of the cell obj?
1009
- // TODO: rowUnit seems wrong sometimes. says 'month' when it should be day
1010
- // TODO: rowUnit is relevant to whole row. put it on a row object, not the cells
1011
- // TODO: use rowUnit to key the Row itself?
1012
- const key = cell.rowUnit + ':' + cell.date.toISOString();
1013
- 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),
1014
- // refs
1015
- innerWidthRef: innerWidthRefMap.createRef(key), innerHeightRef: innerHeightRefMap.createRef(key),
1016
- // dimensions
1017
- slotWidth: props.slotWidth }));
1018
- })));
1016
+ */
1017
+ componentDidUpdate() {
1018
+ if (this.totalHeight !== this.firedTotalHeight) {
1019
+ this.firedTotalHeight = this.totalHeight;
1020
+ setRef(this.props.heightRef, this.totalHeight);
1021
+ }
1019
1022
  }
1020
1023
  componentWillUnmount() {
1021
- setRef(this.props.innerWidthRef, null);
1022
- setRef(this.props.innerHeighRef, null);
1024
+ setRef(this.props.heightRef, null);
1023
1025
  }
1024
1026
  }
1025
1027
 
1026
- class TimelineNowIndicatorLine extends BaseComponent {
1028
+ class TimelineBg extends BaseComponent {
1027
1029
  render() {
1028
- const { props, context } = this;
1029
- return (createElement("div", { className: "fc-timeline-now-indicator-container" },
1030
- createElement(NowIndicatorContainer // TODO: make separate component?
1031
- , { className: 'fc-timeline-now-indicator-line', style: props.slotWidth != null
1032
- ? horizontalCoordToCss(dateToCoord(props.nowDate, context.dateEnv, props.tDateProfile, props.slotWidth), context.isRtl)
1033
- : {}, 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')));
1034
1036
  }
1035
- }
1036
-
1037
- class TimelineNowIndicatorArrow extends BaseComponent {
1038
- render() {
1039
- const { props, context } = this;
1040
- return (createElement("div", { className: "fc-timeline-now-indicator-container" },
1041
- createElement(NowIndicatorContainer, { className: 'fc-timeline-now-indicator-arrow', style: props.slotWidth != null
1042
- ? horizontalCoordToCss(dateToCoord(props.nowDate, context.dateEnv, props.tDateProfile, props.slotWidth), context.isRtl)
1043
- : {}, 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
+ })));
1044
1049
  }
1045
1050
  }
1046
1051
 
@@ -1058,6 +1063,7 @@ class TimelineView extends DateComponent {
1058
1063
  afterSize(this.handleSlotInnerWidths);
1059
1064
  });
1060
1065
  this.scrollTime = null;
1066
+ this.slicer = new TimelineLaneSlicer();
1061
1067
  // Sizing
1062
1068
  // -----------------------------------------------------------------------------------------------
1063
1069
  this.handleBodySlotInnerWidth = (innerWidth) => {
@@ -1065,39 +1071,34 @@ class TimelineView extends DateComponent {
1065
1071
  afterSize(this.handleSlotInnerWidths);
1066
1072
  };
1067
1073
  this.handleSlotInnerWidths = () => {
1068
- const { state } = this;
1069
- const slotInnerWidth = Math.max(this.headerRowInnerWidthMap.current.get(this.tDateProfile.cellRows.length - 1) || 0, this.bodySlotInnerWidth);
1070
- if (state.slotInnerWidth !== slotInnerWidth) {
1071
- 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
+ }
1072
1081
  }
1073
1082
  };
1074
- this.handleClientWidth = (clientWidth) => {
1083
+ this.handleTotalWidth = (totalWidth) => {
1075
1084
  this.setState({
1076
- clientWidth,
1085
+ totalWidth,
1077
1086
  });
1078
1087
  };
1079
- this.handleEndScrollbarWidth = (endScrollbarWidth) => {
1088
+ this.handleClientWidth = (clientWidth) => {
1080
1089
  this.setState({
1081
- endScrollbarWidth
1090
+ clientWidth,
1082
1091
  });
1083
1092
  };
1084
- this.handleTimeScroll = (scrollTime) => {
1093
+ this.handleTimeScrollRequest = (scrollTime) => {
1085
1094
  this.scrollTime = scrollTime;
1086
- this.updateScroll();
1095
+ this.applyTimeScroll();
1087
1096
  };
1088
- this.updateScroll = () => {
1089
- const { props, context, tDateProfile, scrollTime, slotWidth } = this;
1090
- if (scrollTime != null && slotWidth != null) {
1091
- let x = timeToCoord(scrollTime, context.dateEnv, props.dateProfile, tDateProfile, slotWidth);
1092
- if (x) {
1093
- x += 1; // overcome border. TODO: DRY this up
1094
- }
1095
- this.syncedScroller.scrollTo({ x });
1097
+ this.handleTimeScrollEnd = (isUser) => {
1098
+ if (isUser) {
1099
+ this.scrollTime = null;
1096
1100
  }
1097
1101
  };
1098
- this.clearScroll = () => {
1099
- this.scrollTime = null;
1100
- };
1101
1102
  // Hit System
1102
1103
  // -----------------------------------------------------------------------------------------------
1103
1104
  this.handeBodyEl = (el) => {
@@ -1113,6 +1114,10 @@ class TimelineView extends DateComponent {
1113
1114
  render() {
1114
1115
  const { props, state, context } = this;
1115
1116
  const { options } = context;
1117
+ const { totalWidth, clientWidth } = state;
1118
+ const endScrollbarWidth = (totalWidth != null && clientWidth != null)
1119
+ ? totalWidth - clientWidth
1120
+ : undefined;
1116
1121
  /* date */
1117
1122
  const tDateProfile = this.tDateProfile = this.buildTimelineDateProfile(props.dateProfile, context.dateEnv, options, context.dateProfileGenerator);
1118
1123
  const { cellRows } = tDateProfile;
@@ -1123,8 +1128,11 @@ class TimelineView extends DateComponent {
1123
1128
  const stickyFooterScrollbar = !props.forPrint && getStickyFooterScrollbar(options);
1124
1129
  /* table positions */
1125
1130
  const [canvasWidth, slotWidth] = this.computeSlotWidth(tDateProfile.slotCnt, tDateProfile.slotsPerLabel, options.slotMinWidth, state.slotInnerWidth, // is ACTUALLY the label width. rename?
1126
- state.clientWidth);
1131
+ clientWidth);
1127
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);
1128
1136
  return (createElement(NowTimer, { unit: timerUnit }, (nowDate, todayRange) => {
1129
1137
  const enableNowIndicator = // TODO: DRY
1130
1138
  options.nowIndicator &&
@@ -1144,18 +1152,30 @@ class TimelineView extends DateComponent {
1144
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) }));
1145
1153
  }),
1146
1154
  enableNowIndicator && (createElement(TimelineNowIndicatorArrow, { tDateProfile: tDateProfile, nowDate: nowDate, slotWidth: slotWidth }))),
1147
- Boolean(state.endScrollbarWidth) && (createElement("div", { className: 'fc-border-s fc-filler', style: { minWidth: state.endScrollbarWidth } }))),
1148
- 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 },
1149
- createElement("div", { className: "fc-rel fc-grow", style: { width: canvasWidth }, ref: this.handeBodyEl },
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 },
1159
+ createElement("div", { "aria-label": options.eventsHint, className: "fc-rel fc-grow", style: { width: canvasWidth }, ref: this.handeBodyEl },
1150
1160
  createElement(TimelineSlats, { dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: nowDate, todayRange: todayRange,
1151
1161
  // ref
1152
1162
  innerWidthRef: this.handleBodySlotInnerWidth,
1153
1163
  // dimensions
1154
1164
  slotWidth: slotWidth }),
1155
- 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' }),
1156
1176
  enableNowIndicator && (createElement(TimelineNowIndicatorLine, { tDateProfile: tDateProfile, nowDate: nowDate, slotWidth: slotWidth })))),
1157
- stickyFooterScrollbar && (createElement(Scroller, { ref: this.footerScrollerRef, horizontal: true },
1158
- createElement("div", { style: { width: canvasWidth } })))));
1177
+ Boolean(stickyFooterScrollbar) && (createElement(FooterScrollbar, { isSticky: true, canvasWidth: canvasWidth, scrollerRef: this.footerScrollerRef })),
1178
+ createElement(Ruler, { widthRef: this.handleTotalWidth })));
1159
1179
  }));
1160
1180
  }
1161
1181
  // Lifecycle
@@ -1164,8 +1184,8 @@ class TimelineView extends DateComponent {
1164
1184
  this.syncedScroller = new ScrollerSyncer(true); // horizontal=true
1165
1185
  this.updateSyncedScroller();
1166
1186
  this.resetScroll();
1167
- this.context.emitter.on('_timeScrollRequest', this.handleTimeScroll);
1168
- this.syncedScroller.addScrollEndListener(this.clearScroll);
1187
+ this.context.emitter.on('_timeScrollRequest', this.handleTimeScrollRequest);
1188
+ this.syncedScroller.addScrollEndListener(this.handleTimeScrollEnd);
1169
1189
  }
1170
1190
  componentDidUpdate(prevProps) {
1171
1191
  this.updateSyncedScroller();
@@ -1174,13 +1194,13 @@ class TimelineView extends DateComponent {
1174
1194
  }
1175
1195
  else {
1176
1196
  // TODO: inefficient to update so often
1177
- this.updateScroll();
1197
+ this.applyTimeScroll();
1178
1198
  }
1179
1199
  }
1180
1200
  componentWillUnmount() {
1181
1201
  this.syncedScroller.destroy();
1182
- this.context.emitter.off('_timeScrollRequest', this.handleTimeScroll);
1183
- this.syncedScroller.removeScrollEndListener(this.clearScroll);
1202
+ this.context.emitter.off('_timeScrollRequest', this.handleTimeScrollRequest);
1203
+ this.syncedScroller.removeScrollEndListener(this.handleTimeScrollEnd);
1184
1204
  }
1185
1205
  // Scrolling
1186
1206
  // -----------------------------------------------------------------------------------------------
@@ -1192,7 +1212,17 @@ class TimelineView extends DateComponent {
1192
1212
  ]);
1193
1213
  }
1194
1214
  resetScroll() {
1195
- this.handleTimeScroll(this.context.options.scrollTime);
1215
+ this.handleTimeScrollRequest(this.context.options.scrollTime);
1216
+ }
1217
+ applyTimeScroll() {
1218
+ const { props, context, tDateProfile, scrollTime, slotWidth } = this;
1219
+ if (scrollTime != null && slotWidth != null) {
1220
+ let x = timeToCoord(scrollTime, context.dateEnv, props.dateProfile, tDateProfile, slotWidth);
1221
+ if (x) {
1222
+ x += 1; // overcome border. TODO: DRY this up
1223
+ }
1224
+ this.syncedScroller.scrollTo({ x });
1225
+ }
1196
1226
  }
1197
1227
  queryHit(positionLeft, positionTop, elWidth, elHeight) {
1198
1228
  const { props, context, tDateProfile, slotWidth } = this;
@@ -1230,8 +1260,7 @@ class TimelineView extends DateComponent {
1230
1260
  top: 0,
1231
1261
  bottom: elHeight,
1232
1262
  },
1233
- // HACK. TODO: This is expensive to do every hit-query
1234
- dayEl: this.bodyEl.querySelectorAll('.fc-timeline-slot')[slatIndex],
1263
+ getDayEl: () => getTimelineSlotEl(this.bodyEl, slatIndex),
1235
1264
  layer: 0,
1236
1265
  };
1237
1266
  }
@@ -1239,7 +1268,7 @@ class TimelineView extends DateComponent {
1239
1268
  }
1240
1269
  }
1241
1270
 
1242
- 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}";
1243
1272
  injectStyles(css_248z);
1244
1273
 
1245
- 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 };