@fullcalendar/timeline 6.1.14 → 7.0.0-beta.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,6 +1,8 @@
1
- import { config, createFormatter, greatestDurationDenominator, asCleanDays, createDuration, wholeDivideDurations, asRoughMs, addDays, startOfDay, asRoughSeconds, asRoughMinutes, diffWholeDays, isInt, computeVisibleDayRange, padStart, BaseComponent, memoizeObjArg, memoize, getDateMeta, ContentContainer, getSlotClassNames, getDayClassNames, buildNavLinkAttrs, PositionCache, findDirectChildren, rangeContainsMarker, NowTimer, NowIndicatorContainer, findElements, RefMap, multiplyDuration, SegHierarchy, groupIntersectingEntries, buildIsoString, computeEarliestSegStart, buildEventRangeKey, BgEvent, getSegMeta, renderFill, Slicer, intersectRanges, addMs, StandardEvent, MoreLinkContainer, sortEventSegs, mapHash, isPropsEqual, DateComponent, getStickyHeaderDates, getStickyFooterScrollbar, ViewContainer, renderScrollShim, injectStyles } from '@fullcalendar/core/internal.js';
2
- import { createElement, Fragment, createRef } from '@fullcalendar/core/preact.js';
3
- import { ScrollGrid } from '@fullcalendar/scrollgrid/internal.js';
1
+ import { injectStyles, config, createFormatter, greatestDurationDenominator, asCleanDays, createDuration, wholeDivideDurations, asRoughMs, addDays, startOfDay, asRoughSeconds, asRoughMinutes, diffWholeDays, isInt, computeVisibleDayRange, padStart, BaseComponent, getDateMeta, ContentContainer, getDayClassNames, getSlotClassNames, watchWidth, setRef, RefMap, afterSize, SegHierarchy, groupIntersectingEntries, buildEventRangeKey, BgEvent, getSegMeta, renderFill, Slicer, intersectRanges, addMs, StandardEvent, MoreLinkContainer, watchHeight, memoize, sortEventSegs, memoizeObjArg, watchSize, buildNavLinkAttrs, NowIndicatorContainer, DateComponent, ScrollResponder, getIsHeightAuto, getStickyHeaderDates, getStickyFooterScrollbar, NowTimer, rangeContainsMarker, ViewContainer, Scroller, getScrollerSyncerClass, multiplyDuration } from '@fullcalendar/core/internal.js';
2
+ import { createRef, createElement, Fragment, Component } from '@fullcalendar/core/preact.js';
3
+
4
+ var css_248z = ".fc-timeline-slots{z-index:1}.fc-timeline-slot-minor{border-style:dotted}.fc-timeline-now-indicator-container{bottom:0;left:0;overflow:hidden;position:absolute;right:0;top:0;z-index:4}.fc-timeline-now-indicator-arrow,.fc-timeline-now-indicator-line{border-color:var(--fc-now-indicator-color);border-style:solid;pointer-events:none;position:absolute;top:0}.fc-timeline-now-indicator-arrow{border-left-color:transparent;border-right-color:transparent;border-width:6px 5px 0;margin:0 -5px}.fc-timeline-now-indicator-line{border-width:0 0 0 1px;bottom:0}.fc-timeline-events{z-index:3}.fc-timeline-events-overlap-enabled{padding-bottom:10px}.fc-timeline-event{border-radius:0;font-size:var(--fc-small-font-size);margin-bottom:1px;z-index:1}.fc-timeline-event.fc-event-mirror{z-index:2}.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-spacious{margin-bottom:0;padding-bottom:5px;padding-top:5px}.fc-timeline-event .fc-event-inner{align-items:center;display:flex;flex-direction:row;padding:2px 1px}.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-event .fc-event-time{font-weight:700}.fc-timeline-event .fc-event-time,.fc-timeline-event .fc-event-title{padding:0 2px}.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-bg{bottom:0;left:0;position:absolute;right:0;top:0;z-index:2}.fc-timeline-bg .fc-non-business{z-index:1}.fc-timeline-bg .fc-bg-event{z-index:2}.fc-timeline-bg .fc-highlight{z-index:3}.fc-timeline-bg-harness{bottom:0;position:absolute;top:0}";
5
+ injectStyles(css_248z);
4
6
 
5
7
  const MIN_AUTO_LABELS = 18; // more than `12` months but less that `24` hours
6
8
  const MAX_AUTO_SLOTS_PER_LABEL = 6; // allows 6 10-min slots in an hour
@@ -440,152 +442,90 @@ function buildCellObject(date, text, rowUnit) {
440
442
  return { date, text, rowUnit, colspan: 1, isWeekStart: false };
441
443
  }
442
444
 
443
- class TimelineHeaderTh extends BaseComponent {
445
+ class TimelineSlatCell extends BaseComponent {
444
446
  constructor() {
445
447
  super(...arguments);
446
- this.refineRenderProps = memoizeObjArg(refineRenderProps);
447
- this.buildCellNavLinkAttrs = memoize(buildCellNavLinkAttrs);
448
+ // ref
449
+ this.innerElRef = createRef();
448
450
  }
449
451
  render() {
450
452
  let { props, context } = this;
451
- let { dateEnv, options } = context;
452
- let { cell, dateProfile, tDateProfile } = props;
453
- // the cell.rowUnit is f'd
454
- // giving 'month' for a 3-day view
455
- // workaround: to infer day, do NOT time
456
- let dateMeta = getDateMeta(cell.date, props.todayRange, props.nowDate, dateProfile);
457
- let renderProps = this.refineRenderProps({
458
- level: props.rowLevel,
459
- dateMarker: cell.date,
460
- text: cell.text,
461
- dateEnv: context.dateEnv,
462
- viewApi: context.viewApi,
463
- });
464
- return (createElement(ContentContainer, { elTag: "th", elClasses: [
453
+ let { dateEnv, options, theme } = context;
454
+ let { date, tDateProfile, isEm } = props;
455
+ let dateMeta = getDateMeta(props.date, props.todayRange, props.nowDate, props.dateProfile);
456
+ let renderProps = Object.assign(Object.assign({ date: dateEnv.toDate(props.date) }, dateMeta), { view: context.viewApi });
457
+ return (createElement(ContentContainer, { elTag: "div", elClasses: [
458
+ 'fc-flex-column',
459
+ 'fc-align-start',
460
+ 'fc-cell',
465
461
  'fc-timeline-slot',
466
- 'fc-timeline-slot-label',
467
- cell.isWeekStart && 'fc-timeline-slot-em',
468
- ...( // TODO: so slot classnames for week/month/bigger. see note above about rowUnit
469
- cell.rowUnit === 'time' ?
470
- getSlotClassNames(dateMeta, context.theme) :
471
- getDayClassNames(dateMeta, context.theme)),
462
+ 'fc-timeline-slot-lane',
463
+ isEm ? 'fc-timeline-slot-em' : '',
464
+ tDateProfile.isTimeScale ? (isInt(dateEnv.countDurationsBetween(// best to do this here?
465
+ tDateProfile.normalizedRange.start, props.date, tDateProfile.labelInterval)) ?
466
+ 'fc-timeline-slot-major' :
467
+ 'fc-timeline-slot-minor') : '',
468
+ ...(props.isDay ?
469
+ getDayClassNames(dateMeta, theme) :
470
+ getSlotClassNames(dateMeta, theme)),
472
471
  ], elAttrs: {
473
- colSpan: cell.colspan,
474
- 'data-date': dateEnv.formatIso(cell.date, {
475
- omitTime: !tDateProfile.isTimeScale,
472
+ 'data-date': dateEnv.formatIso(date, {
476
473
  omitTimeZoneOffset: true,
474
+ omitTime: !tDateProfile.isTimeScale,
477
475
  }),
478
- }, renderProps: renderProps, generatorName: "slotLabelContent", customGenerator: options.slotLabelContent, defaultGenerator: renderInnerContent, classNameGenerator: options.slotLabelClassNames, didMount: options.slotLabelDidMount, willUnmount: options.slotLabelWillUnmount }, (InnerContent) => (createElement("div", { className: "fc-timeline-slot-frame", style: { height: props.rowInnerHeight } },
479
- createElement(InnerContent, { elTag: "a", elClasses: [
480
- 'fc-timeline-slot-cushion',
481
- 'fc-scrollgrid-sync-inner',
482
- props.isSticky && 'fc-sticky',
483
- ], elAttrs: this.buildCellNavLinkAttrs(context, cell.date, cell.rowUnit) })))));
476
+ }, elStyle: {
477
+ width: props.width,
478
+ }, renderProps: renderProps, generatorName: "slotLaneContent", customGenerator: options.slotLaneContent, classNameGenerator: options.slotLaneClassNames, didMount: options.slotLaneDidMount, willUnmount: options.slotLaneWillUnmount }, (InnerContent) => (createElement("div", { ref: this.innerElRef, className: 'fc-flex-column' },
479
+ createElement(InnerContent, { elTag: "div", elClasses: ['fc-cell-inner'] })))));
484
480
  }
485
- }
486
- function buildCellNavLinkAttrs(context, cellDate, rowUnit) {
487
- return (rowUnit && rowUnit !== 'time')
488
- ? buildNavLinkAttrs(context, cellDate, rowUnit)
489
- : {};
490
- }
491
- function renderInnerContent(renderProps) {
492
- return renderProps.text;
493
- }
494
- function refineRenderProps(input) {
495
- return {
496
- level: input.level,
497
- date: input.dateEnv.toDate(input.dateMarker),
498
- view: input.viewApi,
499
- text: input.text,
500
- };
501
- }
502
-
503
- class TimelineHeaderRows extends BaseComponent {
504
- render() {
505
- let { dateProfile, tDateProfile, rowInnerHeights, todayRange, nowDate } = this.props;
506
- let { cellRows } = tDateProfile;
507
- return (createElement(Fragment, null, cellRows.map((rowCells, rowLevel) => {
508
- let isLast = rowLevel === cellRows.length - 1;
509
- let isChrono = tDateProfile.isTimeScale && isLast; // the final row, with times?
510
- let classNames = [
511
- 'fc-timeline-header-row',
512
- isChrono ? 'fc-timeline-header-row-chrono' : '',
513
- ];
514
- return ( // eslint-disable-next-line react/no-array-index-key
515
- createElement("tr", { key: rowLevel, className: classNames.join(' ') }, rowCells.map((cell) => (createElement(TimelineHeaderTh, { key: cell.date.toISOString(), cell: cell, rowLevel: rowLevel, dateProfile: dateProfile, tDateProfile: tDateProfile, todayRange: todayRange, nowDate: nowDate, rowInnerHeight: rowInnerHeights && rowInnerHeights[rowLevel], isSticky: !isLast })))));
516
- })));
481
+ componentDidMount() {
482
+ const innerEl = this.innerElRef.current;
483
+ this.detachWidth = watchWidth(innerEl, (width) => {
484
+ setRef(this.props.innerWidthRef, width);
485
+ });
486
+ }
487
+ componentWillUnmount() {
488
+ this.detachWidth();
517
489
  }
518
490
  }
519
491
 
520
- class TimelineCoords {
521
- constructor(slatRootEl, // okay to expose?
522
- slatEls, dateProfile, tDateProfile, dateEnv, isRtl) {
523
- this.slatRootEl = slatRootEl;
524
- this.dateProfile = dateProfile;
525
- this.tDateProfile = tDateProfile;
526
- this.dateEnv = dateEnv;
527
- this.isRtl = isRtl;
528
- this.outerCoordCache = new PositionCache(slatRootEl, slatEls, true, // isHorizontal
529
- false);
530
- // for the inner divs within the slats
531
- // used for event rendering and scrollTime, to disregard slat border
532
- this.innerCoordCache = new PositionCache(slatRootEl, findDirectChildren(slatEls, 'div'), true, // isHorizontal
533
- false);
534
- }
535
- isDateInRange(date) {
536
- return rangeContainsMarker(this.dateProfile.currentRange, date);
537
- }
538
- // results range from negative width of area to 0
539
- dateToCoord(date) {
540
- let { tDateProfile } = this;
541
- let snapCoverage = this.computeDateSnapCoverage(date);
542
- let slotCoverage = snapCoverage / tDateProfile.snapsPerSlot;
543
- let slotIndex = Math.floor(slotCoverage);
544
- slotIndex = Math.min(slotIndex, tDateProfile.slotCnt - 1);
545
- let partial = slotCoverage - slotIndex;
546
- let { innerCoordCache, outerCoordCache } = this;
547
- if (this.isRtl) {
548
- return outerCoordCache.originClientRect.width - (outerCoordCache.rights[slotIndex] -
549
- (innerCoordCache.getWidth(slotIndex) * partial));
550
- }
551
- return (outerCoordCache.lefts[slotIndex] +
552
- (innerCoordCache.getWidth(slotIndex) * partial));
553
- }
554
- rangeToCoords(range) {
555
- return {
556
- start: this.dateToCoord(range.start),
557
- end: this.dateToCoord(range.end),
558
- };
559
- }
560
- durationToCoord(duration) {
561
- let { dateProfile, tDateProfile, dateEnv, isRtl } = this;
562
- let coord = 0;
563
- if (dateProfile) {
564
- let date = dateEnv.add(dateProfile.activeRange.start, duration);
565
- if (!tDateProfile.isTimeScale) {
566
- date = startOfDay(date);
567
- }
568
- coord = this.dateToCoord(date);
569
- // hack to overcome the left borders of non-first slat
570
- if (!isRtl && coord) {
571
- coord += 1;
492
+ class TimelineSlats extends BaseComponent {
493
+ constructor() {
494
+ super(...arguments);
495
+ this.innerWidthRefMap = new RefMap(() => {
496
+ afterSize(this.handleInnerWidths);
497
+ });
498
+ this.handleInnerWidths = () => {
499
+ const innerWidthMap = this.innerWidthRefMap.current;
500
+ let max = 0;
501
+ for (const innerWidth of innerWidthMap.values()) {
502
+ max = Math.max(max, innerWidth);
572
503
  }
573
- }
574
- return coord;
575
- }
576
- coordFromLeft(coord) {
577
- if (this.isRtl) {
578
- return this.outerCoordCache.originClientRect.width - coord;
579
- }
580
- return coord;
504
+ // TODO: check to see if changed before firing ref!? YES. do in other places too
505
+ setRef(this.props.innerWidthRef, max);
506
+ };
581
507
  }
582
- // returned value is between 0 and the number of snaps
583
- computeDateSnapCoverage(date) {
584
- return computeDateSnapCoverage(date, this.tDateProfile, this.dateEnv);
508
+ render() {
509
+ let { props, innerWidthRefMap } = this;
510
+ let { tDateProfile, slotWidth } = props;
511
+ let { slotDates, isWeekStarts } = tDateProfile;
512
+ let isDay = !tDateProfile.isTimeScale && !tDateProfile.largeUnit;
513
+ return (createElement("div", { className: "fc-timeline-slots fc-fill fc-flex-row" }, slotDates.map((slotDate, i) => {
514
+ let key = slotDate.toISOString();
515
+ return (createElement(TimelineSlatCell, { key: key, date: slotDate, dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: props.nowDate, todayRange: props.todayRange, isEm: isWeekStarts[i], isDay: isDay,
516
+ // ref
517
+ innerWidthRef: innerWidthRefMap.createRef(key),
518
+ // dimensions
519
+ width: slotWidth }));
520
+ })));
585
521
  }
586
522
  }
523
+
524
+ /*
525
+ TODO: rename this file!
526
+ */
587
527
  // returned value is between 0 and the number of snaps
588
- function computeDateSnapCoverage(date, tDateProfile, dateEnv) {
528
+ function computeDateSnapCoverage$1(date, tDateProfile, dateEnv) {
589
529
  let snapDiff = dateEnv.countDurationsBetween(tDateProfile.normalizedRange.start, date, tDateProfile.snapDuration);
590
530
  if (snapDiff < 0) {
591
531
  return 0;
@@ -605,6 +545,9 @@ function computeDateSnapCoverage(date, tDateProfile, dateEnv) {
605
545
  }
606
546
  return snapCoverage;
607
547
  }
548
+ /*
549
+ TODO: audit!!!
550
+ */
608
551
  function coordToCss(hcoord, isRtl) {
609
552
  if (hcoord === null) {
610
553
  return { left: '', right: '' };
@@ -614,6 +557,9 @@ function coordToCss(hcoord, isRtl) {
614
557
  }
615
558
  return { left: hcoord, right: '' };
616
559
  }
560
+ /*
561
+ TODO: audit!!!
562
+ */
617
563
  function coordsToCss(hcoords, isRtl) {
618
564
  if (!hcoords) {
619
565
  return { left: '', right: '' };
@@ -623,216 +569,141 @@ function coordsToCss(hcoords, isRtl) {
623
569
  }
624
570
  return { left: hcoords.start, right: -hcoords.end };
625
571
  }
626
-
627
- class TimelineHeader extends BaseComponent {
628
- constructor() {
629
- super(...arguments);
630
- this.rootElRef = createRef();
631
- }
632
- render() {
633
- let { props, context } = this;
634
- // TODO: very repetitive
635
- // TODO: make part of tDateProfile?
636
- let timerUnit = greatestDurationDenominator(props.tDateProfile.slotDuration).unit;
637
- // WORKAROUND: make ignore slatCoords when out of sync with dateProfile
638
- let slatCoords = props.slatCoords && props.slatCoords.dateProfile === props.dateProfile ? props.slatCoords : null;
639
- return (createElement(NowTimer, { unit: timerUnit }, (nowDate, todayRange) => (createElement("div", { className: "fc-timeline-header", ref: this.rootElRef },
640
- createElement("table", { "aria-hidden": true, className: "fc-scrollgrid-sync-table", style: { minWidth: props.tableMinWidth, width: props.clientWidth } },
641
- props.tableColGroupNode,
642
- createElement("tbody", null,
643
- createElement(TimelineHeaderRows, { dateProfile: props.dateProfile, tDateProfile: props.tDateProfile, nowDate: nowDate, todayRange: todayRange, rowInnerHeights: props.rowInnerHeights }))),
644
- context.options.nowIndicator && (
645
- // need to have a container regardless of whether the current view has a visible now indicator
646
- // because apparently removal of the element resets the scroll for some reasons (issue #5351).
647
- // this issue doesn't happen for the timeline body however (
648
- createElement("div", { className: "fc-timeline-now-indicator-container" }, (slatCoords && slatCoords.isDateInRange(nowDate)) && (createElement(NowIndicatorContainer, { elClasses: ['fc-timeline-now-indicator-arrow'], elStyle: coordToCss(slatCoords.dateToCoord(nowDate), context.isRtl), isAxis: true, date: nowDate }))))))));
572
+ /*
573
+ TODO: DRY up with elsewhere?
574
+ */
575
+ function horizontalsToCss(hcoord, isRtl) {
576
+ if (!hcoord) {
577
+ return {};
649
578
  }
650
- componentDidMount() {
651
- this.updateSize();
579
+ if (isRtl) {
580
+ return { right: hcoord.start, width: hcoord.size };
652
581
  }
653
- componentDidUpdate() {
654
- this.updateSize();
582
+ else {
583
+ return { left: hcoord.start, width: hcoord.size };
655
584
  }
656
- updateSize() {
657
- if (this.props.onMaxCushionWidth) {
658
- this.props.onMaxCushionWidth(this.computeMaxCushionWidth());
659
- }
585
+ }
586
+ function horizontalCoordToCss(start, isRtl) {
587
+ if (isRtl) {
588
+ return { right: start };
660
589
  }
661
- computeMaxCushionWidth() {
662
- return Math.max(...findElements(this.rootElRef.current, '.fc-timeline-header-row:last-child .fc-timeline-slot-cushion').map((el) => el.getBoundingClientRect().width));
590
+ else {
591
+ return { left: start };
663
592
  }
664
593
  }
665
594
 
666
- class TimelineSlatCell extends BaseComponent {
667
- render() {
668
- let { props, context } = this;
669
- let { dateEnv, options, theme } = context;
670
- let { date, tDateProfile, isEm } = props;
671
- let dateMeta = getDateMeta(props.date, props.todayRange, props.nowDate, props.dateProfile);
672
- let renderProps = Object.assign(Object.assign({ date: dateEnv.toDate(props.date) }, dateMeta), { view: context.viewApi });
673
- return (createElement(ContentContainer, { elTag: "td", elRef: props.elRef, elClasses: [
674
- 'fc-timeline-slot',
675
- 'fc-timeline-slot-lane',
676
- isEm && 'fc-timeline-slot-em',
677
- tDateProfile.isTimeScale ? (isInt(dateEnv.countDurationsBetween(tDateProfile.normalizedRange.start, props.date, tDateProfile.labelInterval)) ?
678
- 'fc-timeline-slot-major' :
679
- 'fc-timeline-slot-minor') : '',
680
- ...(props.isDay ?
681
- getDayClassNames(dateMeta, theme) :
682
- getSlotClassNames(dateMeta, theme)),
683
- ], elAttrs: {
684
- 'data-date': dateEnv.formatIso(date, {
685
- omitTimeZoneOffset: true,
686
- omitTime: !tDateProfile.isTimeScale,
687
- }),
688
- }, renderProps: renderProps, generatorName: "slotLaneContent", customGenerator: options.slotLaneContent, classNameGenerator: options.slotLaneClassNames, didMount: options.slotLaneDidMount, willUnmount: options.slotLaneWillUnmount }, (InnerContent) => (createElement(InnerContent, { elTag: "div" }))));
595
+ function createVerticalStyle(props) {
596
+ if (props) {
597
+ return {
598
+ top: props.start,
599
+ height: props.size,
600
+ };
689
601
  }
690
602
  }
691
-
692
- class TimelineSlatsBody extends BaseComponent {
693
- render() {
694
- let { props } = this;
695
- let { tDateProfile, cellElRefs } = props;
696
- let { slotDates, isWeekStarts } = tDateProfile;
697
- let isDay = !tDateProfile.isTimeScale && !tDateProfile.largeUnit;
698
- return (createElement("tbody", null,
699
- createElement("tr", null, slotDates.map((slotDate, i) => {
700
- let key = slotDate.toISOString();
701
- return (createElement(TimelineSlatCell, { key: key, elRef: cellElRefs.createRef(key), date: slotDate, dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: props.nowDate, todayRange: props.todayRange, isEm: isWeekStarts[i], isDay: isDay }));
702
- }))));
603
+ function createHorizontalStyle(props, isRtl) {
604
+ if (props) {
605
+ return {
606
+ [isRtl ? 'right' : 'left']: props.start,
607
+ width: props.size,
608
+ };
703
609
  }
704
610
  }
705
-
706
- class TimelineSlats extends BaseComponent {
707
- constructor() {
708
- super(...arguments);
709
- this.rootElRef = createRef();
710
- this.cellElRefs = new RefMap();
711
- this.handleScrollRequest = (request) => {
712
- let { onScrollLeftRequest } = this.props;
713
- let { coords } = this;
714
- if (onScrollLeftRequest && coords) {
715
- if (request.time) {
716
- let scrollLeft = coords.coordFromLeft(coords.durationToCoord(request.time));
717
- onScrollLeftRequest(scrollLeft);
718
- }
719
- return true;
720
- }
721
- return null; // best?
722
- };
611
+ // Timeline-specific
612
+ // -------------------------------------------------------------------------------------------------
613
+ const MIN_SLOT_WIDTH = 30; // for real
614
+ /*
615
+ TODO: DRY with computeSlatHeight?
616
+ */
617
+ function computeSlotWidth(slatCnt, slatsPerLabel, slatMinWidth, labelInnerWidth, viewportWidth) {
618
+ if (labelInnerWidth == null || viewportWidth == null) {
619
+ return [undefined, undefined, false];
620
+ }
621
+ slatMinWidth = Math.max(slatMinWidth || 0, (labelInnerWidth + 1) / slatsPerLabel, MIN_SLOT_WIDTH);
622
+ const slatTryWidth = viewportWidth / slatCnt;
623
+ let slatLiquid;
624
+ let slatWidth;
625
+ if (slatTryWidth >= slatMinWidth) {
626
+ slatLiquid = true;
627
+ slatWidth = slatTryWidth;
723
628
  }
724
- render() {
725
- let { props, context } = this;
726
- return (createElement("div", { className: "fc-timeline-slots", ref: this.rootElRef },
727
- createElement("table", { "aria-hidden": true, className: context.theme.getClass('table'), style: {
728
- minWidth: props.tableMinWidth,
729
- width: props.clientWidth,
730
- } },
731
- props.tableColGroupNode,
732
- createElement(TimelineSlatsBody, { cellElRefs: this.cellElRefs, dateProfile: props.dateProfile, tDateProfile: props.tDateProfile, nowDate: props.nowDate, todayRange: props.todayRange }))));
629
+ else {
630
+ slatLiquid = false;
631
+ slatWidth = Math.max(slatMinWidth, slatTryWidth);
733
632
  }
734
- componentDidMount() {
735
- this.updateSizing();
736
- this.scrollResponder = this.context.createScrollResponder(this.handleScrollRequest);
633
+ return [slatWidth * slatCnt, slatWidth, slatLiquid];
634
+ }
635
+ function timeToCoord(// pixels
636
+ time, dateEnv, dateProfile, tDateProfile, slowWidth) {
637
+ let date = dateEnv.add(dateProfile.activeRange.start, time);
638
+ if (!tDateProfile.isTimeScale) {
639
+ date = startOfDay(date);
737
640
  }
738
- componentDidUpdate(prevProps) {
739
- this.updateSizing();
740
- this.scrollResponder.update(prevProps.dateProfile !== this.props.dateProfile);
641
+ return dateToCoord(date, dateEnv, tDateProfile, slowWidth);
642
+ }
643
+ function dateToCoord(// pixels
644
+ date, dateEnv, tDateProfile, slotWidth) {
645
+ let snapCoverage = computeDateSnapCoverage(date, tDateProfile, dateEnv);
646
+ let slotCoverage = snapCoverage / tDateProfile.snapsPerSlot;
647
+ return slotCoverage * slotWidth;
648
+ }
649
+ /*
650
+ returned value is between 0 and the number of snaps
651
+ */
652
+ function computeDateSnapCoverage(date, tDateProfile, dateEnv) {
653
+ let snapDiff = dateEnv.countDurationsBetween(tDateProfile.normalizedRange.start, date, tDateProfile.snapDuration);
654
+ if (snapDiff < 0) {
655
+ return 0;
741
656
  }
742
- componentWillUnmount() {
743
- this.scrollResponder.detach();
744
- if (this.props.onCoords) {
745
- this.props.onCoords(null);
746
- }
657
+ if (snapDiff >= tDateProfile.snapDiffToIndex.length) {
658
+ return tDateProfile.snapCnt;
747
659
  }
748
- updateSizing() {
749
- let { props, context } = this;
750
- if (props.clientWidth !== null && // is sizing stable?
751
- this.scrollResponder
752
- // ^it's possible to have clientWidth immediately after mount (when returning from print view), but w/o scrollResponder
753
- ) {
754
- let rootEl = this.rootElRef.current;
755
- if (rootEl.offsetWidth) { // not hidden by css
756
- this.coords = new TimelineCoords(this.rootElRef.current, collectCellEls(this.cellElRefs.currentMap, props.tDateProfile.slotDates), props.dateProfile, props.tDateProfile, context.dateEnv, context.isRtl);
757
- if (props.onCoords) {
758
- props.onCoords(this.coords);
759
- }
760
- this.scrollResponder.update(false); // TODO: wouldn't have to do this if coords were in state
761
- }
762
- }
660
+ let snapDiffInt = Math.floor(snapDiff);
661
+ let snapCoverage = tDateProfile.snapDiffToIndex[snapDiffInt];
662
+ if (isInt(snapCoverage)) { // not an in-between value
663
+ snapCoverage += snapDiff - snapDiffInt; // add the remainder
763
664
  }
764
- positionToHit(leftPosition) {
765
- let { outerCoordCache } = this.coords;
766
- let { dateEnv, isRtl } = this.context;
767
- let { tDateProfile } = this.props;
768
- let slatIndex = outerCoordCache.leftToIndex(leftPosition);
769
- if (slatIndex != null) {
770
- // somewhat similar to what TimeGrid does. consolidate?
771
- let slatWidth = outerCoordCache.getWidth(slatIndex);
772
- let partial = isRtl ?
773
- (outerCoordCache.rights[slatIndex] - leftPosition) / slatWidth :
774
- (leftPosition - outerCoordCache.lefts[slatIndex]) / slatWidth;
775
- let localSnapIndex = Math.floor(partial * tDateProfile.snapsPerSlot);
776
- let start = dateEnv.add(tDateProfile.slotDates[slatIndex], multiplyDuration(tDateProfile.snapDuration, localSnapIndex));
777
- let end = dateEnv.add(start, tDateProfile.snapDuration);
778
- return {
779
- dateSpan: {
780
- range: { start, end },
781
- allDay: !this.props.tDateProfile.isTimeScale,
782
- },
783
- dayEl: this.cellElRefs.currentMap[slatIndex],
784
- left: outerCoordCache.lefts[slatIndex],
785
- right: outerCoordCache.rights[slatIndex],
786
- };
787
- }
788
- return null;
665
+ else {
666
+ // a fractional value, meaning the date is not visible
667
+ // always round up in this case. works for start AND end dates in a range.
668
+ snapCoverage = Math.ceil(snapCoverage);
789
669
  }
790
- }
791
- function collectCellEls(elMap, slotDates) {
792
- return slotDates.map((slotDate) => {
793
- let key = slotDate.toISOString();
794
- return elMap[key];
795
- });
670
+ return snapCoverage;
796
671
  }
797
672
 
798
- function computeSegHCoords(segs, minWidth, timelineCoords) {
799
- let hcoords = [];
800
- if (timelineCoords) {
801
- for (let seg of segs) {
802
- let res = timelineCoords.rangeToCoords(seg);
803
- let start = Math.round(res.start); // for barely-overlapping collisions
804
- let end = Math.round(res.end); //
805
- if (end - start < minWidth) {
806
- end = start + minWidth;
807
- }
808
- hcoords.push({ start, end });
809
- }
673
+ function computeManySegHorizontals(segs, segMinWidth, dateEnv, tDateProfile, slotWidth) {
674
+ const res = {};
675
+ for (const seg of segs) {
676
+ res[seg.eventRange.instance.instanceId] = computeSegHorizontals(seg, segMinWidth, dateEnv, tDateProfile, slotWidth);
810
677
  }
811
- return hcoords;
678
+ return res;
679
+ }
680
+ function computeSegHorizontals(seg, segMinWidth, dateEnv, tDateProfile, slotWidth) {
681
+ const startCoord = dateToCoord(seg.start, dateEnv, tDateProfile, slotWidth);
682
+ const endCoord = dateToCoord(seg.end, dateEnv, tDateProfile, slotWidth);
683
+ let size = endCoord - startCoord;
684
+ if (segMinWidth) {
685
+ size = Math.max(size, segMinWidth);
686
+ }
687
+ return { start: startCoord, size };
812
688
  }
813
- function computeFgSegPlacements(segs, segHCoords, // might not have for every seg
814
- eventInstanceHeights, // might not have for every seg
815
- moreLinkHeights, // might not have for every more-link
689
+ function computeFgSegPlacements(// mostly horizontals
690
+ segs, segHorizontals, segHeights, // keyed by instanceId
816
691
  strictOrder, maxStackCnt) {
817
- let segInputs = [];
818
- let crudePlacements = []; // when we don't know dims
692
+ let segEntries = [];
819
693
  for (let i = 0; i < segs.length; i += 1) {
820
694
  let seg = segs[i];
821
695
  let instanceId = seg.eventRange.instance.instanceId;
822
- let height = eventInstanceHeights[instanceId];
823
- let hcoords = segHCoords[i];
824
- if (height && hcoords) {
825
- segInputs.push({
696
+ let height = segHeights.get(instanceId);
697
+ let hcoords = segHorizontals[instanceId];
698
+ if (height != null && hcoords != null) {
699
+ segEntries.push({
826
700
  index: i,
827
- span: hcoords,
828
- thickness: height,
829
- });
830
- }
831
- else {
832
- crudePlacements.push({
833
701
  seg,
834
- hcoords,
835
- top: null,
702
+ span: {
703
+ start: hcoords.start,
704
+ end: hcoords.start + hcoords.size,
705
+ },
706
+ thickness: height,
836
707
  });
837
708
  }
838
709
  }
@@ -843,80 +714,85 @@ strictOrder, maxStackCnt) {
843
714
  if (maxStackCnt != null) {
844
715
  hierarchy.maxStackCnt = maxStackCnt;
845
716
  }
846
- let hiddenEntries = hierarchy.addSegs(segInputs);
847
- let hiddenPlacements = hiddenEntries.map((entry) => ({
848
- seg: segs[entry.index],
849
- hcoords: entry.span,
850
- top: null,
851
- }));
717
+ let hiddenEntries = hierarchy.addSegs(segEntries);
852
718
  let hiddenGroups = groupIntersectingEntries(hiddenEntries);
853
- let moreLinkInputs = [];
854
- let moreLinkCrudePlacements = [];
855
- const extractSeg = (entry) => segs[entry.index];
856
- for (let i = 0; i < hiddenGroups.length; i += 1) {
857
- let hiddenGroup = hiddenGroups[i];
858
- let sortedSegs = hiddenGroup.entries.map(extractSeg);
859
- let height = moreLinkHeights[buildIsoString(computeEarliestSegStart(sortedSegs))]; // not optimal :(
860
- if (height != null) {
861
- // NOTE: the hiddenGroup's spanStart/spanEnd are already computed by rangeToCoords. computed during input.
862
- moreLinkInputs.push({
863
- index: segs.length + i,
864
- thickness: height,
865
- span: hiddenGroup.span,
866
- });
867
- }
868
- else {
869
- moreLinkCrudePlacements.push({
870
- seg: sortedSegs,
871
- hcoords: hiddenGroup.span,
872
- top: null,
873
- });
874
- }
875
- }
719
+ let hiddenGroupEntries = hiddenGroups.map((hiddenGroup, index) => ({
720
+ index: segs.length + index,
721
+ segGroup: hiddenGroup,
722
+ span: hiddenGroup.span,
723
+ thickness: 1, // HACK to ensure it's placed
724
+ }));
876
725
  // add more-links into the hierarchy, but don't limit
877
726
  hierarchy.maxStackCnt = -1;
878
- hierarchy.addSegs(moreLinkInputs);
727
+ hierarchy.addSegs(hiddenGroupEntries);
879
728
  let visibleRects = hierarchy.toRects();
880
- let visiblePlacements = [];
881
- let maxHeight = 0;
729
+ let segTops = {};
730
+ let hiddenGroupTops = {};
882
731
  for (let rect of visibleRects) {
883
- let segIndex = rect.index;
884
- visiblePlacements.push({
885
- seg: segIndex < segs.length
886
- ? segs[segIndex] // a real seg
887
- : hiddenGroups[segIndex - segs.length].entries.map(extractSeg),
888
- hcoords: rect.span,
889
- top: rect.levelCoord,
890
- });
891
- maxHeight = Math.max(maxHeight, rect.levelCoord + rect.thickness);
732
+ const { seg, segGroup } = rect;
733
+ if (seg) { // regular seg
734
+ segTops[seg.eventRange.instance.instanceId] = rect.levelCoord;
735
+ }
736
+ else { // hiddenGroup
737
+ hiddenGroupTops[segGroup.key] = rect.levelCoord;
738
+ }
892
739
  }
893
740
  return [
894
- visiblePlacements.concat(crudePlacements, hiddenPlacements, moreLinkCrudePlacements),
895
- maxHeight,
741
+ segTops,
742
+ computeMaxBottom(segs, segTops, segHeights),
743
+ hiddenGroups,
744
+ hiddenGroupTops,
896
745
  ];
897
746
  }
747
+ function computeMaxBottom(segs, segTops, segHeights) {
748
+ let max = 0;
749
+ for (const seg of segs) {
750
+ const { instanceId } = seg.eventRange.instance;
751
+ const top = segTops[instanceId];
752
+ const height = segHeights.get(instanceId);
753
+ if (top != null && height != null) {
754
+ max = Math.max(max, top + height);
755
+ }
756
+ }
757
+ return max;
758
+ }
759
+ /*
760
+ TODO: converge with computeMaxBottom, but keys are different
761
+ */
762
+ function computeMoreLinkMaxBottom(hiddenGroups, hiddenGroupTops, hiddenGroupHeights) {
763
+ let max = 0;
764
+ for (const hiddenGroup of hiddenGroups) {
765
+ const top = hiddenGroupTops[hiddenGroup.key];
766
+ const height = hiddenGroupHeights.get(hiddenGroup.key);
767
+ if (top != null && height != null) {
768
+ max = Math.max(max, top + height);
769
+ }
770
+ }
771
+ return max;
772
+ }
898
773
 
899
774
  class TimelineLaneBg extends BaseComponent {
900
775
  render() {
901
776
  let { props } = this;
902
777
  let highlightSeg = [].concat(props.eventResizeSegs, props.dateSelectionSegs);
903
- return props.timelineCoords && (createElement("div", { className: "fc-timeline-bg" },
904
- this.renderSegs(props.businessHourSegs || [], props.timelineCoords, 'non-business'),
905
- this.renderSegs(props.bgEventSegs || [], props.timelineCoords, 'bg-event'),
906
- this.renderSegs(highlightSeg, props.timelineCoords, 'highlight')));
907
- }
908
- renderSegs(segs, timelineCoords, fillType) {
909
- let { todayRange, nowDate } = this.props;
910
- let { isRtl } = this.context;
911
- let segHCoords = computeSegHCoords(segs, 0, timelineCoords);
912
- let children = segs.map((seg, i) => {
913
- let hcoords = segHCoords[i];
914
- let hStyle = coordsToCss(hcoords, isRtl);
778
+ return (createElement("div", { className: "fc-timeline-bg" },
779
+ this.renderSegs(props.businessHourSegs || [], 'non-business'),
780
+ this.renderSegs(props.bgEventSegs || [], 'bg-event'),
781
+ this.renderSegs(highlightSeg, 'highlight')));
782
+ }
783
+ renderSegs(segs, fillType) {
784
+ let { tDateProfile, todayRange, nowDate, slotWidth } = this.props;
785
+ let { dateEnv, isRtl } = this.context;
786
+ return (createElement(Fragment, null, segs.map((seg) => {
787
+ let hStyle; // TODO
788
+ if (slotWidth != null) {
789
+ let segHorizontal = computeSegHorizontals(seg, undefined, dateEnv, tDateProfile, slotWidth);
790
+ hStyle = horizontalsToCss(segHorizontal, isRtl);
791
+ }
915
792
  return (createElement("div", { key: buildEventRangeKey(seg.eventRange), className: "fc-timeline-bg-harness", style: hStyle }, fillType === 'bg-event' ?
916
793
  createElement(BgEvent, Object.assign({ seg: seg }, getSegMeta(seg, todayRange, nowDate))) :
917
794
  renderFill(fillType)));
918
- });
919
- return createElement(Fragment, null, children);
795
+ })));
920
796
  }
921
797
  }
922
798
 
@@ -925,8 +801,8 @@ class TimelineLaneSlicer extends Slicer {
925
801
  let normalRange = normalizeRange(origRange, tDateProfile, dateEnv);
926
802
  let segs = [];
927
803
  // protect against when the span is entirely in an invalid date region
928
- if (computeDateSnapCoverage(normalRange.start, tDateProfile, dateEnv)
929
- < computeDateSnapCoverage(normalRange.end, tDateProfile, dateEnv)) {
804
+ if (computeDateSnapCoverage$1(normalRange.start, tDateProfile, dateEnv)
805
+ < computeDateSnapCoverage$1(normalRange.end, tDateProfile, dateEnv)) {
930
806
  // intersect the footprint's range with the grid's range
931
807
  let slicedRange = intersectRanges(normalRange, tDateProfile.normalizedRange);
932
808
  if (slicedRange) {
@@ -952,48 +828,84 @@ const DEFAULT_TIME_FORMAT = createFormatter({
952
828
  });
953
829
  class TimelineEvent extends BaseComponent {
954
830
  render() {
955
- let { props } = this;
956
- return (createElement(StandardEvent, Object.assign({}, props, { elClasses: ['fc-timeline-event', 'fc-h-event'], defaultTimeFormat: DEFAULT_TIME_FORMAT, defaultDisplayEventTime: !props.isTimeScale })));
831
+ let { props, context } = this;
832
+ let { options } = context;
833
+ return (createElement(StandardEvent, Object.assign({}, props, { elClasses: [
834
+ 'fc-timeline-event',
835
+ 'fc-h-event',
836
+ options.eventOverlap === false // TODO: fix bad default
837
+ ? 'fc-timeline-event-spacious'
838
+ : ''
839
+ ], defaultTimeFormat: DEFAULT_TIME_FORMAT, defaultDisplayEventTime: !props.isTimeScale })));
957
840
  }
958
841
  }
959
842
 
960
843
  class TimelineLaneMoreLink extends BaseComponent {
961
844
  render() {
962
- let { props, context } = this;
963
- let { hiddenSegs, placement, resourceId } = props;
964
- let { top, hcoords } = placement;
965
- let isVisible = hcoords && top !== null;
966
- let hStyle = coordsToCss(hcoords, context.isRtl);
845
+ let { props } = this;
846
+ let { hiddenSegs, resourceId, forcedInvisibleMap } = props;
967
847
  let extraDateSpan = resourceId ? { resourceId } : {};
968
- return (createElement(MoreLinkContainer, { elRef: props.elRef, elClasses: ['fc-timeline-more-link'], elStyle: Object.assign({ visibility: isVisible ? '' : 'hidden', top: top || 0 }, hStyle), allDayDate: null, moreCnt: hiddenSegs.length, allSegs: hiddenSegs, hiddenSegs: hiddenSegs, dateProfile: props.dateProfile, todayRange: props.todayRange, extraDateSpan: extraDateSpan, popoverContent: () => (createElement(Fragment, null, hiddenSegs.map((seg) => {
848
+ return (createElement(MoreLinkContainer, { elClasses: ['fc-timeline-more-link'], allDayDate: null, segs: hiddenSegs, hiddenSegs: hiddenSegs, dateProfile: props.dateProfile, todayRange: props.todayRange, extraDateSpan: extraDateSpan, popoverContent: () => (createElement(Fragment, null, hiddenSegs.map((seg) => {
969
849
  let instanceId = seg.eventRange.instance.instanceId;
970
- return (createElement("div", { key: instanceId, style: { visibility: props.isForcedInvisible[instanceId] ? 'hidden' : '' } },
850
+ return (createElement("div", { key: instanceId, style: { visibility: forcedInvisibleMap[instanceId] ? 'hidden' : '' } },
971
851
  createElement(TimelineEvent, Object.assign({ isTimeScale: props.isTimeScale, seg: seg, isDragging: false, isResizing: false, isDateSelecting: false, isSelected: instanceId === props.eventSelection }, getSegMeta(seg, props.todayRange, props.nowDate)))));
972
- }))) }, (InnerContent) => (createElement(InnerContent, { elTag: "div", elClasses: ['fc-timeline-more-link-inner', 'fc-sticky'] }))));
852
+ }))) }, (InnerContent) => (createElement(InnerContent, { elTag: "div", elClasses: ['fc-timeline-more-link-inner', 'fc-sticky-x'] }))));
853
+ }
854
+ }
855
+
856
+ /*
857
+ TODO: make DRY with other Event Harnesses
858
+ */
859
+ class TimelineEventHarness extends Component {
860
+ constructor() {
861
+ super(...arguments);
862
+ // ref
863
+ this.rootElRef = createRef();
864
+ }
865
+ render() {
866
+ const { props } = this;
867
+ return (createElement("div", { className: "fc-abs", style: props.style, ref: this.rootElRef }, props.children));
868
+ }
869
+ componentDidMount() {
870
+ const rootEl = this.rootElRef.current; // TODO: make dynamic with useEffect
871
+ this.detachHeight = watchHeight(rootEl, (height) => {
872
+ setRef(this.props.heightRef, height);
873
+ });
874
+ }
875
+ componentWillUnmount() {
876
+ this.detachHeight();
973
877
  }
974
878
  }
975
879
 
880
+ /*
881
+ TODO: split TimelineLaneBg and TimelineLaneFg?
882
+ */
976
883
  class TimelineLane extends BaseComponent {
977
884
  constructor() {
978
885
  super(...arguments);
979
- this.slicer = new TimelineLaneSlicer();
886
+ // memo
980
887
  this.sortEventSegs = memoize(sortEventSegs);
981
- this.harnessElRefs = new RefMap();
982
- this.moreElRefs = new RefMap();
983
- this.innerElRef = createRef();
984
- // TODO: memoize event positioning
985
- this.state = {
986
- eventInstanceHeights: {},
987
- moreLinkHeights: {},
888
+ // refs
889
+ this.segHeightRefMap = new RefMap(() => {
890
+ afterSize(this.handleSegHeights);
891
+ });
892
+ this.moreLinkHeightRefMap = new RefMap(() => {
893
+ afterSize(this.handleMoreLinkHeights);
894
+ });
895
+ // internal
896
+ this.slicer = new TimelineLaneSlicer();
897
+ this.handleMoreLinkHeights = () => {
898
+ this.setState({ moreLinkHeightRev: this.moreLinkHeightRefMap.rev }); // will trigger rerender
988
899
  };
989
- this.handleResize = (isForced) => {
990
- if (isForced) {
991
- this.updateSize();
992
- }
900
+ this.handleSegHeights = () => {
901
+ this.setState({ segHeightRev: this.segHeightRefMap.rev }); // will trigger rerender
993
902
  };
994
903
  }
904
+ /*
905
+ TODO: lots of memoization needed here!
906
+ */
995
907
  render() {
996
- let { props, state, context } = this;
908
+ let { props, context, segHeightRefMap } = this;
997
909
  let { options } = context;
998
910
  let { dateProfile, tDateProfile } = props;
999
911
  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...
@@ -1002,106 +914,275 @@ class TimelineLane extends BaseComponent {
1002
914
  (slicedProps.eventResize ? slicedProps.eventResize.segs : null) ||
1003
915
  [];
1004
916
  let fgSegs = this.sortEventSegs(slicedProps.fgEventSegs, options.eventOrder);
1005
- let fgSegHCoords = computeSegHCoords(fgSegs, options.eventMinWidth, props.timelineCoords);
1006
- let [fgPlacements, fgHeight] = computeFgSegPlacements(fgSegs, fgSegHCoords, state.eventInstanceHeights, state.moreLinkHeights, options.eventOrderStrict, options.eventMaxStack);
1007
- let isForcedInvisible = // TODO: more convenient
917
+ let fgSegHorizontals = props.slotWidth != null
918
+ ? computeManySegHorizontals(fgSegs, options.eventMinWidth, context.dateEnv, tDateProfile, props.slotWidth)
919
+ : {};
920
+ let [fgSegTops, fgSegsBottom, hiddenGroups, hiddenGroupTops] = computeFgSegPlacements(// verticals
921
+ fgSegs, fgSegHorizontals, segHeightRefMap.current, options.eventOrderStrict, options.eventMaxStack);
922
+ let moreLinksBottom = computeMoreLinkMaxBottom(hiddenGroups, hiddenGroupTops, this.moreLinkHeightRefMap.current);
923
+ let innerHeight = Math.max(moreLinksBottom, fgSegsBottom);
924
+ let forcedInvisibleMap = // TODO: more convenient/DRY
1008
925
  (slicedProps.eventDrag ? slicedProps.eventDrag.affectedInstances : null) ||
1009
926
  (slicedProps.eventResize ? slicedProps.eventResize.affectedInstances : null) ||
1010
927
  {};
1011
928
  return (createElement(Fragment, null,
1012
- createElement(TimelineLaneBg, { businessHourSegs: slicedProps.businessHourSegs, bgEventSegs: slicedProps.bgEventSegs, timelineCoords: props.timelineCoords, eventResizeSegs: slicedProps.eventResize ? slicedProps.eventResize.segs : [] /* bad new empty array? */, dateSelectionSegs: slicedProps.dateSelectionSegs, nowDate: props.nowDate, todayRange: props.todayRange }),
1013
- createElement("div", { className: "fc-timeline-events fc-scrollgrid-sync-inner", ref: this.innerElRef, style: { height: fgHeight } },
1014
- this.renderFgSegs(fgPlacements, isForcedInvisible, false, false, false),
1015
- this.renderFgSegs(buildMirrorPlacements(mirrorSegs, props.timelineCoords, fgPlacements), {}, Boolean(slicedProps.eventDrag), Boolean(slicedProps.eventResize), false))));
929
+ createElement(TimelineLaneBg, { tDateProfile: tDateProfile, nowDate: props.nowDate, todayRange: props.todayRange,
930
+ // content
931
+ bgEventSegs: slicedProps.bgEventSegs, businessHourSegs: slicedProps.businessHourSegs, dateSelectionSegs: slicedProps.dateSelectionSegs, eventResizeSegs: slicedProps.eventResize ? slicedProps.eventResize.segs : [] /* bad new empty array? */,
932
+ // dimensions
933
+ slotWidth: props.slotWidth }),
934
+ createElement("div", { className: [
935
+ 'fc-timeline-events',
936
+ 'fc-content-box',
937
+ options.eventOverlap === false // TODO: fix bad default
938
+ ? 'fc-timeline-events-overlap-disabled'
939
+ : 'fc-timeline-events-overlap-enabled'
940
+ ].join(' '), style: { height: innerHeight } },
941
+ this.renderFgSegs(fgSegs, fgSegHorizontals, fgSegTops, forcedInvisibleMap, hiddenGroups, hiddenGroupTops, false, // isDragging
942
+ false, // isResizing
943
+ false),
944
+ this.renderFgSegs(mirrorSegs, props.slotWidth // TODO: memoize
945
+ ? computeManySegHorizontals(mirrorSegs, options.eventMinWidth, context.dateEnv, tDateProfile, props.slotWidth)
946
+ : {}, fgSegTops, {}, // forcedInvisibleMap
947
+ [], {}, Boolean(slicedProps.eventDrag), Boolean(slicedProps.eventResize), false))));
948
+ }
949
+ renderFgSegs(segs, segHorizontals, segTops, forcedInvisibleMap, hiddenGroups, hiddenGroupTops, isDragging, isResizing, isDateSelecting) {
950
+ let { props, context, segHeightRefMap, moreLinkHeightRefMap } = this;
951
+ let isMirror = isDragging || isResizing || isDateSelecting;
952
+ return (createElement(Fragment, null,
953
+ segs.map((seg) => {
954
+ const { instanceId } = seg.eventRange.instance;
955
+ const segTop = segTops[instanceId];
956
+ const segHorizontal = segHorizontals[instanceId];
957
+ const isVisible = isMirror ||
958
+ (segHorizontal && segTop != null && !forcedInvisibleMap[instanceId]);
959
+ return (createElement(TimelineEventHarness, { key: instanceId, style: Object.assign({ visibility: isVisible ? '' : 'hidden', top: segTop || 0 }, horizontalsToCss(segHorizontal, context.isRtl)), heightRef: isMirror ? undefined : segHeightRefMap.createRef(instanceId) },
960
+ createElement(TimelineEvent, Object.assign({ isTimeScale: props.tDateProfile.isTimeScale, seg: seg, isDragging: isDragging, isResizing: isResizing, isDateSelecting: isDateSelecting, isSelected: instanceId === props.eventSelection /* TODO: bad for mirror? */ }, getSegMeta(seg, props.todayRange, props.nowDate)))));
961
+ }),
962
+ hiddenGroups.map((hiddenGroup) => (createElement(TimelineEventHarness, { key: hiddenGroup.key, style: Object.assign({ top: hiddenGroupTops[hiddenGroup.key] || 0 }, horizontalsToCss({
963
+ start: hiddenGroup.span.start,
964
+ size: hiddenGroup.span.end - hiddenGroup.span.start
965
+ }, context.isRtl)), heightRef: moreLinkHeightRefMap.createRef(hiddenGroup.key) },
966
+ createElement(TimelineLaneMoreLink, { hiddenSegs: hiddenGroup.segs /* TODO: make SegGroup generic! */, dateProfile: props.dateProfile, nowDate: props.nowDate, todayRange: props.todayRange, isTimeScale: props.tDateProfile.isTimeScale, eventSelection: props.eventSelection, resourceId: props.resourceId, forcedInvisibleMap: forcedInvisibleMap }))))));
967
+ }
968
+ }
969
+
970
+ class TimelineHeaderCell extends BaseComponent {
971
+ constructor() {
972
+ super(...arguments);
973
+ // memo
974
+ this.refineRenderProps = memoizeObjArg(refineRenderProps);
975
+ this.buildCellNavLinkAttrs = memoize(buildCellNavLinkAttrs);
976
+ // ref
977
+ this.innerElRef = createRef();
978
+ // TODO: unset width/height ref on unmount?
979
+ }
980
+ render() {
981
+ let { props, context } = this;
982
+ let { dateEnv, options } = context;
983
+ let { cell, dateProfile, tDateProfile } = props;
984
+ // the cell.rowUnit is f'd
985
+ // giving 'month' for a 3-day view
986
+ // workaround: to infer day, do NOT time
987
+ let dateMeta = getDateMeta(cell.date, props.todayRange, props.nowDate, dateProfile);
988
+ let renderProps = this.refineRenderProps({
989
+ level: props.rowLevel,
990
+ dateMarker: cell.date,
991
+ text: cell.text,
992
+ dateEnv: context.dateEnv,
993
+ viewApi: context.viewApi,
994
+ });
995
+ return (createElement(ContentContainer, { elTag: "div", elClasses: [
996
+ 'fc-timeline-slot-label',
997
+ 'fc-timeline-slot',
998
+ cell.isWeekStart ? 'fc-timeline-slot-em' : '',
999
+ 'fc-header-cell',
1000
+ 'fc-cell',
1001
+ 'fc-flex-column',
1002
+ 'fc-justify-center',
1003
+ props.isCentered ? 'fc-align-center' : 'fc-align-start',
1004
+ ...( // TODO: so slot classnames for week/month/bigger. see note above about rowUnit
1005
+ cell.rowUnit === 'time' ?
1006
+ getSlotClassNames(dateMeta, context.theme) :
1007
+ getDayClassNames(dateMeta, context.theme)),
1008
+ ], elAttrs: {
1009
+ 'data-date': dateEnv.formatIso(cell.date, {
1010
+ omitTime: !tDateProfile.isTimeScale,
1011
+ omitTimeZoneOffset: true,
1012
+ }),
1013
+ }, elStyle: {
1014
+ width: props.slotWidth != null
1015
+ ? props.slotWidth * cell.colspan
1016
+ : undefined,
1017
+ }, renderProps: renderProps, generatorName: "slotLabelContent", customGenerator: options.slotLabelContent, defaultGenerator: renderInnerContent, classNameGenerator: options.slotLabelClassNames, didMount: options.slotLabelDidMount, willUnmount: options.slotLabelWillUnmount }, (InnerContent) => (createElement("div", { ref: this.innerElRef, className: [
1018
+ 'fc-flex-column',
1019
+ props.isSticky ? 'fc-sticky-x' : '',
1020
+ ].join(' ') },
1021
+ createElement(InnerContent, { elTag: "a", elClasses: [
1022
+ 'fc-cell-inner',
1023
+ 'fc-padding-md',
1024
+ ], elAttrs: this.buildCellNavLinkAttrs(context, cell.date, cell.rowUnit) })))));
1016
1025
  }
1017
1026
  componentDidMount() {
1018
- this.updateSize();
1019
- this.context.addResizeHandler(this.handleResize);
1020
- }
1021
- componentDidUpdate(prevProps, prevState) {
1022
- if (prevProps.eventStore !== this.props.eventStore || // external thing changed?
1023
- prevProps.timelineCoords !== this.props.timelineCoords || // external thing changed?
1024
- prevState.moreLinkHeights !== this.state.moreLinkHeights // HACK. see addStateEquality
1025
- ) {
1026
- this.updateSize();
1027
- }
1027
+ const { props } = this;
1028
+ const innerEl = this.innerElRef.current; // TODO: make dynamic with useEffect
1029
+ this.detachSize = watchSize(innerEl, (width, height) => {
1030
+ setRef(props.innerWidthRef, width);
1031
+ setRef(props.innerHeightRef, height);
1032
+ // HACK for sticky-centering
1033
+ innerEl.style.left = innerEl.style.right =
1034
+ (props.isCentered && props.isSticky)
1035
+ ? `calc(50% - ${width / 2}px)`
1036
+ : '';
1037
+ });
1028
1038
  }
1029
1039
  componentWillUnmount() {
1030
- this.context.removeResizeHandler(this.handleResize);
1031
- }
1032
- updateSize() {
1033
- let { props } = this;
1034
- let { timelineCoords } = props;
1035
- const innerEl = this.innerElRef.current;
1036
- if (props.onHeightChange) {
1037
- props.onHeightChange(innerEl, false);
1038
- }
1039
- if (timelineCoords) {
1040
- this.setState({
1041
- eventInstanceHeights: mapHash(this.harnessElRefs.currentMap, (harnessEl) => (Math.round(harnessEl.getBoundingClientRect().height))),
1042
- moreLinkHeights: mapHash(this.moreElRefs.currentMap, (moreEl) => (Math.round(moreEl.getBoundingClientRect().height))),
1043
- }, () => {
1044
- if (props.onHeightChange) {
1045
- props.onHeightChange(innerEl, true);
1046
- }
1047
- });
1048
- }
1049
- // hack
1050
- if (props.syncParentMinHeight) {
1051
- innerEl.parentElement.style.minHeight = innerEl.style.height;
1052
- }
1040
+ this.detachSize();
1053
1041
  }
1054
- renderFgSegs(segPlacements, isForcedInvisible, isDragging, isResizing, isDateSelecting) {
1055
- let { harnessElRefs, moreElRefs, props, context } = this;
1056
- let isMirror = isDragging || isResizing || isDateSelecting;
1057
- return (createElement(Fragment, null, segPlacements.map((segPlacement) => {
1058
- let { seg, hcoords, top } = segPlacement;
1059
- if (Array.isArray(seg)) { // a more-link
1060
- let isoStr = buildIsoString(computeEarliestSegStart(seg));
1061
- return (createElement(TimelineLaneMoreLink, { key: 'm:' + isoStr /* "m" for "more" */, elRef: moreElRefs.createRef(isoStr), hiddenSegs: seg, placement: segPlacement, dateProfile: props.dateProfile, nowDate: props.nowDate, todayRange: props.todayRange, isTimeScale: props.tDateProfile.isTimeScale, eventSelection: props.eventSelection, resourceId: props.resourceId, isForcedInvisible: isForcedInvisible }));
1042
+ }
1043
+ // Utils
1044
+ // -------------------------------------------------------------------------------------------------
1045
+ function buildCellNavLinkAttrs(context, cellDate, rowUnit) {
1046
+ return (rowUnit && rowUnit !== 'time')
1047
+ ? buildNavLinkAttrs(context, cellDate, rowUnit)
1048
+ : {};
1049
+ }
1050
+ function renderInnerContent(renderProps) {
1051
+ return renderProps.text;
1052
+ }
1053
+ function refineRenderProps(input) {
1054
+ return {
1055
+ level: input.level,
1056
+ date: input.dateEnv.toDate(input.dateMarker),
1057
+ view: input.viewApi,
1058
+ text: input.text,
1059
+ };
1060
+ }
1061
+
1062
+ class TimelineHeaderRow extends BaseComponent {
1063
+ constructor() {
1064
+ super(...arguments);
1065
+ // refs
1066
+ this.innerWidthRefMap = new RefMap(() => {
1067
+ afterSize(this.handleInnerWidths);
1068
+ });
1069
+ this.innerHeightRefMap = new RefMap(() => {
1070
+ afterSize(this.handleInnerHeights);
1071
+ });
1072
+ this.handleInnerWidths = () => {
1073
+ const innerWidthMap = this.innerWidthRefMap.current;
1074
+ let max = 0;
1075
+ for (const innerWidth of innerWidthMap.values()) {
1076
+ max = Math.max(max, innerWidth);
1077
+ }
1078
+ // TODO: ensure not equal?
1079
+ setRef(this.props.innerWidthRef, max);
1080
+ };
1081
+ this.handleInnerHeights = () => {
1082
+ const innerHeightMap = this.innerHeightRefMap.current;
1083
+ let max = 0;
1084
+ for (const innerHeight of innerHeightMap.values()) {
1085
+ max = Math.max(max, innerHeight);
1062
1086
  }
1063
- let instanceId = seg.eventRange.instance.instanceId;
1064
- let isVisible = isMirror || Boolean(!isForcedInvisible[instanceId] && hcoords && top !== null);
1065
- let hStyle = coordsToCss(hcoords, context.isRtl);
1066
- return (createElement("div", { key: 'e:' + instanceId /* "e" for "event" */, ref: isMirror ? null : harnessElRefs.createRef(instanceId), className: "fc-timeline-event-harness", style: Object.assign({ visibility: isVisible ? '' : 'hidden', top: top || 0 }, hStyle) },
1067
- createElement(TimelineEvent, Object.assign({ isTimeScale: props.tDateProfile.isTimeScale, seg: seg, isDragging: isDragging, isResizing: isResizing, isDateSelecting: isDateSelecting, isSelected: instanceId === props.eventSelection /* TODO: bad for mirror? */ }, getSegMeta(seg, props.todayRange, props.nowDate)))));
1087
+ // TODO: ensure not equal?
1088
+ setRef(this.props.innerHeighRef, max);
1089
+ };
1090
+ }
1091
+ render() {
1092
+ const { props, innerWidthRefMap, innerHeightRefMap } = this;
1093
+ const isCentered = !(props.tDateProfile.isTimeScale && props.isLastRow);
1094
+ const isSticky = !props.isLastRow;
1095
+ return (createElement("div", { className: 'fc-row', style: { height: props.height } }, props.cells.map((cell) => {
1096
+ // TODO: make this part of the cell obj?
1097
+ // TODO: rowUnit seems wrong sometimes. says 'month' when it should be day
1098
+ // TODO: rowUnit is relevant to whole row. put it on a row object, not the cells
1099
+ const key = cell.rowUnit + ':' + cell.date.toISOString();
1100
+ 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,
1101
+ // refs
1102
+ innerWidthRef: innerWidthRefMap.createRef(key), innerHeightRef: innerHeightRefMap.createRef(key),
1103
+ // dimensions
1104
+ slotWidth: props.slotWidth }));
1068
1105
  })));
1069
1106
  }
1070
1107
  }
1071
- TimelineLane.addStateEquality({
1072
- eventInstanceHeights: isPropsEqual,
1073
- moreLinkHeights: isPropsEqual,
1074
- });
1075
- function buildMirrorPlacements(mirrorSegs, timelineCoords, fgPlacements) {
1076
- if (!mirrorSegs.length || !timelineCoords) {
1077
- return [];
1078
- }
1079
- let topsByInstanceId = buildAbsoluteTopHash(fgPlacements); // TODO: cache this at first render?
1080
- return mirrorSegs.map((seg) => ({
1081
- seg,
1082
- hcoords: timelineCoords.rangeToCoords(seg),
1083
- top: topsByInstanceId[seg.eventRange.instance.instanceId],
1084
- }));
1108
+
1109
+ class TimelineNowIndicatorLine extends BaseComponent {
1110
+ render() {
1111
+ const { props, context } = this;
1112
+ return (createElement("div", { className: "fc-timeline-now-indicator-container" },
1113
+ createElement(NowIndicatorContainer // TODO: make separate component?
1114
+ , { elClasses: ['fc-timeline-now-indicator-line'], elStyle: props.slotWidth != null
1115
+ ? horizontalCoordToCss(dateToCoord(props.nowDate, context.dateEnv, props.tDateProfile, props.slotWidth), context.isRtl)
1116
+ : {}, isAxis: false, date: props.nowDate })));
1117
+ }
1085
1118
  }
1086
- function buildAbsoluteTopHash(placements) {
1087
- let topsByInstanceId = {};
1088
- for (let placement of placements) {
1089
- let { seg } = placement;
1090
- if (!Array.isArray(seg)) { // doesn't represent a more-link
1091
- topsByInstanceId[seg.eventRange.instance.instanceId] = placement.top;
1092
- }
1119
+
1120
+ class TimelineNowIndicatorArrow extends BaseComponent {
1121
+ render() {
1122
+ const { props, context } = this;
1123
+ return (createElement("div", { className: "fc-timeline-now-indicator-container" },
1124
+ createElement(NowIndicatorContainer, { elClasses: ['fc-timeline-now-indicator-arrow'], elStyle: props.slotWidth != null
1125
+ ? horizontalCoordToCss(dateToCoord(props.nowDate, context.dateEnv, props.tDateProfile, props.slotWidth), context.isRtl)
1126
+ : {}, isAxis: true, date: props.nowDate })));
1093
1127
  }
1094
- return topsByInstanceId;
1095
1128
  }
1096
1129
 
1097
- class TimelineGrid extends DateComponent {
1130
+ class TimelineView extends DateComponent {
1098
1131
  constructor() {
1099
1132
  super(...arguments);
1100
- this.slatsRef = createRef();
1101
- this.state = {
1102
- coords: null,
1133
+ // memoized
1134
+ this.buildTimelineDateProfile = memoize(buildTimelineDateProfile);
1135
+ this.computeSlotWidth = memoize(computeSlotWidth);
1136
+ // refs
1137
+ this.headerScrollerRef = createRef();
1138
+ this.bodyScrollerRef = createRef();
1139
+ this.footerScrollerRef = createRef();
1140
+ // Sizing
1141
+ // -----------------------------------------------------------------------------------------------
1142
+ this.handleHeaderSlotInnerWidth = (innerWidth) => {
1143
+ this.headerSlotInnerWidth = innerWidth;
1144
+ afterSize(this.handleSlotInnerWidths);
1145
+ };
1146
+ this.handleBodySlotInnerWidth = (innerWidth) => {
1147
+ this.bodySlotInnerWidth = innerWidth;
1148
+ afterSize(this.handleSlotInnerWidths);
1149
+ };
1150
+ this.handleSlotInnerWidths = () => {
1151
+ const { state } = this;
1152
+ const slotInnerWidth = Math.max(this.headerSlotInnerWidth, this.bodySlotInnerWidth);
1153
+ if (state.slotInnerWidth !== slotInnerWidth) {
1154
+ this.setState({ slotInnerWidth });
1155
+ }
1156
+ };
1157
+ this.handleScrollerWidth = (scrollerWidth) => {
1158
+ this.setState({
1159
+ scrollerWidth,
1160
+ });
1161
+ };
1162
+ this.handleLeftScrollbarWidth = (leftScrollbarWidth) => {
1163
+ this.setState({
1164
+ leftScrollbarWidth
1165
+ });
1166
+ };
1167
+ this.handleRightScrollbarWidth = (rightScrollbarWidth) => {
1168
+ this.setState({
1169
+ rightScrollbarWidth
1170
+ });
1103
1171
  };
1104
- this.handeEl = (el) => {
1172
+ this.timeScrollResponder = new ScrollResponder((time) => {
1173
+ const { props, context, tDateProfile, slotWidth } = this;
1174
+ if (slotWidth != null) {
1175
+ const x = timeToCoord(time, context.dateEnv, props.dateProfile, tDateProfile, slotWidth) +
1176
+ (context.isRtl ? -1 : 1); // overcome border. TODO: DRY this up
1177
+ this.syncedScroller.scrollTo({ x });
1178
+ return true;
1179
+ }
1180
+ return false;
1181
+ });
1182
+ // Hit System
1183
+ // -----------------------------------------------------------------------------------------------
1184
+ this.handeBodyEl = (el) => {
1185
+ this.bodyEl = el;
1105
1186
  if (el) {
1106
1187
  this.context.registerInteractiveComponent(this, { el });
1107
1188
  }
@@ -1109,45 +1190,141 @@ class TimelineGrid extends DateComponent {
1109
1190
  this.context.unregisterInteractiveComponent(this);
1110
1191
  }
1111
1192
  };
1112
- this.handleCoords = (coords) => {
1113
- this.setState({ coords });
1114
- if (this.props.onSlatCoords) {
1115
- this.props.onSlatCoords(coords);
1116
- }
1117
- };
1118
1193
  }
1119
1194
  render() {
1120
- let { props, state, context } = this;
1121
- let { options } = context;
1122
- let { dateProfile, tDateProfile } = props;
1123
- let timerUnit = greatestDurationDenominator(tDateProfile.slotDuration).unit;
1124
- return (createElement("div", { className: "fc-timeline-body", ref: this.handeEl, style: {
1125
- minWidth: props.tableMinWidth,
1126
- height: props.clientHeight,
1127
- width: props.clientWidth,
1128
- } },
1129
- createElement(NowTimer, { unit: timerUnit }, (nowDate, todayRange) => (createElement(Fragment, null,
1130
- createElement(TimelineSlats, { ref: this.slatsRef, dateProfile: dateProfile, tDateProfile: tDateProfile, nowDate: nowDate, todayRange: todayRange, clientWidth: props.clientWidth, tableColGroupNode: props.tableColGroupNode, tableMinWidth: props.tableMinWidth, onCoords: this.handleCoords, onScrollLeftRequest: props.onScrollLeftRequest }),
1131
- createElement(TimelineLane, { dateProfile: dateProfile, tDateProfile: props.tDateProfile, nowDate: nowDate, todayRange: todayRange, nextDayThreshold: options.nextDayThreshold, businessHours: props.businessHours, eventStore: props.eventStore, eventUiBases: props.eventUiBases, dateSelection: props.dateSelection, eventSelection: props.eventSelection, eventDrag: props.eventDrag, eventResize: props.eventResize, timelineCoords: state.coords, syncParentMinHeight: true }),
1132
- (options.nowIndicator && state.coords && state.coords.isDateInRange(nowDate)) && (createElement("div", { className: "fc-timeline-now-indicator-container" },
1133
- createElement(NowIndicatorContainer, { elClasses: ['fc-timeline-now-indicator-line'], elStyle: coordToCss(state.coords.dateToCoord(nowDate), context.isRtl), isAxis: false, date: nowDate }))))))));
1134
- }
1135
- // Hit System
1136
- // ------------------------------------------------------------------------------------------
1195
+ const { props, state, context } = this;
1196
+ const { options } = context;
1197
+ /* date */
1198
+ const tDateProfile = this.tDateProfile = this.buildTimelineDateProfile(props.dateProfile, context.dateEnv, options, context.dateProfileGenerator);
1199
+ const { cellRows } = tDateProfile;
1200
+ const timerUnit = greatestDurationDenominator(tDateProfile.slotDuration).unit;
1201
+ /* table settings */
1202
+ const verticalScrolling = !props.forPrint && !getIsHeightAuto(options);
1203
+ const stickyHeaderDates = !props.forPrint && getStickyHeaderDates(options);
1204
+ const stickyFooterScrollbar = !props.forPrint && getStickyFooterScrollbar(options);
1205
+ /* table positions */
1206
+ const [canvasWidth, slotWidth] = this.computeSlotWidth(tDateProfile.slotCnt, tDateProfile.slotsPerLabel, options.slotMinWidth, state.slotInnerWidth, // is ACTUALLY the label width. rename?
1207
+ state.scrollerWidth);
1208
+ this.slotWidth = slotWidth;
1209
+ return (createElement(NowTimer, { unit: timerUnit }, (nowDate, todayRange) => {
1210
+ const enableNowIndicator = // TODO: DRY
1211
+ options.nowIndicator &&
1212
+ slotWidth != null &&
1213
+ rangeContainsMarker(props.dateProfile.currentRange, nowDate);
1214
+ return (createElement(ViewContainer, { viewSpec: context.viewSpec, elClasses: [
1215
+ 'fc-timeline-view',
1216
+ 'fc-flex-column',
1217
+ 'fc-border',
1218
+ ] },
1219
+ createElement(Scroller, { horizontal: true, hideScrollbars: true, elClassNames: [
1220
+ 'fc-timeline-header',
1221
+ 'fc-rowgroup',
1222
+ stickyHeaderDates ? 'fc-sticky-header' : '',
1223
+ ], ref: this.headerScrollerRef },
1224
+ createElement("div", { className: 'fc-rel fc-content-box' // origin for now-indicator
1225
+ , style: {
1226
+ width: canvasWidth,
1227
+ paddingLeft: state.leftScrollbarWidth,
1228
+ paddingRight: state.rightScrollbarWidth,
1229
+ } },
1230
+ createElement("div", null, cellRows.map((cells, rowLevel) => {
1231
+ const isLast = rowLevel === cellRows.length - 1;
1232
+ return (createElement(TimelineHeaderRow, { key: rowLevel, dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: nowDate, todayRange: todayRange, rowLevel: rowLevel, isLastRow: isLast, cells: cells, slotWidth: slotWidth, innerWidthRef: isLast ? this.handleHeaderSlotInnerWidth : undefined }));
1233
+ })),
1234
+ enableNowIndicator && (
1235
+ // TODO: make this positioned WITHIN padding?
1236
+ createElement(TimelineNowIndicatorArrow, { tDateProfile: tDateProfile, nowDate: nowDate, slotWidth: slotWidth })))),
1237
+ createElement(Scroller, { vertical: verticalScrolling, horizontal: true, elClassNames: [
1238
+ 'fc-timeline-body',
1239
+ 'fc-rowgroup',
1240
+ verticalScrolling ? 'fc-liquid' : '',
1241
+ ], ref: this.bodyScrollerRef, widthRef: this.handleScrollerWidth, leftScrollbarWidthRef: this.handleLeftScrollbarWidth, rightScrollbarWidthRef: this.handleRightScrollbarWidth },
1242
+ createElement("div", { className: "fc-rel fc-grow", style: {
1243
+ width: canvasWidth,
1244
+ }, ref: this.handeBodyEl },
1245
+ createElement(TimelineSlats, { dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: nowDate, todayRange: todayRange,
1246
+ // ref
1247
+ innerWidthRef: this.handleBodySlotInnerWidth,
1248
+ // dimensions
1249
+ slotWidth: slotWidth }),
1250
+ 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 }),
1251
+ enableNowIndicator && (createElement(TimelineNowIndicatorLine, { tDateProfile: tDateProfile, nowDate: nowDate, slotWidth: slotWidth })))),
1252
+ stickyFooterScrollbar && (createElement(Scroller, { ref: this.footerScrollerRef, horizontal: true },
1253
+ createElement("div", { style: { width: canvasWidth } })))));
1254
+ }));
1255
+ }
1256
+ // Lifecycle
1257
+ // -----------------------------------------------------------------------------------------------
1258
+ componentDidMount() {
1259
+ const { context } = this;
1260
+ const { options } = context;
1261
+ const ScrollerSyncer = getScrollerSyncerClass(this.context.pluginHooks);
1262
+ this.syncedScroller = new ScrollerSyncer(true); // horizontal=true
1263
+ this.updateSyncedScroller();
1264
+ context.emitter.on('_timeScrollRequest', this.timeScrollResponder.handleScroll);
1265
+ this.timeScrollResponder.handleScroll(options.scrollTime);
1266
+ }
1267
+ componentDidUpdate(prevProps) {
1268
+ const { options } = this.context;
1269
+ this.updateSyncedScroller();
1270
+ if (prevProps.dateProfile !== this.props.dateProfile && options.scrollTimeReset) {
1271
+ this.timeScrollResponder.handleScroll(options.scrollTime);
1272
+ }
1273
+ else {
1274
+ this.timeScrollResponder.drain();
1275
+ }
1276
+ }
1277
+ componentWillUnmount() {
1278
+ this.syncedScroller.destroy();
1279
+ this.context.emitter.off('_timeScrollRequest', this.timeScrollResponder.handleScroll);
1280
+ }
1281
+ // Scrolling
1282
+ // -----------------------------------------------------------------------------------------------
1283
+ updateSyncedScroller() {
1284
+ this.syncedScroller.handleChildren([
1285
+ this.headerScrollerRef.current,
1286
+ this.bodyScrollerRef.current,
1287
+ this.footerScrollerRef.current
1288
+ ], this.context.isRtl);
1289
+ }
1137
1290
  queryHit(positionLeft, positionTop, elWidth, elHeight) {
1138
- let slats = this.slatsRef.current;
1139
- let slatHit = slats.positionToHit(positionLeft);
1140
- if (slatHit) {
1291
+ const { props, context, tDateProfile, slotWidth } = this;
1292
+ const { dateEnv } = context;
1293
+ if (slotWidth) {
1294
+ const x = context.isRtl ? elWidth - positionLeft : positionLeft;
1295
+ const slatIndex = Math.floor(x / slotWidth);
1296
+ const slatX = slatIndex * slotWidth;
1297
+ const partial = (x - slatX) / slotWidth; // floating point number between 0 and 1
1298
+ const localSnapIndex = Math.floor(partial * tDateProfile.snapsPerSlot); // the snap # relative to start of slat
1299
+ let startDate = dateEnv.add(tDateProfile.slotDates[slatIndex], multiplyDuration(tDateProfile.snapDuration, localSnapIndex));
1300
+ let endDate = dateEnv.add(startDate, tDateProfile.snapDuration);
1301
+ // TODO: generalize this coord stuff to TimeGrid?
1302
+ let snapWidth = slotWidth / tDateProfile.snapsPerSlot;
1303
+ let startCoord = slatIndex * slotWidth + (snapWidth * localSnapIndex);
1304
+ let endCoord = startCoord + snapWidth;
1305
+ let left, right;
1306
+ if (context.isRtl) {
1307
+ left = elWidth - endCoord;
1308
+ right = elWidth - startCoord;
1309
+ }
1310
+ else {
1311
+ left = startCoord;
1312
+ right = endCoord;
1313
+ }
1141
1314
  return {
1142
- dateProfile: this.props.dateProfile,
1143
- dateSpan: slatHit.dateSpan,
1315
+ dateProfile: props.dateProfile,
1316
+ dateSpan: {
1317
+ range: { start: startDate, end: endDate },
1318
+ allDay: !tDateProfile.isTimeScale,
1319
+ },
1144
1320
  rect: {
1145
- left: slatHit.left,
1146
- right: slatHit.right,
1321
+ left,
1322
+ right,
1147
1323
  top: 0,
1148
1324
  bottom: elHeight,
1149
1325
  },
1150
- dayEl: slatHit.dayEl,
1326
+ // HACK. TODO: This is expensive to do every hit-query
1327
+ dayEl: this.bodyEl.querySelectorAll('.fc-timeline-slot')[slatIndex],
1151
1328
  layer: 0,
1152
1329
  };
1153
1330
  }
@@ -1155,89 +1332,4 @@ class TimelineGrid extends DateComponent {
1155
1332
  }
1156
1333
  }
1157
1334
 
1158
- class TimelineView extends DateComponent {
1159
- constructor() {
1160
- super(...arguments);
1161
- this.buildTimelineDateProfile = memoize(buildTimelineDateProfile);
1162
- this.scrollGridRef = createRef();
1163
- this.state = {
1164
- slatCoords: null,
1165
- slotCushionMaxWidth: null,
1166
- };
1167
- this.handleSlatCoords = (slatCoords) => {
1168
- this.setState({ slatCoords });
1169
- };
1170
- this.handleScrollLeftRequest = (scrollLeft) => {
1171
- let scrollGrid = this.scrollGridRef.current;
1172
- scrollGrid.forceScrollLeft(0, scrollLeft);
1173
- };
1174
- this.handleMaxCushionWidth = (slotCushionMaxWidth) => {
1175
- this.setState({
1176
- slotCushionMaxWidth: Math.ceil(slotCushionMaxWidth), // for less rerendering TODO: DRY
1177
- });
1178
- };
1179
- }
1180
- render() {
1181
- let { props, state, context } = this;
1182
- let { options } = context;
1183
- let stickyHeaderDates = !props.forPrint && getStickyHeaderDates(options);
1184
- let stickyFooterScrollbar = !props.forPrint && getStickyFooterScrollbar(options);
1185
- let tDateProfile = this.buildTimelineDateProfile(props.dateProfile, context.dateEnv, options, context.dateProfileGenerator);
1186
- let { slotMinWidth } = options;
1187
- let slatCols = buildSlatCols(tDateProfile, slotMinWidth || this.computeFallbackSlotMinWidth(tDateProfile));
1188
- let sections = [
1189
- {
1190
- type: 'header',
1191
- key: 'header',
1192
- isSticky: stickyHeaderDates,
1193
- chunks: [{
1194
- key: 'timeline',
1195
- content: (contentArg) => (createElement(TimelineHeader, { dateProfile: props.dateProfile, clientWidth: contentArg.clientWidth, clientHeight: contentArg.clientHeight, tableMinWidth: contentArg.tableMinWidth, tableColGroupNode: contentArg.tableColGroupNode, tDateProfile: tDateProfile, slatCoords: state.slatCoords, onMaxCushionWidth: slotMinWidth ? null : this.handleMaxCushionWidth })),
1196
- }],
1197
- },
1198
- {
1199
- type: 'body',
1200
- key: 'body',
1201
- liquid: true,
1202
- chunks: [{
1203
- key: 'timeline',
1204
- content: (contentArg) => (createElement(TimelineGrid, Object.assign({}, props, { clientWidth: contentArg.clientWidth, clientHeight: contentArg.clientHeight, tableMinWidth: contentArg.tableMinWidth, tableColGroupNode: contentArg.tableColGroupNode, tDateProfile: tDateProfile, onSlatCoords: this.handleSlatCoords, onScrollLeftRequest: this.handleScrollLeftRequest }))),
1205
- }],
1206
- },
1207
- ];
1208
- if (stickyFooterScrollbar) {
1209
- sections.push({
1210
- type: 'footer',
1211
- key: 'footer',
1212
- isSticky: true,
1213
- chunks: [{
1214
- key: 'timeline',
1215
- content: renderScrollShim,
1216
- }],
1217
- });
1218
- }
1219
- return (createElement(ViewContainer, { elClasses: [
1220
- 'fc-timeline',
1221
- options.eventOverlap === false ?
1222
- 'fc-timeline-overlap-disabled' :
1223
- '',
1224
- ], viewSpec: context.viewSpec },
1225
- createElement(ScrollGrid, { ref: this.scrollGridRef, liquid: !props.isHeightAuto && !props.forPrint, forPrint: props.forPrint, collapsibleWidth: false, colGroups: [
1226
- { cols: slatCols },
1227
- ], sections: sections })));
1228
- }
1229
- computeFallbackSlotMinWidth(tDateProfile) {
1230
- return Math.max(30, ((this.state.slotCushionMaxWidth || 0) / tDateProfile.slotsPerLabel));
1231
- }
1232
- }
1233
- function buildSlatCols(tDateProfile, slotMinWidth) {
1234
- return [{
1235
- span: tDateProfile.slotCnt,
1236
- minWidth: slotMinWidth || 1, // needs to be a non-zero number to trigger horizontal scrollbars!??????
1237
- }];
1238
- }
1239
-
1240
- var css_248z = ".fc .fc-timeline-body{min-height:100%;position:relative;z-index:1}.fc .fc-timeline-slots{bottom:0;position:absolute;top:0;z-index:1}.fc .fc-timeline-slots>table{height:100%}.fc .fc-timeline-slot-minor{border-style:dotted}.fc .fc-timeline-slot-frame{align-items:center;display:flex;justify-content:center}.fc .fc-timeline-header-row-chrono .fc-timeline-slot-frame{justify-content:flex-start}.fc .fc-timeline-header-row:last-child .fc-timeline-slot-frame{overflow:hidden}.fc .fc-timeline-slot-cushion{padding:4px 5px;white-space:nowrap}.fc-direction-ltr .fc-timeline-slot{border-right:0!important}.fc-direction-rtl .fc-timeline-slot{border-left:0!important}.fc .fc-timeline-now-indicator-container{bottom:0;left:0;position:absolute;right:0;top:0;width:0;z-index:4}.fc .fc-timeline-now-indicator-arrow,.fc .fc-timeline-now-indicator-line{border-color:var(--fc-now-indicator-color);border-style:solid;position:absolute;top:0}.fc .fc-timeline-now-indicator-arrow{border-left-color:transparent;border-right-color:transparent;border-width:6px 5px 0;margin:0 -6px}.fc .fc-timeline-now-indicator-line{border-width:0 0 0 1px;bottom:0;margin:0 -1px}.fc .fc-timeline-events{position:relative;width:0;z-index:3}.fc .fc-timeline-event-harness,.fc .fc-timeline-more-link{position:absolute;top:0}.fc-timeline-event{z-index:1}.fc-timeline-event.fc-event-mirror{z-index:2}.fc-timeline-event{align-items:center;border-radius:0;display:flex;font-size:var(--fc-small-font-size);margin-bottom:1px;padding:2px 1px;position:relative}.fc-timeline-event .fc-event-main{flex-grow:1;flex-shrink:1;min-width:0}.fc-timeline-event .fc-event-time{font-weight:700}.fc-timeline-event .fc-event-time,.fc-timeline-event .fc-event-title{padding:0 2px;white-space:nowrap}.fc-direction-ltr .fc-timeline-event.fc-event-end,.fc-direction-ltr .fc-timeline-more-link{margin-right:1px}.fc-direction-rtl .fc-timeline-event.fc-event-end,.fc-direction-rtl .fc-timeline-more-link{margin-left:1px}.fc-timeline-overlap-disabled .fc-timeline-event{margin-bottom:0;padding-bottom:5px;padding-top:5px}.fc-timeline-event:not(.fc-event-end):after,.fc-timeline-event:not(.fc-event-start):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):before,.fc-direction-rtl .fc-timeline-event:not(.fc-event-end):after{border-left:0}.fc-direction-ltr .fc-timeline-event:not(.fc-event-end):after,.fc-direction-rtl .fc-timeline-event:not(.fc-event-start):before{border-right:0}.fc-timeline-more-link{background:var(--fc-more-link-bg-color);color:var(--fc-more-link-text-color);cursor:pointer;font-size:var(--fc-small-font-size);padding:1px}.fc-timeline-more-link-inner{display:inline-block;left:0;padding:2px;right:0}.fc .fc-timeline-bg{bottom:0;left:0;position:absolute;right:0;top:0;width:0;z-index:2}.fc .fc-timeline-bg .fc-non-business{z-index:1}.fc .fc-timeline-bg .fc-bg-event{z-index:2}.fc .fc-timeline-bg .fc-highlight{z-index:3}.fc .fc-timeline-bg-harness{bottom:0;position:absolute;top:0}";
1241
- injectStyles(css_248z);
1242
-
1243
- export { TimelineCoords, TimelineHeader, TimelineHeaderRows, TimelineLane, TimelineLaneBg, TimelineLaneSlicer, TimelineSlats, TimelineView, buildSlatCols, buildTimelineDateProfile, coordToCss, coordsToCss };
1335
+ export { TimelineHeaderRow, TimelineLane, TimelineLaneBg, TimelineLaneSlicer, TimelineNowIndicatorArrow, TimelineNowIndicatorLine, TimelineSlats, TimelineView, buildTimelineDateProfile, computeSlotWidth, coordToCss, coordsToCss, createHorizontalStyle, createVerticalStyle, timeToCoord };