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