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