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