@fullcalendar/timeline 6.1.15 → 7.0.0-beta.1

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