@fullcalendar/timeline 6.1.15 → 7.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.global.js CHANGED
@@ -1,9 +1,9 @@
1
1
  /*!
2
- FullCalendar Timeline Plugin v6.1.15
2
+ FullCalendar Timeline Plugin v7.0.0-beta.0
3
3
  Docs & License: https://fullcalendar.io/docs/timeline-view-no-resources
4
4
  (c) 2024 Adam Shaw
5
5
  */
6
- FullCalendar.Timeline = (function (exports, core, premiumCommonPlugin, internal$1, preact, internal$2) {
6
+ FullCalendar.Timeline = (function (exports, core, premiumCommonPlugin, internal$1, preact) {
7
7
  'use strict';
8
8
 
9
9
  function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
@@ -448,152 +448,90 @@ 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();
525
495
  }
526
496
  }
527
497
 
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;
498
+ class TimelineSlats extends internal$1.BaseComponent {
499
+ constructor() {
500
+ super(...arguments);
501
+ this.innerWidthRefMap = new internal$1.RefMap(() => {
502
+ internal$1.afterSize(this.handleInnerWidths);
503
+ });
504
+ this.handleInnerWidths = () => {
505
+ const innerWidthMap = this.innerWidthRefMap.current;
506
+ let max = 0;
507
+ for (const innerWidth of innerWidthMap.values()) {
508
+ max = Math.max(max, innerWidth);
580
509
  }
581
- }
582
- return coord;
583
- }
584
- coordFromLeft(coord) {
585
- if (this.isRtl) {
586
- return this.outerCoordCache.originClientRect.width - coord;
587
- }
588
- return coord;
510
+ // TODO: check to see if changed before firing ref!? YES. do in other places too
511
+ internal$1.setRef(this.props.innerWidthRef, max);
512
+ };
589
513
  }
590
- // returned value is between 0 and the number of snaps
591
- computeDateSnapCoverage(date) {
592
- return computeDateSnapCoverage(date, this.tDateProfile, this.dateEnv);
514
+ render() {
515
+ let { props, innerWidthRefMap } = this;
516
+ let { tDateProfile, slotWidth } = props;
517
+ let { slotDates, isWeekStarts } = tDateProfile;
518
+ let isDay = !tDateProfile.isTimeScale && !tDateProfile.largeUnit;
519
+ return (preact.createElement("div", { className: "fc-timeline-slots fc-fill fc-flex-row" }, slotDates.map((slotDate, i) => {
520
+ let key = slotDate.toISOString();
521
+ return (preact.createElement(TimelineSlatCell, { key: key, date: slotDate, dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: props.nowDate, todayRange: props.todayRange, isEm: isWeekStarts[i], isDay: isDay,
522
+ // ref
523
+ innerWidthRef: innerWidthRefMap.createRef(key),
524
+ // dimensions
525
+ width: slotWidth }));
526
+ })));
593
527
  }
594
528
  }
529
+
530
+ /*
531
+ TODO: rename this file!
532
+ */
595
533
  // returned value is between 0 and the number of snaps
596
- function computeDateSnapCoverage(date, tDateProfile, dateEnv) {
534
+ function computeDateSnapCoverage$1(date, tDateProfile, dateEnv) {
597
535
  let snapDiff = dateEnv.countDurationsBetween(tDateProfile.normalizedRange.start, date, tDateProfile.snapDuration);
598
536
  if (snapDiff < 0) {
599
537
  return 0;
@@ -613,6 +551,9 @@ FullCalendar.Timeline = (function (exports, core, premiumCommonPlugin, internal$
613
551
  }
614
552
  return snapCoverage;
615
553
  }
554
+ /*
555
+ TODO: audit!!!
556
+ */
616
557
  function coordToCss(hcoord, isRtl) {
617
558
  if (hcoord === null) {
618
559
  return { left: '', right: '' };
@@ -622,6 +563,9 @@ FullCalendar.Timeline = (function (exports, core, premiumCommonPlugin, internal$
622
563
  }
623
564
  return { left: hcoord, right: '' };
624
565
  }
566
+ /*
567
+ TODO: audit!!!
568
+ */
625
569
  function coordsToCss(hcoords, isRtl) {
626
570
  if (!hcoords) {
627
571
  return { left: '', right: '' };
@@ -631,216 +575,141 @@ FullCalendar.Timeline = (function (exports, core, premiumCommonPlugin, internal$
631
575
  }
632
576
  return { left: hcoords.start, right: -hcoords.end };
633
577
  }
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 }))))))));
578
+ /*
579
+ TODO: DRY up with elsewhere?
580
+ */
581
+ function horizontalsToCss(hcoord, isRtl) {
582
+ if (!hcoord) {
583
+ return {};
657
584
  }
658
- componentDidMount() {
659
- this.updateSize();
585
+ if (isRtl) {
586
+ return { right: hcoord.start, width: hcoord.size };
660
587
  }
661
- componentDidUpdate() {
662
- this.updateSize();
588
+ else {
589
+ return { left: hcoord.start, width: hcoord.size };
663
590
  }
664
- updateSize() {
665
- if (this.props.onMaxCushionWidth) {
666
- this.props.onMaxCushionWidth(this.computeMaxCushionWidth());
667
- }
591
+ }
592
+ function horizontalCoordToCss(start, isRtl) {
593
+ if (isRtl) {
594
+ return { right: start };
668
595
  }
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));
596
+ else {
597
+ return { left: start };
671
598
  }
672
599
  }
673
600
 
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" }))));
601
+ function createVerticalStyle(props) {
602
+ if (props) {
603
+ return {
604
+ top: props.start,
605
+ height: props.size,
606
+ };
697
607
  }
698
608
  }
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
- }))));
609
+ function createHorizontalStyle(props, isRtl) {
610
+ if (props) {
611
+ return {
612
+ [isRtl ? 'right' : 'left']: props.start,
613
+ width: props.size,
614
+ };
711
615
  }
712
616
  }
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
- };
617
+ // Timeline-specific
618
+ // -------------------------------------------------------------------------------------------------
619
+ const MIN_SLOT_WIDTH = 30; // for real
620
+ /*
621
+ TODO: DRY with computeSlatHeight?
622
+ */
623
+ function computeSlotWidth(slatCnt, slatsPerLabel, slatMinWidth, labelInnerWidth, viewportWidth) {
624
+ if (labelInnerWidth == null || viewportWidth == null) {
625
+ return [undefined, undefined, false];
626
+ }
627
+ slatMinWidth = Math.max(slatMinWidth || 0, (labelInnerWidth + 1) / slatsPerLabel, MIN_SLOT_WIDTH);
628
+ const slatTryWidth = viewportWidth / slatCnt;
629
+ let slatLiquid;
630
+ let slatWidth;
631
+ if (slatTryWidth >= slatMinWidth) {
632
+ slatLiquid = true;
633
+ slatWidth = slatTryWidth;
731
634
  }
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 }))));
635
+ else {
636
+ slatLiquid = false;
637
+ slatWidth = Math.max(slatMinWidth, slatTryWidth);
741
638
  }
742
- componentDidMount() {
743
- this.updateSizing();
744
- this.scrollResponder = this.context.createScrollResponder(this.handleScrollRequest);
639
+ return [slatWidth * slatCnt, slatWidth, slatLiquid];
640
+ }
641
+ function timeToCoord(// pixels
642
+ time, dateEnv, dateProfile, tDateProfile, slowWidth) {
643
+ let date = dateEnv.add(dateProfile.activeRange.start, time);
644
+ if (!tDateProfile.isTimeScale) {
645
+ date = internal$1.startOfDay(date);
745
646
  }
746
- componentDidUpdate(prevProps) {
747
- this.updateSizing();
748
- this.scrollResponder.update(prevProps.dateProfile !== this.props.dateProfile);
647
+ return dateToCoord(date, dateEnv, tDateProfile, slowWidth);
648
+ }
649
+ function dateToCoord(// pixels
650
+ date, dateEnv, tDateProfile, slotWidth) {
651
+ let snapCoverage = computeDateSnapCoverage(date, tDateProfile, dateEnv);
652
+ let slotCoverage = snapCoverage / tDateProfile.snapsPerSlot;
653
+ return slotCoverage * slotWidth;
654
+ }
655
+ /*
656
+ returned value is between 0 and the number of snaps
657
+ */
658
+ function computeDateSnapCoverage(date, tDateProfile, dateEnv) {
659
+ let snapDiff = dateEnv.countDurationsBetween(tDateProfile.normalizedRange.start, date, tDateProfile.snapDuration);
660
+ if (snapDiff < 0) {
661
+ return 0;
749
662
  }
750
- componentWillUnmount() {
751
- this.scrollResponder.detach();
752
- if (this.props.onCoords) {
753
- this.props.onCoords(null);
754
- }
663
+ if (snapDiff >= tDateProfile.snapDiffToIndex.length) {
664
+ return tDateProfile.snapCnt;
755
665
  }
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
- }
666
+ let snapDiffInt = Math.floor(snapDiff);
667
+ let snapCoverage = tDateProfile.snapDiffToIndex[snapDiffInt];
668
+ if (internal$1.isInt(snapCoverage)) { // not an in-between value
669
+ snapCoverage += snapDiff - snapDiffInt; // add the remainder
771
670
  }
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;
671
+ else {
672
+ // a fractional value, meaning the date is not visible
673
+ // always round up in this case. works for start AND end dates in a range.
674
+ snapCoverage = Math.ceil(snapCoverage);
797
675
  }
798
- }
799
- function collectCellEls(elMap, slotDates) {
800
- return slotDates.map((slotDate) => {
801
- let key = slotDate.toISOString();
802
- return elMap[key];
803
- });
676
+ return snapCoverage;
804
677
  }
805
678
 
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
- }
679
+ function computeManySegHorizontals(segs, segMinWidth, dateEnv, tDateProfile, slotWidth) {
680
+ const res = {};
681
+ for (const seg of segs) {
682
+ res[seg.eventRange.instance.instanceId] = computeSegHorizontals(seg, segMinWidth, dateEnv, tDateProfile, slotWidth);
818
683
  }
819
- return hcoords;
684
+ return res;
685
+ }
686
+ function computeSegHorizontals(seg, segMinWidth, dateEnv, tDateProfile, slotWidth) {
687
+ const startCoord = dateToCoord(seg.start, dateEnv, tDateProfile, slotWidth);
688
+ const endCoord = dateToCoord(seg.end, dateEnv, tDateProfile, slotWidth);
689
+ let size = endCoord - startCoord;
690
+ if (segMinWidth) {
691
+ size = Math.max(size, segMinWidth);
692
+ }
693
+ return { start: startCoord, size };
820
694
  }
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
695
+ function computeFgSegPlacements(// mostly horizontals
696
+ segs, segHorizontals, segHeights, // keyed by instanceId
824
697
  strictOrder, maxStackCnt) {
825
- let segInputs = [];
826
- let crudePlacements = []; // when we don't know dims
698
+ let segEntries = [];
827
699
  for (let i = 0; i < segs.length; i += 1) {
828
700
  let seg = segs[i];
829
701
  let instanceId = seg.eventRange.instance.instanceId;
830
- let height = eventInstanceHeights[instanceId];
831
- let hcoords = segHCoords[i];
832
- if (height && hcoords) {
833
- segInputs.push({
702
+ let height = segHeights.get(instanceId);
703
+ let hcoords = segHorizontals[instanceId];
704
+ if (height != null && hcoords != null) {
705
+ segEntries.push({
834
706
  index: i,
835
- span: hcoords,
836
- thickness: height,
837
- });
838
- }
839
- else {
840
- crudePlacements.push({
841
707
  seg,
842
- hcoords,
843
- top: null,
708
+ span: {
709
+ start: hcoords.start,
710
+ end: hcoords.start + hcoords.size,
711
+ },
712
+ thickness: height,
844
713
  });
845
714
  }
846
715
  }
@@ -851,80 +720,85 @@ FullCalendar.Timeline = (function (exports, core, premiumCommonPlugin, internal$
851
720
  if (maxStackCnt != null) {
852
721
  hierarchy.maxStackCnt = maxStackCnt;
853
722
  }
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
- }));
723
+ let hiddenEntries = hierarchy.addSegs(segEntries);
860
724
  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
- }
725
+ let hiddenGroupEntries = hiddenGroups.map((hiddenGroup, index) => ({
726
+ index: segs.length + index,
727
+ segGroup: hiddenGroup,
728
+ span: hiddenGroup.span,
729
+ thickness: 1, // HACK to ensure it's placed
730
+ }));
884
731
  // add more-links into the hierarchy, but don't limit
885
732
  hierarchy.maxStackCnt = -1;
886
- hierarchy.addSegs(moreLinkInputs);
733
+ hierarchy.addSegs(hiddenGroupEntries);
887
734
  let visibleRects = hierarchy.toRects();
888
- let visiblePlacements = [];
889
- let maxHeight = 0;
735
+ let segTops = {};
736
+ let hiddenGroupTops = {};
890
737
  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);
738
+ const { seg, segGroup } = rect;
739
+ if (seg) { // regular seg
740
+ segTops[seg.eventRange.instance.instanceId] = rect.levelCoord;
741
+ }
742
+ else { // hiddenGroup
743
+ hiddenGroupTops[segGroup.key] = rect.levelCoord;
744
+ }
900
745
  }
901
746
  return [
902
- visiblePlacements.concat(crudePlacements, hiddenPlacements, moreLinkCrudePlacements),
903
- maxHeight,
747
+ segTops,
748
+ computeMaxBottom(segs, segTops, segHeights),
749
+ hiddenGroups,
750
+ hiddenGroupTops,
904
751
  ];
905
752
  }
753
+ function computeMaxBottom(segs, segTops, segHeights) {
754
+ let max = 0;
755
+ for (const seg of segs) {
756
+ const { instanceId } = seg.eventRange.instance;
757
+ const top = segTops[instanceId];
758
+ const height = segHeights.get(instanceId);
759
+ if (top != null && height != null) {
760
+ max = Math.max(max, top + height);
761
+ }
762
+ }
763
+ return max;
764
+ }
765
+ /*
766
+ TODO: converge with computeMaxBottom, but keys are different
767
+ */
768
+ function computeMoreLinkMaxBottom(hiddenGroups, hiddenGroupTops, hiddenGroupHeights) {
769
+ let max = 0;
770
+ for (const hiddenGroup of hiddenGroups) {
771
+ const top = hiddenGroupTops[hiddenGroup.key];
772
+ const height = hiddenGroupHeights.get(hiddenGroup.key);
773
+ if (top != null && height != null) {
774
+ max = Math.max(max, top + height);
775
+ }
776
+ }
777
+ return max;
778
+ }
906
779
 
907
780
  class TimelineLaneBg extends internal$1.BaseComponent {
908
781
  render() {
909
782
  let { props } = this;
910
783
  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);
784
+ return (preact.createElement("div", { className: "fc-timeline-bg" },
785
+ this.renderSegs(props.businessHourSegs || [], 'non-business'),
786
+ this.renderSegs(props.bgEventSegs || [], 'bg-event'),
787
+ this.renderSegs(highlightSeg, 'highlight')));
788
+ }
789
+ renderSegs(segs, fillType) {
790
+ let { tDateProfile, todayRange, nowDate, slotWidth } = this.props;
791
+ let { dateEnv, isRtl } = this.context;
792
+ return (preact.createElement(preact.Fragment, null, segs.map((seg) => {
793
+ let hStyle; // TODO
794
+ if (slotWidth != null) {
795
+ let segHorizontal = computeSegHorizontals(seg, undefined, dateEnv, tDateProfile, slotWidth);
796
+ hStyle = horizontalsToCss(segHorizontal, isRtl);
797
+ }
923
798
  return (preact.createElement("div", { key: internal$1.buildEventRangeKey(seg.eventRange), className: "fc-timeline-bg-harness", style: hStyle }, fillType === 'bg-event' ?
924
799
  preact.createElement(internal$1.BgEvent, Object.assign({ seg: seg }, internal$1.getSegMeta(seg, todayRange, nowDate))) :
925
800
  internal$1.renderFill(fillType)));
926
- });
927
- return preact.createElement(preact.Fragment, null, children);
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,84 @@ 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) => {
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) => {
977
855
  let instanceId = seg.eventRange.instance.instanceId;
978
- return (preact.createElement("div", { key: instanceId, style: { visibility: props.isForcedInvisible[instanceId] ? 'hidden' : '' } },
856
+ return (preact.createElement("div", { key: instanceId, style: { visibility: forcedInvisibleMap[instanceId] ? 'hidden' : '' } },
979
857
  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'] }))));
858
+ }))) }, (InnerContent) => (preact.createElement(InnerContent, { elTag: "div", elClasses: ['fc-timeline-more-link-inner', 'fc-sticky-x'] }))));
859
+ }
860
+ }
861
+
862
+ /*
863
+ TODO: make DRY with other Event Harnesses
864
+ */
865
+ class TimelineEventHarness extends preact.Component {
866
+ constructor() {
867
+ super(...arguments);
868
+ // ref
869
+ this.rootElRef = preact.createRef();
870
+ }
871
+ render() {
872
+ const { props } = this;
873
+ return (preact.createElement("div", { className: "fc-abs", style: props.style, ref: this.rootElRef }, props.children));
874
+ }
875
+ componentDidMount() {
876
+ const rootEl = this.rootElRef.current; // TODO: make dynamic with useEffect
877
+ this.detachHeight = internal$1.watchHeight(rootEl, (height) => {
878
+ internal$1.setRef(this.props.heightRef, height);
879
+ });
880
+ }
881
+ componentWillUnmount() {
882
+ this.detachHeight();
981
883
  }
982
884
  }
983
885
 
886
+ /*
887
+ TODO: split TimelineLaneBg and TimelineLaneFg?
888
+ */
984
889
  class TimelineLane extends internal$1.BaseComponent {
985
890
  constructor() {
986
891
  super(...arguments);
987
- this.slicer = new TimelineLaneSlicer();
892
+ // memo
988
893
  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: {},
894
+ // refs
895
+ this.segHeightRefMap = new internal$1.RefMap(() => {
896
+ internal$1.afterSize(this.handleSegHeights);
897
+ });
898
+ this.moreLinkHeightRefMap = new internal$1.RefMap(() => {
899
+ internal$1.afterSize(this.handleMoreLinkHeights);
900
+ });
901
+ // internal
902
+ this.slicer = new TimelineLaneSlicer();
903
+ this.handleMoreLinkHeights = () => {
904
+ this.setState({ moreLinkHeightRev: this.moreLinkHeightRefMap.rev }); // will trigger rerender
996
905
  };
997
- this.handleResize = (isForced) => {
998
- if (isForced) {
999
- this.updateSize();
1000
- }
906
+ this.handleSegHeights = () => {
907
+ this.setState({ segHeightRev: this.segHeightRefMap.rev }); // will trigger rerender
1001
908
  };
1002
909
  }
910
+ /*
911
+ TODO: lots of memoization needed here!
912
+ */
1003
913
  render() {
1004
- let { props, state, context } = this;
914
+ let { props, context, segHeightRefMap } = this;
1005
915
  let { options } = context;
1006
916
  let { dateProfile, tDateProfile } = props;
1007
917
  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 +920,275 @@ FullCalendar.Timeline = (function (exports, core, premiumCommonPlugin, internal$
1010
920
  (slicedProps.eventResize ? slicedProps.eventResize.segs : null) ||
1011
921
  [];
1012
922
  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
923
+ let fgSegHorizontals = props.slotWidth != null
924
+ ? computeManySegHorizontals(fgSegs, options.eventMinWidth, context.dateEnv, tDateProfile, props.slotWidth)
925
+ : {};
926
+ let [fgSegTops, fgSegsBottom, hiddenGroups, hiddenGroupTops] = computeFgSegPlacements(// verticals
927
+ fgSegs, fgSegHorizontals, segHeightRefMap.current, options.eventOrderStrict, options.eventMaxStack);
928
+ let moreLinksBottom = computeMoreLinkMaxBottom(hiddenGroups, hiddenGroupTops, this.moreLinkHeightRefMap.current);
929
+ let innerHeight = Math.max(moreLinksBottom, fgSegsBottom);
930
+ let forcedInvisibleMap = // TODO: more convenient/DRY
1016
931
  (slicedProps.eventDrag ? slicedProps.eventDrag.affectedInstances : null) ||
1017
932
  (slicedProps.eventResize ? slicedProps.eventResize.affectedInstances : null) ||
1018
933
  {};
1019
934
  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))));
935
+ preact.createElement(TimelineLaneBg, { tDateProfile: tDateProfile, nowDate: props.nowDate, todayRange: props.todayRange,
936
+ // content
937
+ bgEventSegs: slicedProps.bgEventSegs, businessHourSegs: slicedProps.businessHourSegs, dateSelectionSegs: slicedProps.dateSelectionSegs, eventResizeSegs: slicedProps.eventResize ? slicedProps.eventResize.segs : [] /* bad new empty array? */,
938
+ // dimensions
939
+ slotWidth: props.slotWidth }),
940
+ preact.createElement("div", { className: [
941
+ 'fc-timeline-events',
942
+ 'fc-content-box',
943
+ options.eventOverlap === false // TODO: fix bad default
944
+ ? 'fc-timeline-events-overlap-disabled'
945
+ : 'fc-timeline-events-overlap-enabled'
946
+ ].join(' '), style: { height: innerHeight } },
947
+ this.renderFgSegs(fgSegs, fgSegHorizontals, fgSegTops, forcedInvisibleMap, hiddenGroups, hiddenGroupTops, false, // isDragging
948
+ false, // isResizing
949
+ false),
950
+ this.renderFgSegs(mirrorSegs, props.slotWidth // TODO: memoize
951
+ ? computeManySegHorizontals(mirrorSegs, options.eventMinWidth, context.dateEnv, tDateProfile, props.slotWidth)
952
+ : {}, fgSegTops, {}, // forcedInvisibleMap
953
+ [], {}, Boolean(slicedProps.eventDrag), Boolean(slicedProps.eventResize), false))));
954
+ }
955
+ renderFgSegs(segs, segHorizontals, segTops, forcedInvisibleMap, hiddenGroups, hiddenGroupTops, isDragging, isResizing, isDateSelecting) {
956
+ let { props, context, segHeightRefMap, moreLinkHeightRefMap } = this;
957
+ let isMirror = isDragging || isResizing || isDateSelecting;
958
+ return (preact.createElement(preact.Fragment, null,
959
+ segs.map((seg) => {
960
+ const { instanceId } = seg.eventRange.instance;
961
+ const segTop = segTops[instanceId];
962
+ const segHorizontal = segHorizontals[instanceId];
963
+ const isVisible = isMirror ||
964
+ (segHorizontal && segTop != null && !forcedInvisibleMap[instanceId]);
965
+ 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) },
966
+ 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)))));
967
+ }),
968
+ hiddenGroups.map((hiddenGroup) => (preact.createElement(TimelineEventHarness, { key: hiddenGroup.key, style: Object.assign({ top: hiddenGroupTops[hiddenGroup.key] || 0 }, horizontalsToCss({
969
+ start: hiddenGroup.span.start,
970
+ size: hiddenGroup.span.end - hiddenGroup.span.start
971
+ }, context.isRtl)), heightRef: moreLinkHeightRefMap.createRef(hiddenGroup.key) },
972
+ 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 }))))));
973
+ }
974
+ }
975
+
976
+ class TimelineHeaderCell extends internal$1.BaseComponent {
977
+ constructor() {
978
+ super(...arguments);
979
+ // memo
980
+ this.refineRenderProps = internal$1.memoizeObjArg(refineRenderProps);
981
+ this.buildCellNavLinkAttrs = internal$1.memoize(buildCellNavLinkAttrs);
982
+ // ref
983
+ this.innerElRef = preact.createRef();
984
+ // TODO: unset width/height ref on unmount?
985
+ }
986
+ render() {
987
+ let { props, context } = this;
988
+ let { dateEnv, options } = context;
989
+ let { cell, dateProfile, tDateProfile } = props;
990
+ // the cell.rowUnit is f'd
991
+ // giving 'month' for a 3-day view
992
+ // workaround: to infer day, do NOT time
993
+ let dateMeta = internal$1.getDateMeta(cell.date, props.todayRange, props.nowDate, dateProfile);
994
+ let renderProps = this.refineRenderProps({
995
+ level: props.rowLevel,
996
+ dateMarker: cell.date,
997
+ text: cell.text,
998
+ dateEnv: context.dateEnv,
999
+ viewApi: context.viewApi,
1000
+ });
1001
+ return (preact.createElement(internal$1.ContentContainer, { elTag: "div", elClasses: [
1002
+ 'fc-timeline-slot-label',
1003
+ 'fc-timeline-slot',
1004
+ cell.isWeekStart ? 'fc-timeline-slot-em' : '',
1005
+ 'fc-header-cell',
1006
+ 'fc-cell',
1007
+ 'fc-flex-column',
1008
+ 'fc-justify-center',
1009
+ props.isCentered ? 'fc-align-center' : 'fc-align-start',
1010
+ ...( // TODO: so slot classnames for week/month/bigger. see note above about rowUnit
1011
+ cell.rowUnit === 'time' ?
1012
+ internal$1.getSlotClassNames(dateMeta, context.theme) :
1013
+ internal$1.getDayClassNames(dateMeta, context.theme)),
1014
+ ], elAttrs: {
1015
+ 'data-date': dateEnv.formatIso(cell.date, {
1016
+ omitTime: !tDateProfile.isTimeScale,
1017
+ omitTimeZoneOffset: true,
1018
+ }),
1019
+ }, elStyle: {
1020
+ width: props.slotWidth != null
1021
+ ? props.slotWidth * cell.colspan
1022
+ : undefined,
1023
+ }, 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: [
1024
+ 'fc-flex-column',
1025
+ props.isSticky ? 'fc-sticky-x' : '',
1026
+ ].join(' ') },
1027
+ preact.createElement(InnerContent, { elTag: "a", elClasses: [
1028
+ 'fc-cell-inner',
1029
+ 'fc-padding-md',
1030
+ ], elAttrs: this.buildCellNavLinkAttrs(context, cell.date, cell.rowUnit) })))));
1024
1031
  }
1025
1032
  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
- }
1033
+ const { props } = this;
1034
+ const innerEl = this.innerElRef.current; // TODO: make dynamic with useEffect
1035
+ this.detachSize = internal$1.watchSize(innerEl, (width, height) => {
1036
+ internal$1.setRef(props.innerWidthRef, width);
1037
+ internal$1.setRef(props.innerHeightRef, height);
1038
+ // HACK for sticky-centering
1039
+ innerEl.style.left = innerEl.style.right =
1040
+ (props.isCentered && props.isSticky)
1041
+ ? `calc(50% - ${width / 2}px)`
1042
+ : '';
1043
+ });
1036
1044
  }
1037
1045
  componentWillUnmount() {
1038
- this.context.removeResizeHandler(this.handleResize);
1039
- }
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
- }
1046
+ this.detachSize();
1061
1047
  }
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 }));
1048
+ }
1049
+ // Utils
1050
+ // -------------------------------------------------------------------------------------------------
1051
+ function buildCellNavLinkAttrs(context, cellDate, rowUnit) {
1052
+ return (rowUnit && rowUnit !== 'time')
1053
+ ? internal$1.buildNavLinkAttrs(context, cellDate, rowUnit)
1054
+ : {};
1055
+ }
1056
+ function renderInnerContent(renderProps) {
1057
+ return renderProps.text;
1058
+ }
1059
+ function refineRenderProps(input) {
1060
+ return {
1061
+ level: input.level,
1062
+ date: input.dateEnv.toDate(input.dateMarker),
1063
+ view: input.viewApi,
1064
+ text: input.text,
1065
+ };
1066
+ }
1067
+
1068
+ class TimelineHeaderRow extends internal$1.BaseComponent {
1069
+ constructor() {
1070
+ super(...arguments);
1071
+ // refs
1072
+ this.innerWidthRefMap = new internal$1.RefMap(() => {
1073
+ internal$1.afterSize(this.handleInnerWidths);
1074
+ });
1075
+ this.innerHeightRefMap = new internal$1.RefMap(() => {
1076
+ internal$1.afterSize(this.handleInnerHeights);
1077
+ });
1078
+ this.handleInnerWidths = () => {
1079
+ const innerWidthMap = this.innerWidthRefMap.current;
1080
+ let max = 0;
1081
+ for (const innerWidth of innerWidthMap.values()) {
1082
+ max = Math.max(max, innerWidth);
1083
+ }
1084
+ // TODO: ensure not equal?
1085
+ internal$1.setRef(this.props.innerWidthRef, max);
1086
+ };
1087
+ this.handleInnerHeights = () => {
1088
+ const innerHeightMap = this.innerHeightRefMap.current;
1089
+ let max = 0;
1090
+ for (const innerHeight of innerHeightMap.values()) {
1091
+ max = Math.max(max, innerHeight);
1070
1092
  }
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)))));
1093
+ // TODO: ensure not equal?
1094
+ internal$1.setRef(this.props.innerHeighRef, max);
1095
+ };
1096
+ }
1097
+ render() {
1098
+ const { props, innerWidthRefMap, innerHeightRefMap } = this;
1099
+ const isCentered = !(props.tDateProfile.isTimeScale && props.isLastRow);
1100
+ const isSticky = !props.isLastRow;
1101
+ return (preact.createElement("div", { className: 'fc-row', style: { height: props.height } }, props.cells.map((cell) => {
1102
+ // TODO: make this part of the cell obj?
1103
+ // TODO: rowUnit seems wrong sometimes. says 'month' when it should be day
1104
+ // TODO: rowUnit is relevant to whole row. put it on a row object, not the cells
1105
+ const key = cell.rowUnit + ':' + cell.date.toISOString();
1106
+ 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,
1107
+ // refs
1108
+ innerWidthRef: innerWidthRefMap.createRef(key), innerHeightRef: innerHeightRefMap.createRef(key),
1109
+ // dimensions
1110
+ slotWidth: props.slotWidth }));
1076
1111
  })));
1077
1112
  }
1078
1113
  }
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
- }));
1114
+
1115
+ class TimelineNowIndicatorLine extends internal$1.BaseComponent {
1116
+ render() {
1117
+ const { props, context } = this;
1118
+ return (preact.createElement("div", { className: "fc-timeline-now-indicator-container" },
1119
+ preact.createElement(internal$1.NowIndicatorContainer // TODO: make separate component?
1120
+ , { elClasses: ['fc-timeline-now-indicator-line'], elStyle: props.slotWidth != null
1121
+ ? horizontalCoordToCss(dateToCoord(props.nowDate, context.dateEnv, props.tDateProfile, props.slotWidth), context.isRtl)
1122
+ : {}, isAxis: false, date: props.nowDate })));
1123
+ }
1093
1124
  }
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
- }
1125
+
1126
+ class TimelineNowIndicatorArrow extends internal$1.BaseComponent {
1127
+ render() {
1128
+ const { props, context } = this;
1129
+ return (preact.createElement("div", { className: "fc-timeline-now-indicator-container" },
1130
+ preact.createElement(internal$1.NowIndicatorContainer, { elClasses: ['fc-timeline-now-indicator-arrow'], elStyle: props.slotWidth != null
1131
+ ? horizontalCoordToCss(dateToCoord(props.nowDate, context.dateEnv, props.tDateProfile, props.slotWidth), context.isRtl)
1132
+ : {}, isAxis: true, date: props.nowDate })));
1101
1133
  }
1102
- return topsByInstanceId;
1103
1134
  }
1104
1135
 
1105
- class TimelineGrid extends internal$1.DateComponent {
1136
+ class TimelineView extends internal$1.DateComponent {
1106
1137
  constructor() {
1107
1138
  super(...arguments);
1108
- this.slatsRef = preact.createRef();
1109
- this.state = {
1110
- coords: null,
1139
+ // memoized
1140
+ this.buildTimelineDateProfile = internal$1.memoize(buildTimelineDateProfile);
1141
+ this.computeSlotWidth = internal$1.memoize(computeSlotWidth);
1142
+ // refs
1143
+ this.headerScrollerRef = preact.createRef();
1144
+ this.bodyScrollerRef = preact.createRef();
1145
+ this.footerScrollerRef = preact.createRef();
1146
+ // Sizing
1147
+ // -----------------------------------------------------------------------------------------------
1148
+ this.handleHeaderSlotInnerWidth = (innerWidth) => {
1149
+ this.headerSlotInnerWidth = innerWidth;
1150
+ internal$1.afterSize(this.handleSlotInnerWidths);
1151
+ };
1152
+ this.handleBodySlotInnerWidth = (innerWidth) => {
1153
+ this.bodySlotInnerWidth = innerWidth;
1154
+ internal$1.afterSize(this.handleSlotInnerWidths);
1111
1155
  };
1112
- this.handeEl = (el) => {
1156
+ this.handleSlotInnerWidths = () => {
1157
+ const { state } = this;
1158
+ const slotInnerWidth = Math.max(this.headerSlotInnerWidth, this.bodySlotInnerWidth);
1159
+ if (state.slotInnerWidth !== slotInnerWidth) {
1160
+ this.setState({ slotInnerWidth });
1161
+ }
1162
+ };
1163
+ this.handleScrollerWidth = (scrollerWidth) => {
1164
+ this.setState({
1165
+ scrollerWidth,
1166
+ });
1167
+ };
1168
+ this.handleLeftScrollbarWidth = (leftScrollbarWidth) => {
1169
+ this.setState({
1170
+ leftScrollbarWidth
1171
+ });
1172
+ };
1173
+ this.handleRightScrollbarWidth = (rightScrollbarWidth) => {
1174
+ this.setState({
1175
+ rightScrollbarWidth
1176
+ });
1177
+ };
1178
+ this.timeScrollResponder = new internal$1.ScrollResponder((time) => {
1179
+ const { props, context, tDateProfile, slotWidth } = this;
1180
+ if (slotWidth != null) {
1181
+ const x = timeToCoord(time, context.dateEnv, props.dateProfile, tDateProfile, slotWidth) +
1182
+ (context.isRtl ? -1 : 1); // overcome border. TODO: DRY this up
1183
+ this.syncedScroller.scrollTo({ x });
1184
+ return true;
1185
+ }
1186
+ return false;
1187
+ });
1188
+ // Hit System
1189
+ // -----------------------------------------------------------------------------------------------
1190
+ this.handeBodyEl = (el) => {
1191
+ this.bodyEl = el;
1113
1192
  if (el) {
1114
1193
  this.context.registerInteractiveComponent(this, { el });
1115
1194
  }
@@ -1117,45 +1196,141 @@ FullCalendar.Timeline = (function (exports, core, premiumCommonPlugin, internal$
1117
1196
  this.context.unregisterInteractiveComponent(this);
1118
1197
  }
1119
1198
  };
1120
- this.handleCoords = (coords) => {
1121
- this.setState({ coords });
1122
- if (this.props.onSlatCoords) {
1123
- this.props.onSlatCoords(coords);
1124
- }
1125
- };
1126
1199
  }
1127
1200
  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
- // ------------------------------------------------------------------------------------------
1201
+ const { props, state, context } = this;
1202
+ const { options } = context;
1203
+ /* date */
1204
+ const tDateProfile = this.tDateProfile = this.buildTimelineDateProfile(props.dateProfile, context.dateEnv, options, context.dateProfileGenerator);
1205
+ const { cellRows } = tDateProfile;
1206
+ const timerUnit = internal$1.greatestDurationDenominator(tDateProfile.slotDuration).unit;
1207
+ /* table settings */
1208
+ const verticalScrolling = !props.forPrint && !internal$1.getIsHeightAuto(options);
1209
+ const stickyHeaderDates = !props.forPrint && internal$1.getStickyHeaderDates(options);
1210
+ const stickyFooterScrollbar = !props.forPrint && internal$1.getStickyFooterScrollbar(options);
1211
+ /* table positions */
1212
+ const [canvasWidth, slotWidth] = this.computeSlotWidth(tDateProfile.slotCnt, tDateProfile.slotsPerLabel, options.slotMinWidth, state.slotInnerWidth, // is ACTUALLY the label width. rename?
1213
+ state.scrollerWidth);
1214
+ this.slotWidth = slotWidth;
1215
+ return (preact.createElement(internal$1.NowTimer, { unit: timerUnit }, (nowDate, todayRange) => {
1216
+ const enableNowIndicator = // TODO: DRY
1217
+ options.nowIndicator &&
1218
+ slotWidth != null &&
1219
+ internal$1.rangeContainsMarker(props.dateProfile.currentRange, nowDate);
1220
+ return (preact.createElement(internal$1.ViewContainer, { viewSpec: context.viewSpec, elClasses: [
1221
+ 'fc-timeline-view',
1222
+ 'fc-flex-column',
1223
+ 'fc-border',
1224
+ ] },
1225
+ preact.createElement(internal$1.Scroller, { horizontal: true, hideScrollbars: true, elClassNames: [
1226
+ 'fc-timeline-header',
1227
+ 'fc-rowgroup',
1228
+ stickyHeaderDates ? 'fc-sticky-header' : '',
1229
+ ], ref: this.headerScrollerRef },
1230
+ preact.createElement("div", { className: 'fc-rel fc-content-box' // origin for now-indicator
1231
+ , style: {
1232
+ width: canvasWidth,
1233
+ paddingLeft: state.leftScrollbarWidth,
1234
+ paddingRight: state.rightScrollbarWidth,
1235
+ } },
1236
+ preact.createElement("div", null, cellRows.map((cells, rowLevel) => {
1237
+ const isLast = rowLevel === cellRows.length - 1;
1238
+ return (preact.createElement(TimelineHeaderRow, { key: rowLevel, dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: nowDate, todayRange: todayRange, rowLevel: rowLevel, isLastRow: isLast, cells: cells, slotWidth: slotWidth, innerWidthRef: isLast ? this.handleHeaderSlotInnerWidth : undefined }));
1239
+ })),
1240
+ enableNowIndicator && (
1241
+ // TODO: make this positioned WITHIN padding?
1242
+ preact.createElement(TimelineNowIndicatorArrow, { tDateProfile: tDateProfile, nowDate: nowDate, slotWidth: slotWidth })))),
1243
+ preact.createElement(internal$1.Scroller, { vertical: verticalScrolling, horizontal: true, elClassNames: [
1244
+ 'fc-timeline-body',
1245
+ 'fc-rowgroup',
1246
+ verticalScrolling ? 'fc-liquid' : '',
1247
+ ], ref: this.bodyScrollerRef, widthRef: this.handleScrollerWidth, leftScrollbarWidthRef: this.handleLeftScrollbarWidth, rightScrollbarWidthRef: this.handleRightScrollbarWidth },
1248
+ preact.createElement("div", { className: "fc-rel fc-grow", style: {
1249
+ width: canvasWidth,
1250
+ }, ref: this.handeBodyEl },
1251
+ preact.createElement(TimelineSlats, { dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: nowDate, todayRange: todayRange,
1252
+ // ref
1253
+ innerWidthRef: this.handleBodySlotInnerWidth,
1254
+ // dimensions
1255
+ slotWidth: slotWidth }),
1256
+ 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 }),
1257
+ enableNowIndicator && (preact.createElement(TimelineNowIndicatorLine, { tDateProfile: tDateProfile, nowDate: nowDate, slotWidth: slotWidth })))),
1258
+ stickyFooterScrollbar && (preact.createElement(internal$1.Scroller, { ref: this.footerScrollerRef, horizontal: true },
1259
+ preact.createElement("div", { style: { width: canvasWidth } })))));
1260
+ }));
1261
+ }
1262
+ // Lifecycle
1263
+ // -----------------------------------------------------------------------------------------------
1264
+ componentDidMount() {
1265
+ const { context } = this;
1266
+ const { options } = context;
1267
+ const ScrollerSyncer = internal$1.getScrollerSyncerClass(this.context.pluginHooks);
1268
+ this.syncedScroller = new ScrollerSyncer(true); // horizontal=true
1269
+ this.updateSyncedScroller();
1270
+ context.emitter.on('_timeScrollRequest', this.timeScrollResponder.handleScroll);
1271
+ this.timeScrollResponder.handleScroll(options.scrollTime);
1272
+ }
1273
+ componentDidUpdate(prevProps) {
1274
+ const { options } = this.context;
1275
+ this.updateSyncedScroller();
1276
+ if (prevProps.dateProfile !== this.props.dateProfile && options.scrollTimeReset) {
1277
+ this.timeScrollResponder.handleScroll(options.scrollTime);
1278
+ }
1279
+ else {
1280
+ this.timeScrollResponder.drain();
1281
+ }
1282
+ }
1283
+ componentWillUnmount() {
1284
+ this.syncedScroller.destroy();
1285
+ this.context.emitter.off('_timeScrollRequest', this.timeScrollResponder.handleScroll);
1286
+ }
1287
+ // Scrolling
1288
+ // -----------------------------------------------------------------------------------------------
1289
+ updateSyncedScroller() {
1290
+ this.syncedScroller.handleChildren([
1291
+ this.headerScrollerRef.current,
1292
+ this.bodyScrollerRef.current,
1293
+ this.footerScrollerRef.current
1294
+ ], this.context.isRtl);
1295
+ }
1145
1296
  queryHit(positionLeft, positionTop, elWidth, elHeight) {
1146
- let slats = this.slatsRef.current;
1147
- let slatHit = slats.positionToHit(positionLeft);
1148
- if (slatHit) {
1297
+ const { props, context, tDateProfile, slotWidth } = this;
1298
+ const { dateEnv } = context;
1299
+ if (slotWidth) {
1300
+ const x = context.isRtl ? elWidth - positionLeft : positionLeft;
1301
+ const slatIndex = Math.floor(x / slotWidth);
1302
+ const slatX = slatIndex * slotWidth;
1303
+ const partial = (x - slatX) / slotWidth; // floating point number between 0 and 1
1304
+ const localSnapIndex = Math.floor(partial * tDateProfile.snapsPerSlot); // the snap # relative to start of slat
1305
+ let startDate = dateEnv.add(tDateProfile.slotDates[slatIndex], internal$1.multiplyDuration(tDateProfile.snapDuration, localSnapIndex));
1306
+ let endDate = dateEnv.add(startDate, tDateProfile.snapDuration);
1307
+ // TODO: generalize this coord stuff to TimeGrid?
1308
+ let snapWidth = slotWidth / tDateProfile.snapsPerSlot;
1309
+ let startCoord = slatIndex * slotWidth + (snapWidth * localSnapIndex);
1310
+ let endCoord = startCoord + snapWidth;
1311
+ let left, right;
1312
+ if (context.isRtl) {
1313
+ left = elWidth - endCoord;
1314
+ right = elWidth - startCoord;
1315
+ }
1316
+ else {
1317
+ left = startCoord;
1318
+ right = endCoord;
1319
+ }
1149
1320
  return {
1150
- dateProfile: this.props.dateProfile,
1151
- dateSpan: slatHit.dateSpan,
1321
+ dateProfile: props.dateProfile,
1322
+ dateSpan: {
1323
+ range: { start: startDate, end: endDate },
1324
+ allDay: !tDateProfile.isTimeScale,
1325
+ },
1152
1326
  rect: {
1153
- left: slatHit.left,
1154
- right: slatHit.right,
1327
+ left,
1328
+ right,
1155
1329
  top: 0,
1156
1330
  bottom: elHeight,
1157
1331
  },
1158
- dayEl: slatHit.dayEl,
1332
+ // HACK. TODO: This is expensive to do every hit-query
1333
+ dayEl: this.bodyEl.querySelectorAll('.fc-timeline-slot')[slatIndex],
1159
1334
  layer: 0,
1160
1335
  };
1161
1336
  }
@@ -1163,94 +1338,12 @@ FullCalendar.Timeline = (function (exports, core, premiumCommonPlugin, internal$
1163
1338
  }
1164
1339
  }
1165
1340
 
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}";
1341
+ 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
1342
  internal$1.injectStyles(css_248z);
1250
1343
 
1251
1344
  var plugin = core.createPlugin({
1252
1345
  name: '@fullcalendar/timeline',
1253
- premiumReleaseDate: '2024-07-12',
1346
+ premiumReleaseDate: '2024-10-01',
1254
1347
  deps: [premiumCommonPlugin__default["default"]],
1255
1348
  initialView: 'timelineDay',
1256
1349
  views: {
@@ -1281,17 +1374,20 @@ FullCalendar.Timeline = (function (exports, core, premiumCommonPlugin, internal$
1281
1374
  var internal = {
1282
1375
  __proto__: null,
1283
1376
  TimelineView: TimelineView,
1284
- buildSlatCols: buildSlatCols,
1285
1377
  TimelineLane: TimelineLane,
1286
1378
  TimelineLaneBg: TimelineLaneBg,
1287
- TimelineHeader: TimelineHeader,
1288
1379
  TimelineSlats: TimelineSlats,
1289
1380
  buildTimelineDateProfile: buildTimelineDateProfile,
1290
- TimelineCoords: TimelineCoords,
1381
+ createVerticalStyle: createVerticalStyle,
1382
+ createHorizontalStyle: createHorizontalStyle,
1383
+ computeSlotWidth: computeSlotWidth,
1384
+ timeToCoord: timeToCoord,
1291
1385
  coordToCss: coordToCss,
1292
1386
  coordsToCss: coordsToCss,
1293
1387
  TimelineLaneSlicer: TimelineLaneSlicer,
1294
- TimelineHeaderRows: TimelineHeaderRows
1388
+ TimelineHeaderRow: TimelineHeaderRow,
1389
+ TimelineNowIndicatorArrow: TimelineNowIndicatorArrow,
1390
+ TimelineNowIndicatorLine: TimelineNowIndicatorLine
1295
1391
  };
1296
1392
 
1297
1393
  core.globalPlugins.push(plugin);
@@ -1303,4 +1399,4 @@ FullCalendar.Timeline = (function (exports, core, premiumCommonPlugin, internal$
1303
1399
 
1304
1400
  return exports;
1305
1401
 
1306
- })({}, FullCalendar, FullCalendar.PremiumCommon, FullCalendar.Internal, FullCalendar.Preact, FullCalendar.ScrollGrid.Internal);
1402
+ })({}, FullCalendar, FullCalendar.PremiumCommon, FullCalendar.Internal, FullCalendar.Preact);