@fullcalendar/timeline 7.0.0-beta.3 → 7.0.0-rc.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/LICENSE.md +2 -2
- package/index.cjs +1 -1
- package/index.global.js +371 -341
- package/index.global.min.js +2 -2
- package/index.js +1 -1
- package/internal.cjs +368 -338
- package/internal.d.ts +24 -21
- package/internal.js +367 -338
- package/package.json +4 -4
package/internal.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { config, createFormatter, greatestDurationDenominator, asCleanDays, createDuration, wholeDivideDurations, asRoughMs, addDays, startOfDay, asRoughSeconds, asRoughMinutes, diffWholeDays, isInt, computeVisibleDayRange, padStart, BaseComponent, getDateMeta, ContentContainer, joinClassNames, getDayClassName, getSlotClassName, watchWidth, setRef, RefMap, afterSize,
|
|
1
|
+
import { config, createFormatter, greatestDurationDenominator, asCleanDays, createDuration, wholeDivideDurations, asRoughMs, addDays, startOfDay, asRoughSeconds, asRoughMinutes, diffWholeDays, isInt, computeVisibleDayRange, padStart, BaseComponent, memoize, getDateMeta, ContentContainer, joinClassNames, getDayClassName, getSlotClassName, watchWidth, setRef, RefMap, afterSize, memoizeObjArg, buildNavLinkAttrs, watchSize, NowIndicatorContainer, Slicer, intersectRanges, addMs, StandardEvent, MoreLinkContainer, getEventRangeMeta, getEventKey, SegHierarchy, groupIntersectingSegs, watchHeight, sortEventSegs, buildEventRangeKey, BgEvent, renderFill, DateComponent, getIsHeightAuto, getStickyHeaderDates, getStickyFooterScrollbar, NowTimer, rangeContainsMarker, ViewContainer, Scroller, FooterScrollbar, Ruler, multiplyDuration, injectStyles } from '@fullcalendar/core/internal.js';
|
|
2
2
|
import { createRef, createElement, Fragment, Component } from '@fullcalendar/core/preact.js';
|
|
3
3
|
import { ScrollerSyncer } from '@fullcalendar/scrollgrid/internal.js';
|
|
4
4
|
|
|
@@ -443,6 +443,8 @@ function buildCellObject(date, text, rowUnit) {
|
|
|
443
443
|
class TimelineSlatCell extends BaseComponent {
|
|
444
444
|
constructor() {
|
|
445
445
|
super(...arguments);
|
|
446
|
+
// memo
|
|
447
|
+
this.getPublicDate = memoize((dateEnv, date) => dateEnv.toDate(date));
|
|
446
448
|
// ref
|
|
447
449
|
this.innerElRef = createRef();
|
|
448
450
|
}
|
|
@@ -451,7 +453,7 @@ class TimelineSlatCell extends BaseComponent {
|
|
|
451
453
|
let { dateEnv, options } = context;
|
|
452
454
|
let { date, tDateProfile, isEm } = props;
|
|
453
455
|
let dateMeta = getDateMeta(props.date, props.todayRange, props.nowDate, props.dateProfile);
|
|
454
|
-
let renderProps = Object.assign(Object.assign({ date:
|
|
456
|
+
let renderProps = Object.assign(Object.assign({ date: this.getPublicDate(dateEnv, props.date) }, dateMeta), { view: context.viewApi });
|
|
455
457
|
return (createElement(ContentContainer, { tag: "div",
|
|
456
458
|
// fc-align-start shrinks width of InnerContent
|
|
457
459
|
// TODO: document this semantic className fc-timeline-slot-em
|
|
@@ -460,14 +462,16 @@ class TimelineSlatCell extends BaseComponent {
|
|
|
460
462
|
'fc-timeline-slot-major' :
|
|
461
463
|
'fc-timeline-slot-minor'), 'fc-timeline-slot-lane fc-cell fc-flex-col fc-align-start', props.borderStart && 'fc-border-s', props.isDay ?
|
|
462
464
|
getDayClassName(dateMeta) :
|
|
463
|
-
getSlotClassName(dateMeta)), attrs: {
|
|
464
|
-
'data-date': dateEnv.formatIso(date, {
|
|
465
|
+
getSlotClassName(dateMeta)), attrs: Object.assign({ 'data-date': dateEnv.formatIso(date, {
|
|
465
466
|
omitTimeZoneOffset: true,
|
|
466
467
|
omitTime: !tDateProfile.isTimeScale,
|
|
467
|
-
}),
|
|
468
|
-
}, style: {
|
|
468
|
+
}) }, (dateMeta.isToday ? { 'aria-current': 'date' } : {})), style: {
|
|
469
469
|
width: props.width,
|
|
470
|
-
}, renderProps: renderProps, generatorName: "slotLaneContent", customGenerator: options.slotLaneContent, classNameGenerator: options.slotLaneClassNames, didMount: options.slotLaneDidMount, willUnmount: options.slotLaneWillUnmount }, (InnerContent) => (createElement(InnerContent, { tag: "div", className: 'fc-cell-inner',
|
|
470
|
+
}, renderProps: renderProps, generatorName: "slotLaneContent", customGenerator: options.slotLaneContent, classNameGenerator: options.slotLaneClassNames, didMount: options.slotLaneDidMount, willUnmount: options.slotLaneWillUnmount }, (InnerContent) => (createElement(InnerContent, { tag: "div", className: 'fc-cell-inner', style: {
|
|
471
|
+
// HACK for Safari 16.4,
|
|
472
|
+
// which can't use ResizeObserver on elements with natural width 0
|
|
473
|
+
minWidth: 1,
|
|
474
|
+
}, elRef: this.innerElRef }))));
|
|
471
475
|
}
|
|
472
476
|
componentDidMount() {
|
|
473
477
|
const innerEl = this.innerElRef.current;
|
|
@@ -502,7 +506,7 @@ class TimelineSlats extends BaseComponent {
|
|
|
502
506
|
let { tDateProfile, slotWidth } = props;
|
|
503
507
|
let { slotDates, isWeekStarts } = tDateProfile;
|
|
504
508
|
let isDay = !tDateProfile.isTimeScale && !tDateProfile.largeUnit;
|
|
505
|
-
return (createElement("div", { className: "fc-timeline-slots fc-
|
|
509
|
+
return (createElement("div", { "aria-hidden": true, className: "fc-timeline-slots fc-flex-row fc-fill", style: { height: props.height } }, slotDates.map((slotDate, i) => {
|
|
506
510
|
let key = slotDate.toISOString();
|
|
507
511
|
return (createElement(TimelineSlatCell, { key: key, date: slotDate, dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: props.nowDate, todayRange: props.todayRange, isEm: isWeekStarts[i], isDay: isDay, borderStart: Boolean(i),
|
|
508
512
|
// ref
|
|
@@ -513,50 +517,132 @@ class TimelineSlats extends BaseComponent {
|
|
|
513
517
|
}
|
|
514
518
|
}
|
|
515
519
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
//
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
return 0;
|
|
520
|
+
class TimelineHeaderCell extends BaseComponent {
|
|
521
|
+
constructor() {
|
|
522
|
+
super(...arguments);
|
|
523
|
+
// memo
|
|
524
|
+
this.refineRenderProps = memoizeObjArg(refineRenderProps);
|
|
525
|
+
// ref
|
|
526
|
+
this.innerElRef = createRef();
|
|
524
527
|
}
|
|
525
|
-
|
|
526
|
-
|
|
528
|
+
render() {
|
|
529
|
+
let { props, context } = this;
|
|
530
|
+
let { dateEnv, options } = context;
|
|
531
|
+
let { cell, dateProfile, tDateProfile } = props;
|
|
532
|
+
// the cell.rowUnit is f'd
|
|
533
|
+
// giving 'month' for a 3-day view
|
|
534
|
+
// workaround: to infer day, do NOT time
|
|
535
|
+
let dateMeta = getDateMeta(cell.date, props.todayRange, props.nowDate, dateProfile);
|
|
536
|
+
let renderProps = this.refineRenderProps({
|
|
537
|
+
level: props.rowLevel,
|
|
538
|
+
dateMarker: cell.date,
|
|
539
|
+
text: cell.text,
|
|
540
|
+
dateEnv: context.dateEnv,
|
|
541
|
+
viewApi: context.viewApi,
|
|
542
|
+
});
|
|
543
|
+
let isNavLink = !dateMeta.isDisabled && (cell.rowUnit && cell.rowUnit !== 'time');
|
|
544
|
+
return (createElement(ContentContainer, { tag: "div", className: joinClassNames('fc-timeline-slot-label fc-timeline-slot', cell.isWeekStart && 'fc-timeline-slot-em', // TODO: document this semantic className
|
|
545
|
+
'fc-header-cell fc-cell fc-flex-col fc-justify-center', props.borderStart && 'fc-border-s', props.isCentered ? 'fc-align-center' : 'fc-align-start',
|
|
546
|
+
// TODO: so slot classnames for week/month/bigger. see note above about rowUnit
|
|
547
|
+
cell.rowUnit === 'time' ?
|
|
548
|
+
getSlotClassName(dateMeta) :
|
|
549
|
+
getDayClassName(dateMeta)), attrs: Object.assign({ 'data-date': dateEnv.formatIso(cell.date, {
|
|
550
|
+
omitTime: !tDateProfile.isTimeScale,
|
|
551
|
+
omitTimeZoneOffset: true,
|
|
552
|
+
}) }, (dateMeta.isToday ? { 'aria-current': 'date' } : {})), style: {
|
|
553
|
+
width: props.slotWidth != null
|
|
554
|
+
? props.slotWidth * cell.colspan
|
|
555
|
+
: undefined,
|
|
556
|
+
}, renderProps: renderProps, generatorName: "slotLabelContent", customGenerator: options.slotLabelContent, defaultGenerator: renderInnerContent, classNameGenerator: options.slotLabelClassNames, didMount: options.slotLabelDidMount, willUnmount: options.slotLabelWillUnmount }, (InnerContent) => (createElement(InnerContent, { tag: 'div', attrs: isNavLink
|
|
557
|
+
// not tabbable because parent is aria-hidden
|
|
558
|
+
? buildNavLinkAttrs(context, cell.date, cell.rowUnit, undefined, /* isTabbable = */ false)
|
|
559
|
+
: {} // don't bother with aria-hidden because parent already hidden
|
|
560
|
+
, className: joinClassNames('fc-cell-inner fc-padding-md', props.isSticky && 'fc-sticky-s'), elRef: this.innerElRef }))));
|
|
527
561
|
}
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
562
|
+
componentDidMount() {
|
|
563
|
+
const { props } = this;
|
|
564
|
+
const innerEl = this.innerElRef.current; // TODO: make dynamic with useEffect
|
|
565
|
+
this.detachSize = watchSize(innerEl, (width, height) => {
|
|
566
|
+
setRef(props.innerWidthRef, width);
|
|
567
|
+
setRef(props.innerHeightRef, height);
|
|
568
|
+
// HACK for sticky-centering
|
|
569
|
+
innerEl.style.left = innerEl.style.right =
|
|
570
|
+
(props.isCentered && props.isSticky)
|
|
571
|
+
? `calc(50% - ${width / 2}px)`
|
|
572
|
+
: '';
|
|
573
|
+
});
|
|
532
574
|
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
575
|
+
componentWillUnmount() {
|
|
576
|
+
const { props } = this;
|
|
577
|
+
this.detachSize();
|
|
578
|
+
setRef(props.innerWidthRef, null);
|
|
579
|
+
setRef(props.innerHeightRef, null);
|
|
537
580
|
}
|
|
538
|
-
return snapCoverage;
|
|
539
581
|
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
if (!hcoord) {
|
|
545
|
-
return {};
|
|
546
|
-
}
|
|
547
|
-
if (isRtl) {
|
|
548
|
-
return { right: hcoord.start, width: hcoord.size };
|
|
549
|
-
}
|
|
550
|
-
else {
|
|
551
|
-
return { left: hcoord.start, width: hcoord.size };
|
|
552
|
-
}
|
|
582
|
+
// Utils
|
|
583
|
+
// -------------------------------------------------------------------------------------------------
|
|
584
|
+
function renderInnerContent(renderProps) {
|
|
585
|
+
return renderProps.text;
|
|
553
586
|
}
|
|
554
|
-
function
|
|
555
|
-
|
|
556
|
-
|
|
587
|
+
function refineRenderProps(input) {
|
|
588
|
+
return {
|
|
589
|
+
level: input.level,
|
|
590
|
+
date: input.dateEnv.toDate(input.dateMarker),
|
|
591
|
+
view: input.viewApi,
|
|
592
|
+
text: input.text,
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
class TimelineHeaderRow extends BaseComponent {
|
|
597
|
+
constructor() {
|
|
598
|
+
super(...arguments);
|
|
599
|
+
// refs
|
|
600
|
+
this.innerWidthRefMap = new RefMap(() => {
|
|
601
|
+
afterSize(this.handleInnerWidths);
|
|
602
|
+
});
|
|
603
|
+
this.innerHeightRefMap = new RefMap(() => {
|
|
604
|
+
afterSize(this.handleInnerHeights);
|
|
605
|
+
});
|
|
606
|
+
this.handleInnerWidths = () => {
|
|
607
|
+
const innerWidthMap = this.innerWidthRefMap.current;
|
|
608
|
+
let max = 0;
|
|
609
|
+
for (const innerWidth of innerWidthMap.values()) {
|
|
610
|
+
max = Math.max(max, innerWidth);
|
|
611
|
+
}
|
|
612
|
+
// TODO: ensure not equal?
|
|
613
|
+
setRef(this.props.innerWidthRef, max);
|
|
614
|
+
};
|
|
615
|
+
this.handleInnerHeights = () => {
|
|
616
|
+
const innerHeightMap = this.innerHeightRefMap.current;
|
|
617
|
+
let max = 0;
|
|
618
|
+
for (const innerHeight of innerHeightMap.values()) {
|
|
619
|
+
max = Math.max(max, innerHeight);
|
|
620
|
+
}
|
|
621
|
+
// TODO: ensure not equal?
|
|
622
|
+
setRef(this.props.innerHeighRef, max);
|
|
623
|
+
};
|
|
557
624
|
}
|
|
558
|
-
|
|
559
|
-
|
|
625
|
+
render() {
|
|
626
|
+
const { props, innerWidthRefMap, innerHeightRefMap } = this;
|
|
627
|
+
const isCentered = !(props.tDateProfile.isTimeScale && props.isLastRow);
|
|
628
|
+
const isSticky = !props.isLastRow;
|
|
629
|
+
return (createElement("div", { className: joinClassNames('fc-flex-row fc-grow', // TODO: move fc-grow to parent?
|
|
630
|
+
!props.isLastRow && 'fc-border-b') }, props.cells.map((cell, cellI) => {
|
|
631
|
+
// TODO: make this part of the cell obj?
|
|
632
|
+
// TODO: rowUnit seems wrong sometimes. says 'month' when it should be day
|
|
633
|
+
// TODO: rowUnit is relevant to whole row. put it on a row object, not the cells
|
|
634
|
+
// TODO: use rowUnit to key the Row itself?
|
|
635
|
+
const key = cell.rowUnit + ':' + cell.date.toISOString();
|
|
636
|
+
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, borderStart: Boolean(cellI),
|
|
637
|
+
// refs
|
|
638
|
+
innerWidthRef: innerWidthRefMap.createRef(key), innerHeightRef: innerHeightRefMap.createRef(key),
|
|
639
|
+
// dimensions
|
|
640
|
+
slotWidth: props.slotWidth }));
|
|
641
|
+
})));
|
|
642
|
+
}
|
|
643
|
+
componentWillUnmount() {
|
|
644
|
+
setRef(this.props.innerWidthRef, null);
|
|
645
|
+
setRef(this.props.innerHeighRef, null);
|
|
560
646
|
}
|
|
561
647
|
}
|
|
562
648
|
|
|
@@ -611,13 +697,38 @@ time, dateEnv, dateProfile, tDateProfile, slowWidth) {
|
|
|
611
697
|
}
|
|
612
698
|
function dateToCoord(// pixels
|
|
613
699
|
date, dateEnv, tDateProfile, slotWidth) {
|
|
614
|
-
let snapCoverage = computeDateSnapCoverage(date, tDateProfile, dateEnv);
|
|
700
|
+
let snapCoverage = computeDateSnapCoverage$1(date, tDateProfile, dateEnv);
|
|
615
701
|
let slotCoverage = snapCoverage / tDateProfile.snapsPerSlot;
|
|
616
702
|
return slotCoverage * slotWidth;
|
|
617
703
|
}
|
|
618
704
|
/*
|
|
619
705
|
returned value is between 0 and the number of snaps
|
|
620
706
|
*/
|
|
707
|
+
function computeDateSnapCoverage$1(date, tDateProfile, dateEnv) {
|
|
708
|
+
let snapDiff = dateEnv.countDurationsBetween(tDateProfile.normalizedRange.start, date, tDateProfile.snapDuration);
|
|
709
|
+
if (snapDiff < 0) {
|
|
710
|
+
return 0;
|
|
711
|
+
}
|
|
712
|
+
if (snapDiff >= tDateProfile.snapDiffToIndex.length) {
|
|
713
|
+
return tDateProfile.snapCnt;
|
|
714
|
+
}
|
|
715
|
+
let snapDiffInt = Math.floor(snapDiff);
|
|
716
|
+
let snapCoverage = tDateProfile.snapDiffToIndex[snapDiffInt];
|
|
717
|
+
if (isInt(snapCoverage)) { // not an in-between value
|
|
718
|
+
snapCoverage += snapDiff - snapDiffInt; // add the remainder
|
|
719
|
+
}
|
|
720
|
+
else {
|
|
721
|
+
// a fractional value, meaning the date is not visible
|
|
722
|
+
// always round up in this case. works for start AND end dates in a range.
|
|
723
|
+
snapCoverage = Math.ceil(snapCoverage);
|
|
724
|
+
}
|
|
725
|
+
return snapCoverage;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/*
|
|
729
|
+
TODO: rename this file!
|
|
730
|
+
*/
|
|
731
|
+
// returned value is between 0 and the number of snaps
|
|
621
732
|
function computeDateSnapCoverage(date, tDateProfile, dateEnv) {
|
|
622
733
|
let snapDiff = dateEnv.countDurationsBetween(tDateProfile.normalizedRange.start, date, tDateProfile.snapDuration);
|
|
623
734
|
if (snapDiff < 0) {
|
|
@@ -636,7 +747,107 @@ function computeDateSnapCoverage(date, tDateProfile, dateEnv) {
|
|
|
636
747
|
// always round up in this case. works for start AND end dates in a range.
|
|
637
748
|
snapCoverage = Math.ceil(snapCoverage);
|
|
638
749
|
}
|
|
639
|
-
return snapCoverage;
|
|
750
|
+
return snapCoverage;
|
|
751
|
+
}
|
|
752
|
+
/*
|
|
753
|
+
TODO: DRY up with elsewhere?
|
|
754
|
+
*/
|
|
755
|
+
function horizontalsToCss(hcoord, isRtl) {
|
|
756
|
+
if (!hcoord) {
|
|
757
|
+
return {};
|
|
758
|
+
}
|
|
759
|
+
if (isRtl) {
|
|
760
|
+
return { right: hcoord.start, width: hcoord.size };
|
|
761
|
+
}
|
|
762
|
+
else {
|
|
763
|
+
return { left: hcoord.start, width: hcoord.size };
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
function horizontalCoordToCss(start, isRtl) {
|
|
767
|
+
if (isRtl) {
|
|
768
|
+
return { right: start };
|
|
769
|
+
}
|
|
770
|
+
else {
|
|
771
|
+
return { left: start };
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
class TimelineNowIndicatorLine extends BaseComponent {
|
|
776
|
+
render() {
|
|
777
|
+
const { props, context } = this;
|
|
778
|
+
return (createElement("div", { className: "fc-timeline-now-indicator-container" },
|
|
779
|
+
createElement(NowIndicatorContainer // TODO: make separate component?
|
|
780
|
+
, { className: 'fc-timeline-now-indicator-line', style: props.slotWidth != null
|
|
781
|
+
? horizontalCoordToCss(dateToCoord(props.nowDate, context.dateEnv, props.tDateProfile, props.slotWidth), context.isRtl)
|
|
782
|
+
: {}, isAxis: false, date: props.nowDate })));
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
class TimelineNowIndicatorArrow extends BaseComponent {
|
|
787
|
+
render() {
|
|
788
|
+
const { props, context } = this;
|
|
789
|
+
return (createElement("div", { className: "fc-timeline-now-indicator-container" },
|
|
790
|
+
createElement(NowIndicatorContainer, { className: 'fc-timeline-now-indicator-arrow', style: props.slotWidth != null
|
|
791
|
+
? horizontalCoordToCss(dateToCoord(props.nowDate, context.dateEnv, props.tDateProfile, props.slotWidth), context.isRtl)
|
|
792
|
+
: {}, isAxis: true, date: props.nowDate })));
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
function getTimelineSlotEl(parentEl, index) {
|
|
797
|
+
return parentEl.querySelectorAll('.fc-timeline-slot')[index];
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
class TimelineLaneSlicer extends Slicer {
|
|
801
|
+
sliceRange(origRange, dateProfile, dateProfileGenerator, tDateProfile, dateEnv) {
|
|
802
|
+
let normalRange = normalizeRange(origRange, tDateProfile, dateEnv);
|
|
803
|
+
let segs = [];
|
|
804
|
+
// protect against when the span is entirely in an invalid date region
|
|
805
|
+
if (computeDateSnapCoverage(normalRange.start, tDateProfile, dateEnv)
|
|
806
|
+
< computeDateSnapCoverage(normalRange.end, tDateProfile, dateEnv)) {
|
|
807
|
+
// intersect the footprint's range with the grid's range
|
|
808
|
+
let slicedRange = intersectRanges(normalRange, tDateProfile.normalizedRange);
|
|
809
|
+
if (slicedRange) {
|
|
810
|
+
segs.push({
|
|
811
|
+
startDate: slicedRange.start,
|
|
812
|
+
endDate: slicedRange.end,
|
|
813
|
+
isStart: slicedRange.start.valueOf() === normalRange.start.valueOf()
|
|
814
|
+
&& isValidDate(slicedRange.start, tDateProfile, dateProfile, dateProfileGenerator),
|
|
815
|
+
isEnd: slicedRange.end.valueOf() === normalRange.end.valueOf()
|
|
816
|
+
&& isValidDate(addMs(slicedRange.end, -1), tDateProfile, dateProfile, dateProfileGenerator),
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
return segs;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
const DEFAULT_TIME_FORMAT = createFormatter({
|
|
825
|
+
hour: 'numeric',
|
|
826
|
+
minute: '2-digit',
|
|
827
|
+
omitZeroMinute: true,
|
|
828
|
+
meridiem: 'narrow',
|
|
829
|
+
});
|
|
830
|
+
class TimelineEvent extends BaseComponent {
|
|
831
|
+
render() {
|
|
832
|
+
let { props, context } = this;
|
|
833
|
+
let { options } = context;
|
|
834
|
+
return (createElement(StandardEvent, Object.assign({}, props, { className: joinClassNames('fc-timeline-event', options.eventOverlap === false // TODO: fix bad default
|
|
835
|
+
&& 'fc-timeline-event-spacious', 'fc-h-event'), defaultTimeFormat: DEFAULT_TIME_FORMAT, defaultDisplayEventTime: !props.isTimeScale })));
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
class TimelineLaneMoreLink extends BaseComponent {
|
|
840
|
+
render() {
|
|
841
|
+
let { props } = this;
|
|
842
|
+
let { hiddenSegs, resourceId, forcedInvisibleMap } = props;
|
|
843
|
+
let dateSpanProps = resourceId ? { resourceId } : {};
|
|
844
|
+
return (createElement(MoreLinkContainer, { className: 'fc-timeline-more-link', allDayDate: null, segs: hiddenSegs, hiddenSegs: hiddenSegs, dateProfile: props.dateProfile, todayRange: props.todayRange, dateSpanProps: dateSpanProps, popoverContent: () => (createElement(Fragment, null, hiddenSegs.map((seg) => {
|
|
845
|
+
let { eventRange } = seg;
|
|
846
|
+
let instanceId = eventRange.instance.instanceId;
|
|
847
|
+
return (createElement("div", { key: instanceId, style: { visibility: forcedInvisibleMap[instanceId] ? 'hidden' : '' } },
|
|
848
|
+
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)))));
|
|
849
|
+
}))) }, (InnerContent) => (createElement(InnerContent, { tag: "div", className: 'fc-timeline-more-link-inner fc-sticky-s' }))));
|
|
850
|
+
}
|
|
640
851
|
}
|
|
641
852
|
|
|
642
853
|
function computeManySegHorizontals(segs, segMinWidth, dateEnv, tDateProfile, slotWidth) {
|
|
@@ -703,83 +914,6 @@ hiddenGroupHeights, strictOrder, maxDepth) {
|
|
|
703
914
|
];
|
|
704
915
|
}
|
|
705
916
|
|
|
706
|
-
class TimelineLaneBg extends BaseComponent {
|
|
707
|
-
render() {
|
|
708
|
-
let { props } = this;
|
|
709
|
-
let highlightSeg = [].concat(props.eventResizeSegs, props.dateSelectionSegs);
|
|
710
|
-
return (createElement(Fragment, null,
|
|
711
|
-
this.renderSegs(props.businessHourSegs || [], 'non-business'),
|
|
712
|
-
this.renderSegs(props.bgEventSegs || [], 'bg-event'),
|
|
713
|
-
this.renderSegs(highlightSeg, 'highlight')));
|
|
714
|
-
}
|
|
715
|
-
renderSegs(segs, fillType) {
|
|
716
|
-
let { tDateProfile, todayRange, nowDate, slotWidth } = this.props;
|
|
717
|
-
let { dateEnv, isRtl } = this.context;
|
|
718
|
-
return (createElement(Fragment, null, segs.map((seg) => {
|
|
719
|
-
let hStyle; // TODO
|
|
720
|
-
if (slotWidth != null) {
|
|
721
|
-
let segHorizontal = computeSegHorizontals(seg, undefined, dateEnv, tDateProfile, slotWidth);
|
|
722
|
-
hStyle = horizontalsToCss(segHorizontal, isRtl);
|
|
723
|
-
}
|
|
724
|
-
return (createElement("div", { key: buildEventRangeKey(seg.eventRange), className: "fc-fill-y", style: hStyle }, fillType === 'bg-event' ?
|
|
725
|
-
createElement(BgEvent, Object.assign({ eventRange: seg.eventRange, isStart: seg.isStart, isEnd: seg.isEnd }, getEventRangeMeta(seg.eventRange, todayRange, nowDate))) : (renderFill(fillType))));
|
|
726
|
-
})));
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
class TimelineLaneSlicer extends Slicer {
|
|
731
|
-
sliceRange(origRange, dateProfile, dateProfileGenerator, tDateProfile, dateEnv) {
|
|
732
|
-
let normalRange = normalizeRange(origRange, tDateProfile, dateEnv);
|
|
733
|
-
let segs = [];
|
|
734
|
-
// protect against when the span is entirely in an invalid date region
|
|
735
|
-
if (computeDateSnapCoverage$1(normalRange.start, tDateProfile, dateEnv)
|
|
736
|
-
< computeDateSnapCoverage$1(normalRange.end, tDateProfile, dateEnv)) {
|
|
737
|
-
// intersect the footprint's range with the grid's range
|
|
738
|
-
let slicedRange = intersectRanges(normalRange, tDateProfile.normalizedRange);
|
|
739
|
-
if (slicedRange) {
|
|
740
|
-
segs.push({
|
|
741
|
-
startDate: slicedRange.start,
|
|
742
|
-
endDate: slicedRange.end,
|
|
743
|
-
isStart: slicedRange.start.valueOf() === normalRange.start.valueOf()
|
|
744
|
-
&& isValidDate(slicedRange.start, tDateProfile, dateProfile, dateProfileGenerator),
|
|
745
|
-
isEnd: slicedRange.end.valueOf() === normalRange.end.valueOf()
|
|
746
|
-
&& isValidDate(addMs(slicedRange.end, -1), tDateProfile, dateProfile, dateProfileGenerator),
|
|
747
|
-
});
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
return segs;
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
const DEFAULT_TIME_FORMAT = createFormatter({
|
|
755
|
-
hour: 'numeric',
|
|
756
|
-
minute: '2-digit',
|
|
757
|
-
omitZeroMinute: true,
|
|
758
|
-
meridiem: 'narrow',
|
|
759
|
-
});
|
|
760
|
-
class TimelineEvent extends BaseComponent {
|
|
761
|
-
render() {
|
|
762
|
-
let { props, context } = this;
|
|
763
|
-
let { options } = context;
|
|
764
|
-
return (createElement(StandardEvent, Object.assign({}, props, { className: joinClassNames('fc-timeline-event', options.eventOverlap === false // TODO: fix bad default
|
|
765
|
-
&& 'fc-timeline-event-spacious', 'fc-h-event'), defaultTimeFormat: DEFAULT_TIME_FORMAT, defaultDisplayEventTime: !props.isTimeScale })));
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
class TimelineLaneMoreLink extends BaseComponent {
|
|
770
|
-
render() {
|
|
771
|
-
let { props } = this;
|
|
772
|
-
let { hiddenSegs, resourceId, forcedInvisibleMap } = props;
|
|
773
|
-
let dateSpanProps = resourceId ? { resourceId } : {};
|
|
774
|
-
return (createElement(MoreLinkContainer, { className: 'fc-timeline-more-link', allDayDate: null, segs: hiddenSegs, hiddenSegs: hiddenSegs, dateProfile: props.dateProfile, todayRange: props.todayRange, dateSpanProps: dateSpanProps, popoverContent: () => (createElement(Fragment, null, hiddenSegs.map((seg) => {
|
|
775
|
-
let { eventRange } = seg;
|
|
776
|
-
let instanceId = eventRange.instance.instanceId;
|
|
777
|
-
return (createElement("div", { key: instanceId, style: { visibility: forcedInvisibleMap[instanceId] ? 'hidden' : '' } },
|
|
778
|
-
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)))));
|
|
779
|
-
}))) }, (InnerContent) => (createElement(InnerContent, { tag: "div", className: 'fc-timeline-more-link-inner fc-sticky-s' }))));
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
|
|
783
917
|
/*
|
|
784
918
|
TODO: make DRY with other Event Harnesses
|
|
785
919
|
*/
|
|
@@ -805,10 +939,7 @@ class TimelineEventHarness extends Component {
|
|
|
805
939
|
}
|
|
806
940
|
}
|
|
807
941
|
|
|
808
|
-
|
|
809
|
-
TODO: split TimelineLaneBg and TimelineLaneFg?
|
|
810
|
-
*/
|
|
811
|
-
class TimelineLane extends BaseComponent {
|
|
942
|
+
class TimelineFg extends BaseComponent {
|
|
812
943
|
constructor() {
|
|
813
944
|
super(...arguments);
|
|
814
945
|
// memo
|
|
@@ -820,8 +951,6 @@ class TimelineLane extends BaseComponent {
|
|
|
820
951
|
this.moreLinkHeightRefMap = new RefMap(() => {
|
|
821
952
|
afterSize(this.handleMoreLinkHeights);
|
|
822
953
|
});
|
|
823
|
-
// internal
|
|
824
|
-
this.slicer = new TimelineLaneSlicer();
|
|
825
954
|
this.handleMoreLinkHeights = () => {
|
|
826
955
|
this.setState({ moreLinkHeightRev: this.moreLinkHeightRefMap.rev }); // will trigger rerender
|
|
827
956
|
};
|
|
@@ -835,39 +964,30 @@ class TimelineLane extends BaseComponent {
|
|
|
835
964
|
render() {
|
|
836
965
|
let { props, context, segHeightRefMap, moreLinkHeightRefMap } = this;
|
|
837
966
|
let { options } = context;
|
|
838
|
-
let {
|
|
839
|
-
let
|
|
840
|
-
|
|
841
|
-
let mirrorSegs = (slicedProps.eventDrag ? slicedProps.eventDrag.segs : null) ||
|
|
842
|
-
(slicedProps.eventResize ? slicedProps.eventResize.segs : null) ||
|
|
967
|
+
let { tDateProfile } = props;
|
|
968
|
+
let mirrorSegs = (props.eventDrag ? props.eventDrag.segs : null) ||
|
|
969
|
+
(props.eventResize ? props.eventResize.segs : null) ||
|
|
843
970
|
[];
|
|
844
|
-
let fgSegs = this.sortEventSegs(
|
|
971
|
+
let fgSegs = this.sortEventSegs(props.fgEventSegs, options.eventOrder);
|
|
845
972
|
let fgSegHorizontals = props.slotWidth != null
|
|
846
973
|
? computeManySegHorizontals(fgSegs, options.eventMinWidth, context.dateEnv, tDateProfile, props.slotWidth)
|
|
847
974
|
: {};
|
|
848
975
|
let [fgSegTops, hiddenGroups, hiddenGroupTops, totalHeight] = computeFgSegPlacements(fgSegs, fgSegHorizontals, segHeightRefMap.current, moreLinkHeightRefMap.current, options.eventOrderStrict, options.eventMaxStack);
|
|
976
|
+
this.totalHeight = totalHeight;
|
|
849
977
|
let forcedInvisibleMap = // TODO: more convenient/DRY
|
|
850
|
-
(
|
|
851
|
-
(
|
|
978
|
+
(props.eventDrag ? props.eventDrag.affectedInstances : null) ||
|
|
979
|
+
(props.eventResize ? props.eventResize.affectedInstances : null) ||
|
|
852
980
|
{};
|
|
853
|
-
return (createElement(
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
false, // isResizing
|
|
864
|
-
false),
|
|
865
|
-
this.renderFgSegs(mirrorSegs, props.slotWidth // TODO: memoize
|
|
866
|
-
? computeManySegHorizontals(mirrorSegs, options.eventMinWidth, context.dateEnv, tDateProfile, props.slotWidth)
|
|
867
|
-
: {}, fgSegTops, {}, // forcedInvisibleMap
|
|
868
|
-
[], // hiddenGroups
|
|
869
|
-
new Map(), // hiddenGroupTops
|
|
870
|
-
Boolean(slicedProps.eventDrag), Boolean(slicedProps.eventResize), false))));
|
|
981
|
+
return (createElement("div", { className: 'fc-timeline-events fc-rel', style: { height: totalHeight } },
|
|
982
|
+
this.renderFgSegs(fgSegs, fgSegHorizontals, fgSegTops, forcedInvisibleMap, hiddenGroups, hiddenGroupTops, false, // isDragging
|
|
983
|
+
false, // isResizing
|
|
984
|
+
false),
|
|
985
|
+
this.renderFgSegs(mirrorSegs, props.slotWidth // TODO: memoize
|
|
986
|
+
? computeManySegHorizontals(mirrorSegs, options.eventMinWidth, context.dateEnv, tDateProfile, props.slotWidth)
|
|
987
|
+
: {}, fgSegTops, {}, // forcedInvisibleMap
|
|
988
|
+
[], // hiddenGroups
|
|
989
|
+
new Map(), // hiddenGroupTops
|
|
990
|
+
Boolean(props.eventDrag), Boolean(props.eventResize), false)));
|
|
871
991
|
}
|
|
872
992
|
renderFgSegs(segs, segHorizontals, segTops, forcedInvisibleMap, hiddenGroups, hiddenGroupTops, isDragging, isResizing, isDateSelecting) {
|
|
873
993
|
let { props, context, segHeightRefMap, moreLinkHeightRefMap } = this;
|
|
@@ -889,158 +1009,43 @@ class TimelineLane extends BaseComponent {
|
|
|
889
1009
|
}, context.isRtl)), heightRef: moreLinkHeightRefMap.createRef(hiddenGroup.key) },
|
|
890
1010
|
createElement(TimelineLaneMoreLink, { hiddenSegs: hiddenGroup.segs, dateProfile: props.dateProfile, nowDate: props.nowDate, todayRange: props.todayRange, isTimeScale: props.tDateProfile.isTimeScale, eventSelection: props.eventSelection, resourceId: props.resourceId, forcedInvisibleMap: forcedInvisibleMap }))))));
|
|
891
1011
|
}
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
constructor() {
|
|
896
|
-
super(...arguments);
|
|
897
|
-
// memo
|
|
898
|
-
this.refineRenderProps = memoizeObjArg(refineRenderProps);
|
|
899
|
-
this.buildCellNavLinkAttrs = memoize(buildCellNavLinkAttrs);
|
|
900
|
-
// ref
|
|
901
|
-
this.innerElRef = createRef();
|
|
902
|
-
}
|
|
903
|
-
render() {
|
|
904
|
-
let { props, context } = this;
|
|
905
|
-
let { dateEnv, options } = context;
|
|
906
|
-
let { cell, dateProfile, tDateProfile } = props;
|
|
907
|
-
// the cell.rowUnit is f'd
|
|
908
|
-
// giving 'month' for a 3-day view
|
|
909
|
-
// workaround: to infer day, do NOT time
|
|
910
|
-
let dateMeta = getDateMeta(cell.date, props.todayRange, props.nowDate, dateProfile);
|
|
911
|
-
let renderProps = this.refineRenderProps({
|
|
912
|
-
level: props.rowLevel,
|
|
913
|
-
dateMarker: cell.date,
|
|
914
|
-
text: cell.text,
|
|
915
|
-
dateEnv: context.dateEnv,
|
|
916
|
-
viewApi: context.viewApi,
|
|
917
|
-
});
|
|
918
|
-
return (createElement(ContentContainer, { tag: "div", className: joinClassNames('fc-timeline-slot-label fc-timeline-slot', cell.isWeekStart && 'fc-timeline-slot-em', // TODO: document this semantic className
|
|
919
|
-
'fc-header-cell fc-cell fc-flex-col fc-justify-center', props.borderStart && 'fc-border-s', props.isCentered ? 'fc-align-center' : 'fc-align-start',
|
|
920
|
-
// TODO: so slot classnames for week/month/bigger. see note above about rowUnit
|
|
921
|
-
cell.rowUnit === 'time' ?
|
|
922
|
-
getSlotClassName(dateMeta) :
|
|
923
|
-
getDayClassName(dateMeta)), attrs: {
|
|
924
|
-
'data-date': dateEnv.formatIso(cell.date, {
|
|
925
|
-
omitTime: !tDateProfile.isTimeScale,
|
|
926
|
-
omitTimeZoneOffset: true,
|
|
927
|
-
}),
|
|
928
|
-
}, style: {
|
|
929
|
-
width: props.slotWidth != null
|
|
930
|
-
? props.slotWidth * cell.colspan
|
|
931
|
-
: undefined,
|
|
932
|
-
}, renderProps: renderProps, generatorName: "slotLabelContent", customGenerator: options.slotLabelContent, defaultGenerator: renderInnerContent, classNameGenerator: options.slotLabelClassNames, didMount: options.slotLabelDidMount, willUnmount: options.slotLabelWillUnmount }, (InnerContent) => (createElement(InnerContent, { tag: "a", attrs: this.buildCellNavLinkAttrs(context, cell.date, cell.rowUnit), className: joinClassNames('fc-cell-inner fc-padding-md', props.isSticky && 'fc-sticky-s'), elRef: this.innerElRef }))));
|
|
933
|
-
}
|
|
934
|
-
componentDidMount() {
|
|
935
|
-
const { props } = this;
|
|
936
|
-
const innerEl = this.innerElRef.current; // TODO: make dynamic with useEffect
|
|
937
|
-
this.detachSize = watchSize(innerEl, (width, height) => {
|
|
938
|
-
setRef(props.innerWidthRef, width);
|
|
939
|
-
setRef(props.innerHeightRef, height);
|
|
940
|
-
// HACK for sticky-centering
|
|
941
|
-
innerEl.style.left = innerEl.style.right =
|
|
942
|
-
(props.isCentered && props.isSticky)
|
|
943
|
-
? `calc(50% - ${width / 2}px)`
|
|
944
|
-
: '';
|
|
945
|
-
});
|
|
946
|
-
}
|
|
947
|
-
componentWillUnmount() {
|
|
948
|
-
const { props } = this;
|
|
949
|
-
this.detachSize();
|
|
950
|
-
setRef(props.innerWidthRef, null);
|
|
951
|
-
setRef(props.innerHeightRef, null);
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
// Utils
|
|
955
|
-
// -------------------------------------------------------------------------------------------------
|
|
956
|
-
function buildCellNavLinkAttrs(context, cellDate, rowUnit) {
|
|
957
|
-
return (rowUnit && rowUnit !== 'time')
|
|
958
|
-
? buildNavLinkAttrs(context, cellDate, rowUnit)
|
|
959
|
-
: {};
|
|
960
|
-
}
|
|
961
|
-
function renderInnerContent(renderProps) {
|
|
962
|
-
return renderProps.text;
|
|
963
|
-
}
|
|
964
|
-
function refineRenderProps(input) {
|
|
965
|
-
return {
|
|
966
|
-
level: input.level,
|
|
967
|
-
date: input.dateEnv.toDate(input.dateMarker),
|
|
968
|
-
view: input.viewApi,
|
|
969
|
-
text: input.text,
|
|
970
|
-
};
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
class TimelineHeaderRow extends BaseComponent {
|
|
974
|
-
constructor() {
|
|
975
|
-
super(...arguments);
|
|
976
|
-
// refs
|
|
977
|
-
this.innerWidthRefMap = new RefMap(() => {
|
|
978
|
-
afterSize(this.handleInnerWidths);
|
|
979
|
-
});
|
|
980
|
-
this.innerHeightRefMap = new RefMap(() => {
|
|
981
|
-
afterSize(this.handleInnerHeights);
|
|
982
|
-
});
|
|
983
|
-
this.handleInnerWidths = () => {
|
|
984
|
-
const innerWidthMap = this.innerWidthRefMap.current;
|
|
985
|
-
let max = 0;
|
|
986
|
-
for (const innerWidth of innerWidthMap.values()) {
|
|
987
|
-
max = Math.max(max, innerWidth);
|
|
988
|
-
}
|
|
989
|
-
// TODO: ensure not equal?
|
|
990
|
-
setRef(this.props.innerWidthRef, max);
|
|
991
|
-
};
|
|
992
|
-
this.handleInnerHeights = () => {
|
|
993
|
-
const innerHeightMap = this.innerHeightRefMap.current;
|
|
994
|
-
let max = 0;
|
|
995
|
-
for (const innerHeight of innerHeightMap.values()) {
|
|
996
|
-
max = Math.max(max, innerHeight);
|
|
997
|
-
}
|
|
998
|
-
// TODO: ensure not equal?
|
|
999
|
-
setRef(this.props.innerHeighRef, max);
|
|
1000
|
-
};
|
|
1012
|
+
/*
|
|
1013
|
+
componentDidMount(): void {
|
|
1014
|
+
// might want to do firedTotalHeight, but won't be ready on first render
|
|
1001
1015
|
}
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
// TODO: make this part of the cell obj?
|
|
1009
|
-
// TODO: rowUnit seems wrong sometimes. says 'month' when it should be day
|
|
1010
|
-
// TODO: rowUnit is relevant to whole row. put it on a row object, not the cells
|
|
1011
|
-
// TODO: use rowUnit to key the Row itself?
|
|
1012
|
-
const key = cell.rowUnit + ':' + cell.date.toISOString();
|
|
1013
|
-
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, borderStart: Boolean(cellI),
|
|
1014
|
-
// refs
|
|
1015
|
-
innerWidthRef: innerWidthRefMap.createRef(key), innerHeightRef: innerHeightRefMap.createRef(key),
|
|
1016
|
-
// dimensions
|
|
1017
|
-
slotWidth: props.slotWidth }));
|
|
1018
|
-
})));
|
|
1016
|
+
*/
|
|
1017
|
+
componentDidUpdate() {
|
|
1018
|
+
if (this.totalHeight !== this.firedTotalHeight) {
|
|
1019
|
+
this.firedTotalHeight = this.totalHeight;
|
|
1020
|
+
setRef(this.props.heightRef, this.totalHeight);
|
|
1021
|
+
}
|
|
1019
1022
|
}
|
|
1020
1023
|
componentWillUnmount() {
|
|
1021
|
-
setRef(this.props.
|
|
1022
|
-
setRef(this.props.innerHeighRef, null);
|
|
1024
|
+
setRef(this.props.heightRef, null);
|
|
1023
1025
|
}
|
|
1024
1026
|
}
|
|
1025
1027
|
|
|
1026
|
-
class
|
|
1028
|
+
class TimelineBg extends BaseComponent {
|
|
1027
1029
|
render() {
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1030
|
+
let { props } = this;
|
|
1031
|
+
let highlightSeg = [].concat(props.eventResizeSegs || [], props.dateSelectionSegs);
|
|
1032
|
+
return (createElement(Fragment, null,
|
|
1033
|
+
this.renderSegs(props.businessHourSegs || [], 'non-business'),
|
|
1034
|
+
this.renderSegs(props.bgEventSegs || [], 'bg-event'),
|
|
1035
|
+
this.renderSegs(highlightSeg, 'highlight')));
|
|
1034
1036
|
}
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1037
|
+
renderSegs(segs, fillType) {
|
|
1038
|
+
let { tDateProfile, todayRange, nowDate, slotWidth } = this.props;
|
|
1039
|
+
let { dateEnv, isRtl } = this.context;
|
|
1040
|
+
return (createElement(Fragment, null, segs.map((seg) => {
|
|
1041
|
+
let hStyle; // TODO
|
|
1042
|
+
if (slotWidth != null) {
|
|
1043
|
+
let segHorizontal = computeSegHorizontals(seg, undefined, dateEnv, tDateProfile, slotWidth);
|
|
1044
|
+
hStyle = horizontalsToCss(segHorizontal, isRtl);
|
|
1045
|
+
}
|
|
1046
|
+
return (createElement("div", { key: buildEventRangeKey(seg.eventRange), className: "fc-fill-y", style: hStyle }, fillType === 'bg-event' ?
|
|
1047
|
+
createElement(BgEvent, Object.assign({ eventRange: seg.eventRange, isStart: seg.isStart, isEnd: seg.isEnd }, getEventRangeMeta(seg.eventRange, todayRange, nowDate))) : (renderFill(fillType))));
|
|
1048
|
+
})));
|
|
1044
1049
|
}
|
|
1045
1050
|
}
|
|
1046
1051
|
|
|
@@ -1058,6 +1063,7 @@ class TimelineView extends DateComponent {
|
|
|
1058
1063
|
afterSize(this.handleSlotInnerWidths);
|
|
1059
1064
|
});
|
|
1060
1065
|
this.scrollTime = null;
|
|
1066
|
+
this.slicer = new TimelineLaneSlicer();
|
|
1061
1067
|
// Sizing
|
|
1062
1068
|
// -----------------------------------------------------------------------------------------------
|
|
1063
1069
|
this.handleBodySlotInnerWidth = (innerWidth) => {
|
|
@@ -1065,39 +1071,34 @@ class TimelineView extends DateComponent {
|
|
|
1065
1071
|
afterSize(this.handleSlotInnerWidths);
|
|
1066
1072
|
};
|
|
1067
1073
|
this.handleSlotInnerWidths = () => {
|
|
1068
|
-
const
|
|
1069
|
-
const
|
|
1070
|
-
if (
|
|
1071
|
-
|
|
1074
|
+
const headerSlotInnerWidth = this.headerRowInnerWidthMap.current.get(this.tDateProfile.cellRows.length - 1);
|
|
1075
|
+
const { bodySlotInnerWidth } = this;
|
|
1076
|
+
if (headerSlotInnerWidth != null && bodySlotInnerWidth != null) {
|
|
1077
|
+
const slotInnerWidth = Math.max(headerSlotInnerWidth, bodySlotInnerWidth);
|
|
1078
|
+
if (slotInnerWidth !== this.state.slotInnerWidth) {
|
|
1079
|
+
this.setState({ slotInnerWidth });
|
|
1080
|
+
}
|
|
1072
1081
|
}
|
|
1073
1082
|
};
|
|
1074
|
-
this.
|
|
1083
|
+
this.handleTotalWidth = (totalWidth) => {
|
|
1075
1084
|
this.setState({
|
|
1076
|
-
|
|
1085
|
+
totalWidth,
|
|
1077
1086
|
});
|
|
1078
1087
|
};
|
|
1079
|
-
this.
|
|
1088
|
+
this.handleClientWidth = (clientWidth) => {
|
|
1080
1089
|
this.setState({
|
|
1081
|
-
|
|
1090
|
+
clientWidth,
|
|
1082
1091
|
});
|
|
1083
1092
|
};
|
|
1084
|
-
this.
|
|
1093
|
+
this.handleTimeScrollRequest = (scrollTime) => {
|
|
1085
1094
|
this.scrollTime = scrollTime;
|
|
1086
|
-
this.
|
|
1095
|
+
this.applyTimeScroll();
|
|
1087
1096
|
};
|
|
1088
|
-
this.
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
let x = timeToCoord(scrollTime, context.dateEnv, props.dateProfile, tDateProfile, slotWidth);
|
|
1092
|
-
if (x) {
|
|
1093
|
-
x += 1; // overcome border. TODO: DRY this up
|
|
1094
|
-
}
|
|
1095
|
-
this.syncedScroller.scrollTo({ x });
|
|
1097
|
+
this.handleTimeScrollEnd = (isUser) => {
|
|
1098
|
+
if (isUser) {
|
|
1099
|
+
this.scrollTime = null;
|
|
1096
1100
|
}
|
|
1097
1101
|
};
|
|
1098
|
-
this.clearScroll = () => {
|
|
1099
|
-
this.scrollTime = null;
|
|
1100
|
-
};
|
|
1101
1102
|
// Hit System
|
|
1102
1103
|
// -----------------------------------------------------------------------------------------------
|
|
1103
1104
|
this.handeBodyEl = (el) => {
|
|
@@ -1113,6 +1114,10 @@ class TimelineView extends DateComponent {
|
|
|
1113
1114
|
render() {
|
|
1114
1115
|
const { props, state, context } = this;
|
|
1115
1116
|
const { options } = context;
|
|
1117
|
+
const { totalWidth, clientWidth } = state;
|
|
1118
|
+
const endScrollbarWidth = (totalWidth != null && clientWidth != null)
|
|
1119
|
+
? totalWidth - clientWidth
|
|
1120
|
+
: undefined;
|
|
1116
1121
|
/* date */
|
|
1117
1122
|
const tDateProfile = this.tDateProfile = this.buildTimelineDateProfile(props.dateProfile, context.dateEnv, options, context.dateProfileGenerator);
|
|
1118
1123
|
const { cellRows } = tDateProfile;
|
|
@@ -1123,8 +1128,11 @@ class TimelineView extends DateComponent {
|
|
|
1123
1128
|
const stickyFooterScrollbar = !props.forPrint && getStickyFooterScrollbar(options);
|
|
1124
1129
|
/* table positions */
|
|
1125
1130
|
const [canvasWidth, slotWidth] = this.computeSlotWidth(tDateProfile.slotCnt, tDateProfile.slotsPerLabel, options.slotMinWidth, state.slotInnerWidth, // is ACTUALLY the label width. rename?
|
|
1126
|
-
|
|
1131
|
+
clientWidth);
|
|
1127
1132
|
this.slotWidth = slotWidth;
|
|
1133
|
+
/* sliced */
|
|
1134
|
+
let slicedProps = this.slicer.sliceProps(props, props.dateProfile, tDateProfile.isTimeScale ? null : options.nextDayThreshold, context, // wish we didn't have to pass in the rest of the args...
|
|
1135
|
+
props.dateProfile, context.dateProfileGenerator, tDateProfile, context.dateEnv);
|
|
1128
1136
|
return (createElement(NowTimer, { unit: timerUnit }, (nowDate, todayRange) => {
|
|
1129
1137
|
const enableNowIndicator = // TODO: DRY
|
|
1130
1138
|
options.nowIndicator &&
|
|
@@ -1144,18 +1152,30 @@ class TimelineView extends DateComponent {
|
|
|
1144
1152
|
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) }));
|
|
1145
1153
|
}),
|
|
1146
1154
|
enableNowIndicator && (createElement(TimelineNowIndicatorArrow, { tDateProfile: tDateProfile, nowDate: nowDate, slotWidth: slotWidth }))),
|
|
1147
|
-
Boolean(
|
|
1148
|
-
createElement(Scroller, { vertical: verticalScrolling, horizontal: true, hideScrollbars:
|
|
1149
|
-
|
|
1155
|
+
Boolean(endScrollbarWidth) && (createElement("div", { className: 'fc-border-s fc-filler', style: { minWidth: endScrollbarWidth } }))),
|
|
1156
|
+
createElement(Scroller, { vertical: verticalScrolling, horizontal: true, hideScrollbars: stickyFooterScrollbar ||
|
|
1157
|
+
props.forPrint // prevents blank space in print-view on Safari
|
|
1158
|
+
, className: joinClassNames('fc-timeline-body fc-flex-col', verticalScrolling && 'fc-liquid'), ref: this.bodyScrollerRef, clientWidthRef: this.handleClientWidth },
|
|
1159
|
+
createElement("div", { "aria-label": options.eventsHint, className: "fc-rel fc-grow", style: { width: canvasWidth }, ref: this.handeBodyEl },
|
|
1150
1160
|
createElement(TimelineSlats, { dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: nowDate, todayRange: todayRange,
|
|
1151
1161
|
// ref
|
|
1152
1162
|
innerWidthRef: this.handleBodySlotInnerWidth,
|
|
1153
1163
|
// dimensions
|
|
1154
1164
|
slotWidth: slotWidth }),
|
|
1155
|
-
createElement(
|
|
1165
|
+
createElement(TimelineBg, { tDateProfile: tDateProfile, nowDate: nowDate, todayRange: todayRange,
|
|
1166
|
+
// content
|
|
1167
|
+
bgEventSegs: slicedProps.bgEventSegs, businessHourSegs: slicedProps.businessHourSegs, dateSelectionSegs: slicedProps.dateSelectionSegs, eventResizeSegs: slicedProps.eventResize ? slicedProps.eventResize.segs : null,
|
|
1168
|
+
// dimensions
|
|
1169
|
+
slotWidth: slotWidth }),
|
|
1170
|
+
createElement(TimelineFg, { dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: nowDate, todayRange: todayRange,
|
|
1171
|
+
// content
|
|
1172
|
+
fgEventSegs: slicedProps.fgEventSegs, eventDrag: slicedProps.eventDrag, eventResize: slicedProps.eventResize, eventSelection: slicedProps.eventSelection,
|
|
1173
|
+
// dimensions
|
|
1174
|
+
slotWidth: slotWidth }),
|
|
1175
|
+
createElement("div", { className: 'fc-timeline-lane-footer' }),
|
|
1156
1176
|
enableNowIndicator && (createElement(TimelineNowIndicatorLine, { tDateProfile: tDateProfile, nowDate: nowDate, slotWidth: slotWidth })))),
|
|
1157
|
-
stickyFooterScrollbar && (createElement(
|
|
1158
|
-
|
|
1177
|
+
Boolean(stickyFooterScrollbar) && (createElement(FooterScrollbar, { isSticky: true, canvasWidth: canvasWidth, scrollerRef: this.footerScrollerRef })),
|
|
1178
|
+
createElement(Ruler, { widthRef: this.handleTotalWidth })));
|
|
1159
1179
|
}));
|
|
1160
1180
|
}
|
|
1161
1181
|
// Lifecycle
|
|
@@ -1164,8 +1184,8 @@ class TimelineView extends DateComponent {
|
|
|
1164
1184
|
this.syncedScroller = new ScrollerSyncer(true); // horizontal=true
|
|
1165
1185
|
this.updateSyncedScroller();
|
|
1166
1186
|
this.resetScroll();
|
|
1167
|
-
this.context.emitter.on('_timeScrollRequest', this.
|
|
1168
|
-
this.syncedScroller.addScrollEndListener(this.
|
|
1187
|
+
this.context.emitter.on('_timeScrollRequest', this.handleTimeScrollRequest);
|
|
1188
|
+
this.syncedScroller.addScrollEndListener(this.handleTimeScrollEnd);
|
|
1169
1189
|
}
|
|
1170
1190
|
componentDidUpdate(prevProps) {
|
|
1171
1191
|
this.updateSyncedScroller();
|
|
@@ -1174,13 +1194,13 @@ class TimelineView extends DateComponent {
|
|
|
1174
1194
|
}
|
|
1175
1195
|
else {
|
|
1176
1196
|
// TODO: inefficient to update so often
|
|
1177
|
-
this.
|
|
1197
|
+
this.applyTimeScroll();
|
|
1178
1198
|
}
|
|
1179
1199
|
}
|
|
1180
1200
|
componentWillUnmount() {
|
|
1181
1201
|
this.syncedScroller.destroy();
|
|
1182
|
-
this.context.emitter.off('_timeScrollRequest', this.
|
|
1183
|
-
this.syncedScroller.removeScrollEndListener(this.
|
|
1202
|
+
this.context.emitter.off('_timeScrollRequest', this.handleTimeScrollRequest);
|
|
1203
|
+
this.syncedScroller.removeScrollEndListener(this.handleTimeScrollEnd);
|
|
1184
1204
|
}
|
|
1185
1205
|
// Scrolling
|
|
1186
1206
|
// -----------------------------------------------------------------------------------------------
|
|
@@ -1192,7 +1212,17 @@ class TimelineView extends DateComponent {
|
|
|
1192
1212
|
]);
|
|
1193
1213
|
}
|
|
1194
1214
|
resetScroll() {
|
|
1195
|
-
this.
|
|
1215
|
+
this.handleTimeScrollRequest(this.context.options.scrollTime);
|
|
1216
|
+
}
|
|
1217
|
+
applyTimeScroll() {
|
|
1218
|
+
const { props, context, tDateProfile, scrollTime, slotWidth } = this;
|
|
1219
|
+
if (scrollTime != null && slotWidth != null) {
|
|
1220
|
+
let x = timeToCoord(scrollTime, context.dateEnv, props.dateProfile, tDateProfile, slotWidth);
|
|
1221
|
+
if (x) {
|
|
1222
|
+
x += 1; // overcome border. TODO: DRY this up
|
|
1223
|
+
}
|
|
1224
|
+
this.syncedScroller.scrollTo({ x });
|
|
1225
|
+
}
|
|
1196
1226
|
}
|
|
1197
1227
|
queryHit(positionLeft, positionTop, elWidth, elHeight) {
|
|
1198
1228
|
const { props, context, tDateProfile, slotWidth } = this;
|
|
@@ -1230,8 +1260,7 @@ class TimelineView extends DateComponent {
|
|
|
1230
1260
|
top: 0,
|
|
1231
1261
|
bottom: elHeight,
|
|
1232
1262
|
},
|
|
1233
|
-
|
|
1234
|
-
dayEl: this.bodyEl.querySelectorAll('.fc-timeline-slot')[slatIndex],
|
|
1263
|
+
getDayEl: () => getTimelineSlotEl(this.bodyEl, slatIndex),
|
|
1235
1264
|
layer: 0,
|
|
1236
1265
|
};
|
|
1237
1266
|
}
|
|
@@ -1239,7 +1268,7 @@ class TimelineView extends DateComponent {
|
|
|
1239
1268
|
}
|
|
1240
1269
|
}
|
|
1241
1270
|
|
|
1242
|
-
var css_248z = ".fc-timeline-
|
|
1271
|
+
var css_248z = ".fc-timeline-lane-footer{padding-bottom:10px}.fc-timeline-overlap-disabled .fc-timeline-lane-footer{padding-bottom:0}.fc-timeline-slot-minor{border-style:dotted}.fc-timeline-event{border-radius:0;font-size:var(--fc-small-font-size);margin-bottom:1px}.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 .fc-event-inner{align-items:center;display:flex;flex-direction:row;padding:2px 1px}.fc-timeline-event-spacious .fc-event-inner{padding-bottom:5px;padding-top:5px}.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-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-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-now-indicator-container{bottom:0;left:0;overflow:hidden;pointer-events:none;position:absolute;right:0;top:0;z-index:2}.fc-timeline-now-indicator-arrow{border-bottom-style:solid;border-bottom-width:0;border-color:var(--fc-now-indicator-color);border-left:5px solid transparent;border-right:5px solid transparent;border-top-style:solid;border-top-width:6px;height:0;margin:0 -5px;position:absolute;top:0;width:0}.fc-timeline-now-indicator-line{border-left:1px solid var(--fc-now-indicator-color);bottom:0;position:absolute;top:0}";
|
|
1243
1272
|
injectStyles(css_248z);
|
|
1244
1273
|
|
|
1245
|
-
export {
|
|
1274
|
+
export { TimelineBg, TimelineFg, TimelineHeaderRow, TimelineLaneSlicer, TimelineNowIndicatorArrow, TimelineNowIndicatorLine, TimelineSlats, TimelineView, buildTimelineDateProfile, computeSlotWidth, createHorizontalStyle, createVerticalStyle, getTimelineSlotEl, timeToCoord };
|