@fullcalendar/timeline 7.0.0-beta.3 → 7.0.0-beta.5
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 → cjs/index.cjs} +14 -7
- package/cjs/internal.cjs +1224 -0
- package/esm/index.d.ts +25 -0
- package/{index.js → esm/index.js} +13 -6
- package/{internal.d.ts → esm/internal.d.ts} +33 -48
- package/{internal.js → esm/internal.js} +455 -495
- package/{index.global.js → global.js} +464 -500
- package/global.min.js +6 -0
- package/package.json +24 -21
- package/index.d.ts +0 -8
- package/index.global.min.js +0 -6
- package/internal.cjs +0 -1261
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
1
|
+
import { joinClassNames } from '@fullcalendar/core';
|
|
2
|
+
import { config, createFormatter, greatestDurationDenominator, createDuration, wholeDivideDurations, asRoughMs, addDays, computeMajorUnit, isMajorUnit, startOfDay, asRoughSeconds, asRoughMinutes, diffWholeDays, asCleanDays, isInt, computeVisibleDayRange, padStart, BaseComponent, memoize, getDateMeta, ContentContainer, joinArrayishClassNames, buildNavLinkAttrs, generateClassName, watchSize, setRef, RefMap, afterSize, NowIndicatorLineContainer, NowIndicatorDot, NowIndicatorHeaderContainer, 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 } from '@fullcalendar/core/internal';
|
|
3
|
+
import classNames from '@fullcalendar/core/internal-classnames';
|
|
4
|
+
import { createElement, createRef, Fragment, Component } from '@fullcalendar/core/preact';
|
|
5
|
+
import { ScrollerSyncer } from '@fullcalendar/scrollgrid/internal';
|
|
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
|
|
@@ -29,13 +31,13 @@ const STOCK_SUB_DURATIONS = [
|
|
|
29
31
|
];
|
|
30
32
|
function buildTimelineDateProfile(dateProfile, dateEnv, allOptions, dateProfileGenerator) {
|
|
31
33
|
let tDateProfile = {
|
|
32
|
-
labelInterval: allOptions.
|
|
34
|
+
labelInterval: allOptions.slotHeaderInterval,
|
|
33
35
|
slotDuration: allOptions.slotDuration,
|
|
34
36
|
};
|
|
35
37
|
validateLabelAndSlot(tDateProfile, dateProfile, dateEnv); // validate after computed grid duration
|
|
36
38
|
ensureLabelInterval(tDateProfile, dateProfile, dateEnv);
|
|
37
39
|
ensureSlotDuration(tDateProfile, dateProfile, dateEnv);
|
|
38
|
-
let input = allOptions.
|
|
40
|
+
let input = allOptions.slotHeaderFormat;
|
|
39
41
|
let rawFormats = Array.isArray(input) ? input :
|
|
40
42
|
(input != null) ? [input] :
|
|
41
43
|
computeHeaderFormats(tDateProfile, dateProfile, dateEnv, allOptions);
|
|
@@ -49,10 +51,6 @@ function buildTimelineDateProfile(dateProfile, dateEnv, allOptions, dateProfileG
|
|
|
49
51
|
}
|
|
50
52
|
}
|
|
51
53
|
tDateProfile.largeUnit = largeUnit;
|
|
52
|
-
tDateProfile.emphasizeWeeks =
|
|
53
|
-
asCleanDays(tDateProfile.slotDuration) === 1 &&
|
|
54
|
-
currentRangeAs('weeks', dateProfile, dateEnv) >= 2 &&
|
|
55
|
-
!allOptions.businessHours;
|
|
56
54
|
/*
|
|
57
55
|
console.log('label interval =', timelineView.labelInterval.humanize())
|
|
58
56
|
console.log('slot duration =', timelineView.slotDuration.humanize())
|
|
@@ -88,14 +86,18 @@ function buildTimelineDateProfile(dateProfile, dateEnv, allOptions, dateProfileG
|
|
|
88
86
|
tDateProfile.timeWindowMs = timeWindowMs;
|
|
89
87
|
tDateProfile.normalizedRange = { start: normalizedStart, end: normalizedEnd };
|
|
90
88
|
let slotDates = [];
|
|
89
|
+
let slotDatesMajor = [];
|
|
91
90
|
let date = normalizedStart;
|
|
91
|
+
let majorUnit = computeMajorUnit(dateProfile, dateEnv);
|
|
92
92
|
while (date < normalizedEnd) {
|
|
93
93
|
if (isValidDate(date, tDateProfile, dateProfile, dateProfileGenerator)) {
|
|
94
94
|
slotDates.push(date);
|
|
95
|
+
slotDatesMajor.push(isMajorUnit(date, majorUnit, dateEnv));
|
|
95
96
|
}
|
|
96
97
|
date = dateEnv.add(date, tDateProfile.slotDuration);
|
|
97
98
|
}
|
|
98
99
|
tDateProfile.slotDates = slotDates;
|
|
100
|
+
tDateProfile.slotDatesMajor = slotDatesMajor;
|
|
99
101
|
// more...
|
|
100
102
|
let snapIndex = -1;
|
|
101
103
|
let snapDiff = 0; // index of the diff :(
|
|
@@ -119,8 +121,7 @@ function buildTimelineDateProfile(dateProfile, dateEnv, allOptions, dateProfileG
|
|
|
119
121
|
tDateProfile.snapCnt = snapIndex + 1; // is always one behind
|
|
120
122
|
tDateProfile.slotCnt = tDateProfile.snapCnt / tDateProfile.snapsPerSlot;
|
|
121
123
|
// more...
|
|
122
|
-
tDateProfile.
|
|
123
|
-
tDateProfile.cellRows = buildCellRows(tDateProfile, dateEnv);
|
|
124
|
+
tDateProfile.cellRows = buildCellRows(tDateProfile, dateEnv, majorUnit);
|
|
124
125
|
tDateProfile.slotsPerLabel = wholeDivideDurations(tDateProfile.labelInterval, tDateProfile.slotDuration);
|
|
125
126
|
return tDateProfile;
|
|
126
127
|
}
|
|
@@ -181,7 +182,7 @@ function validateLabelAndSlot(tDateProfile, dateProfile, dateEnv) {
|
|
|
181
182
|
if (tDateProfile.labelInterval) {
|
|
182
183
|
const labelCnt = dateEnv.countDurationsBetween(currentRange.start, currentRange.end, tDateProfile.labelInterval);
|
|
183
184
|
if (labelCnt > config.MAX_TIMELINE_SLOTS) {
|
|
184
|
-
console.warn('
|
|
185
|
+
console.warn('slotHeaderInterval results in too many cells');
|
|
185
186
|
tDateProfile.labelInterval = null;
|
|
186
187
|
}
|
|
187
188
|
}
|
|
@@ -197,7 +198,7 @@ function validateLabelAndSlot(tDateProfile, dateProfile, dateEnv) {
|
|
|
197
198
|
if (tDateProfile.labelInterval && tDateProfile.slotDuration) {
|
|
198
199
|
const slotsPerLabel = wholeDivideDurations(tDateProfile.labelInterval, tDateProfile.slotDuration);
|
|
199
200
|
if (slotsPerLabel === null || slotsPerLabel < 1) {
|
|
200
|
-
console.warn('
|
|
201
|
+
console.warn('slotHeaderInterval must be a multiple of slotDuration');
|
|
201
202
|
tDateProfile.slotDuration = null;
|
|
202
203
|
}
|
|
203
204
|
}
|
|
@@ -272,6 +273,7 @@ function computeHeaderFormats(tDateProfile, dateProfile, dateEnv, allOptions) {
|
|
|
272
273
|
let format1;
|
|
273
274
|
let format2;
|
|
274
275
|
const { labelInterval } = tDateProfile;
|
|
276
|
+
const { currentRange } = dateProfile;
|
|
275
277
|
let unit = greatestDurationDenominator(labelInterval).unit;
|
|
276
278
|
const weekNumbersVisible = allOptions.weekNumbers;
|
|
277
279
|
let format0 = (format1 = (format2 = null));
|
|
@@ -284,22 +286,22 @@ function computeHeaderFormats(tDateProfile, dateProfile, dateEnv, allOptions) {
|
|
|
284
286
|
format0 = { year: 'numeric' }; // '2015'
|
|
285
287
|
break;
|
|
286
288
|
case 'month':
|
|
287
|
-
if (
|
|
289
|
+
if (dateEnv.diffWholeYears(currentRange.start, currentRange.end) > 1) {
|
|
288
290
|
format0 = { year: 'numeric' }; // '2015'
|
|
289
291
|
}
|
|
290
292
|
format1 = { month: 'short' }; // 'Jan'
|
|
291
293
|
break;
|
|
292
294
|
case 'week':
|
|
293
|
-
if (
|
|
295
|
+
if (dateEnv.diffWholeYears(currentRange.start, currentRange.end) > 1) {
|
|
294
296
|
format0 = { year: 'numeric' }; // '2015'
|
|
295
297
|
}
|
|
296
298
|
format1 = { week: 'narrow' }; // 'Wk4'
|
|
297
299
|
break;
|
|
298
300
|
case 'day':
|
|
299
|
-
if (
|
|
301
|
+
if (dateEnv.diffWholeYears(currentRange.start, currentRange.end) > 1) {
|
|
300
302
|
format0 = { year: 'numeric', month: 'long' }; // 'January 2014'
|
|
301
303
|
}
|
|
302
|
-
else if (
|
|
304
|
+
else if (dateEnv.diffWholeMonths(currentRange.start, currentRange.end) > 1) {
|
|
303
305
|
format0 = { month: 'long' }; // 'January'
|
|
304
306
|
}
|
|
305
307
|
if (weekNumbersVisible) {
|
|
@@ -311,7 +313,7 @@ function computeHeaderFormats(tDateProfile, dateProfile, dateEnv, allOptions) {
|
|
|
311
313
|
if (weekNumbersVisible) {
|
|
312
314
|
format0 = { week: 'short' }; // 'Wk 4'
|
|
313
315
|
}
|
|
314
|
-
if (
|
|
316
|
+
if (diffWholeDays(currentRange.start, currentRange.end) > 1) {
|
|
315
317
|
format1 = { weekday: 'short', day: 'numeric', month: 'numeric', omitCommas: true }; // Sat 4/7
|
|
316
318
|
}
|
|
317
319
|
format2 = {
|
|
@@ -357,39 +359,7 @@ function computeHeaderFormats(tDateProfile, dateProfile, dateEnv, allOptions) {
|
|
|
357
359
|
}
|
|
358
360
|
return [].concat(format0 || [], format1 || [], format2 || []);
|
|
359
361
|
}
|
|
360
|
-
|
|
361
|
-
// Won't go more precise than days.
|
|
362
|
-
// Will return `0` if there's not a clean whole interval.
|
|
363
|
-
function currentRangeAs(unit, dateProfile, dateEnv) {
|
|
364
|
-
let range = dateProfile.currentRange;
|
|
365
|
-
let res = null;
|
|
366
|
-
if (unit === 'years') {
|
|
367
|
-
res = dateEnv.diffWholeYears(range.start, range.end);
|
|
368
|
-
}
|
|
369
|
-
else if (unit === 'months') {
|
|
370
|
-
res = dateEnv.diffWholeMonths(range.start, range.end);
|
|
371
|
-
}
|
|
372
|
-
else if (unit === 'weeks') {
|
|
373
|
-
res = dateEnv.diffWholeMonths(range.start, range.end);
|
|
374
|
-
}
|
|
375
|
-
else if (unit === 'days') {
|
|
376
|
-
res = diffWholeDays(range.start, range.end);
|
|
377
|
-
}
|
|
378
|
-
return res || 0;
|
|
379
|
-
}
|
|
380
|
-
function buildIsWeekStarts(tDateProfile, dateEnv) {
|
|
381
|
-
let { slotDates, emphasizeWeeks } = tDateProfile;
|
|
382
|
-
let prevWeekNumber = null;
|
|
383
|
-
let isWeekStarts = [];
|
|
384
|
-
for (let slotDate of slotDates) {
|
|
385
|
-
let weekNumber = dateEnv.computeWeekNumber(slotDate);
|
|
386
|
-
let isWeekStart = emphasizeWeeks && (prevWeekNumber !== null) && (prevWeekNumber !== weekNumber);
|
|
387
|
-
prevWeekNumber = weekNumber;
|
|
388
|
-
isWeekStarts.push(isWeekStart);
|
|
389
|
-
}
|
|
390
|
-
return isWeekStarts;
|
|
391
|
-
}
|
|
392
|
-
function buildCellRows(tDateProfile, dateEnv) {
|
|
362
|
+
function buildCellRows(tDateProfile, dateEnv, majorUnit) {
|
|
393
363
|
let slotDates = tDateProfile.slotDates;
|
|
394
364
|
let formats = tDateProfile.headerFormats;
|
|
395
365
|
let cellRows = formats.map(() => []); // indexed by row,col
|
|
@@ -398,23 +368,23 @@ function buildCellRows(tDateProfile, dateEnv) {
|
|
|
398
368
|
slotAsDays === 1 ? 'day' :
|
|
399
369
|
null;
|
|
400
370
|
// specifically for navclicks
|
|
401
|
-
let rowUnitsFromFormats = formats.map((format) => (format.
|
|
371
|
+
let rowUnitsFromFormats = formats.map((format) => (format.getSmallestUnit ? format.getSmallestUnit() : null));
|
|
402
372
|
// builds cellRows and slotCells
|
|
403
373
|
for (let i = 0; i < slotDates.length; i += 1) {
|
|
404
374
|
let date = slotDates[i];
|
|
405
|
-
let isWeekStart = tDateProfile.isWeekStarts[i];
|
|
406
375
|
for (let row = 0; row < formats.length; row += 1) {
|
|
407
376
|
let format = formats[row];
|
|
408
377
|
let rowCells = cellRows[row];
|
|
409
378
|
let leadingCell = rowCells[rowCells.length - 1];
|
|
410
379
|
let isLastRow = row === formats.length - 1;
|
|
411
380
|
let isSuperRow = formats.length > 1 && !isLastRow; // more than one row and not the last
|
|
381
|
+
let isMajor = isMajorUnit(date, majorUnit, dateEnv);
|
|
412
382
|
let newCell = null;
|
|
413
383
|
let rowUnit = rowUnitsFromFormats[row] || (isLastRow ? guessedSlotUnit : null);
|
|
414
384
|
if (isSuperRow) {
|
|
415
|
-
let text = dateEnv.format(date, format);
|
|
385
|
+
let [text] = dateEnv.format(date, format);
|
|
416
386
|
if (!leadingCell || (leadingCell.text !== text)) {
|
|
417
|
-
newCell = buildCellObject(date, text, rowUnit);
|
|
387
|
+
newCell = buildCellObject(date, isMajor, text, rowUnit);
|
|
418
388
|
}
|
|
419
389
|
else {
|
|
420
390
|
leadingCell.colspan += 1;
|
|
@@ -422,161 +392,202 @@ function buildCellRows(tDateProfile, dateEnv) {
|
|
|
422
392
|
}
|
|
423
393
|
else if (!leadingCell ||
|
|
424
394
|
isInt(dateEnv.countDurationsBetween(tDateProfile.normalizedRange.start, date, tDateProfile.labelInterval))) {
|
|
425
|
-
let text = dateEnv.format(date, format);
|
|
426
|
-
newCell = buildCellObject(date, text, rowUnit);
|
|
395
|
+
let [text] = dateEnv.format(date, format);
|
|
396
|
+
newCell = buildCellObject(date, isMajor, text, rowUnit);
|
|
427
397
|
}
|
|
428
398
|
else {
|
|
429
399
|
leadingCell.colspan += 1;
|
|
430
400
|
}
|
|
431
401
|
if (newCell) {
|
|
432
|
-
newCell.weekStart = isWeekStart;
|
|
433
402
|
rowCells.push(newCell);
|
|
434
403
|
}
|
|
435
404
|
}
|
|
436
405
|
}
|
|
437
406
|
return cellRows;
|
|
438
407
|
}
|
|
439
|
-
function buildCellObject(date, text, rowUnit) {
|
|
440
|
-
return { date, text, rowUnit, colspan: 1
|
|
408
|
+
function buildCellObject(date, isMajor, text, rowUnit) {
|
|
409
|
+
return { date, isMajor, text, rowUnit, colspan: 1 }; // colspan mutated later
|
|
441
410
|
}
|
|
442
411
|
|
|
443
412
|
class TimelineSlatCell extends BaseComponent {
|
|
444
413
|
constructor() {
|
|
445
414
|
super(...arguments);
|
|
446
|
-
//
|
|
447
|
-
this.
|
|
415
|
+
// memo
|
|
416
|
+
this.getDateMeta = memoize(getDateMeta);
|
|
448
417
|
}
|
|
449
418
|
render() {
|
|
450
419
|
let { props, context } = this;
|
|
451
420
|
let { dateEnv, options } = context;
|
|
452
|
-
let { date, tDateProfile,
|
|
453
|
-
let dateMeta = getDateMeta(props.date, props.
|
|
454
|
-
let
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
'fc-timeline-slot-major' :
|
|
461
|
-
'fc-timeline-slot-minor'), 'fc-timeline-slot-lane fc-cell fc-flex-col fc-align-start', props.borderStart && 'fc-border-s', props.isDay ?
|
|
462
|
-
getDayClassName(dateMeta) :
|
|
463
|
-
getSlotClassName(dateMeta)), attrs: {
|
|
464
|
-
'data-date': dateEnv.formatIso(date, {
|
|
421
|
+
let { date, tDateProfile, isMajor } = props;
|
|
422
|
+
let dateMeta = this.getDateMeta(props.date, dateEnv, props.dateProfile, props.todayRange, props.nowDate);
|
|
423
|
+
let isMinor = tDateProfile.isTimeScale &&
|
|
424
|
+
!isInt(dateEnv.countDurationsBetween(tDateProfile.normalizedRange.start, props.date, tDateProfile.labelInterval));
|
|
425
|
+
let renderProps = Object.assign(Object.assign({}, dateMeta), { isMajor,
|
|
426
|
+
isMinor, view: context.viewApi });
|
|
427
|
+
return (createElement(ContentContainer, { tag: "div", className: joinClassNames(classNames.tight, classNames.alignStart, // shrinks width of InnerContent
|
|
428
|
+
props.borderStart ? classNames.borderOnlyS : classNames.borderNone, classNames.internalTimelineSlot), attrs: Object.assign({ 'data-date': dateEnv.formatIso(date, {
|
|
465
429
|
omitTimeZoneOffset: true,
|
|
466
430
|
omitTime: !tDateProfile.isTimeScale,
|
|
467
|
-
}),
|
|
468
|
-
}, style: {
|
|
431
|
+
}) }, (dateMeta.isToday ? { 'aria-current': 'date' } : {})), style: {
|
|
469
432
|
width: props.width,
|
|
470
|
-
}, renderProps: renderProps, generatorName:
|
|
433
|
+
}, renderProps: renderProps, generatorName: undefined, classNameGenerator: options.slotLaneClass, didMount: options.slotLaneDidMount, willUnmount: options.slotLaneWillUnmount }));
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
class TimelineSlats extends BaseComponent {
|
|
438
|
+
render() {
|
|
439
|
+
let { props } = this;
|
|
440
|
+
let { tDateProfile, slotWidth } = props;
|
|
441
|
+
let { slotDates, slotDatesMajor } = tDateProfile;
|
|
442
|
+
return (createElement("div", { "aria-hidden": true, className: joinClassNames(classNames.flexRow, classNames.fill), style: { height: props.height } }, slotDates.map((slotDate, i) => {
|
|
443
|
+
let key = slotDate.toISOString();
|
|
444
|
+
return (createElement(TimelineSlatCell, { key: key, date: slotDate, dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: props.nowDate, todayRange: props.todayRange, isMajor: slotDatesMajor[i], borderStart: Boolean(i),
|
|
445
|
+
// dimensions
|
|
446
|
+
width: slotWidth }));
|
|
447
|
+
})));
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
class TimelineHeaderCell extends BaseComponent {
|
|
452
|
+
constructor() {
|
|
453
|
+
super(...arguments);
|
|
454
|
+
// memo
|
|
455
|
+
this.getDateMeta = memoize(getDateMeta);
|
|
456
|
+
// ref
|
|
457
|
+
this.innerWrapperElRef = createRef();
|
|
458
|
+
}
|
|
459
|
+
render() {
|
|
460
|
+
let { props, state, context } = this;
|
|
461
|
+
let { dateEnv, options } = context;
|
|
462
|
+
let { cell, dateProfile, tDateProfile } = props;
|
|
463
|
+
// the cell.rowUnit is f'd
|
|
464
|
+
// giving 'month' for a 3-day view
|
|
465
|
+
// workaround: to infer day, do NOT time
|
|
466
|
+
let dateMeta = this.getDateMeta(cell.date, dateEnv, dateProfile, props.todayRange, props.nowDate);
|
|
467
|
+
let hasNavLink = options.navLinks && !dateMeta.isDisabled && (cell.rowUnit && cell.rowUnit !== 'time');
|
|
468
|
+
let isTime = tDateProfile.isTimeScale && !props.rowLevel; // HACK: faulty way of determining this
|
|
469
|
+
let renderProps = Object.assign(Object.assign({}, dateMeta), { level: props.rowLevel, isMajor: cell.isMajor, isMinor: false, isNarrow: false, isTime,
|
|
470
|
+
hasNavLink, text: cell.text, isFirst: props.isFirst, view: context.viewApi });
|
|
471
|
+
const { slotHeaderAlign } = options;
|
|
472
|
+
const align = this.align =
|
|
473
|
+
typeof slotHeaderAlign === 'function'
|
|
474
|
+
? slotHeaderAlign({ level: props.rowLevel, isTime })
|
|
475
|
+
: slotHeaderAlign;
|
|
476
|
+
const isSticky = this.isSticky =
|
|
477
|
+
props.rowLevel && options.slotHeaderSticky !== false;
|
|
478
|
+
let edgeCoord;
|
|
479
|
+
if (isSticky) {
|
|
480
|
+
if (align === 'center') {
|
|
481
|
+
if (state.innerWidth != null) {
|
|
482
|
+
edgeCoord = `calc(50% - ${state.innerWidth / 2}px)`;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
else {
|
|
486
|
+
edgeCoord = (typeof options.slotHeaderSticky === 'number' ||
|
|
487
|
+
typeof options.slotHeaderSticky === 'string') ? options.slotHeaderSticky : 0;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return (createElement(ContentContainer, { tag: "div", className: joinArrayishClassNames(classNames.tight, classNames.flexCol, props.isFirst ? classNames.borderNone : classNames.borderOnlyS, align === 'center' ? classNames.alignCenter :
|
|
491
|
+
align === 'end' ? classNames.alignEnd :
|
|
492
|
+
classNames.alignStart, classNames.internalTimelineSlot), attrs: Object.assign({ 'data-date': dateEnv.formatIso(cell.date, {
|
|
493
|
+
omitTime: !tDateProfile.isTimeScale,
|
|
494
|
+
omitTimeZoneOffset: true,
|
|
495
|
+
}) }, (dateMeta.isToday ? { 'aria-current': 'date' } : {})), style: {
|
|
496
|
+
width: props.slotWidth != null
|
|
497
|
+
? props.slotWidth * cell.colspan
|
|
498
|
+
: undefined,
|
|
499
|
+
}, renderProps: renderProps, generatorName: "slotHeaderContent", customGenerator: options.slotHeaderContent, defaultGenerator: renderInnerContent, classNameGenerator: options.slotHeaderClass, didMount: options.slotHeaderDidMount, willUnmount: options.slotHeaderWillUnmount }, (InnerContent) => (createElement("div", { ref: this.innerWrapperElRef, className: joinClassNames(classNames.flexCol, classNames.rigid, isSticky && classNames.sticky), style: {
|
|
500
|
+
left: edgeCoord,
|
|
501
|
+
right: edgeCoord,
|
|
502
|
+
} },
|
|
503
|
+
createElement(InnerContent, { tag: 'div', attrs: hasNavLink
|
|
504
|
+
// not tabbable because parent is aria-hidden
|
|
505
|
+
? buildNavLinkAttrs(context, cell.date, cell.rowUnit, undefined, /* isTabbable = */ false)
|
|
506
|
+
: {} // don't bother with aria-hidden because parent already hidden
|
|
507
|
+
, className: generateClassName(options.slotHeaderInnerClass, renderProps) })))));
|
|
471
508
|
}
|
|
472
509
|
componentDidMount() {
|
|
473
|
-
const
|
|
474
|
-
this.
|
|
475
|
-
|
|
510
|
+
const { props } = this;
|
|
511
|
+
const innerWrapperEl = this.innerWrapperElRef.current; // TODO: make dynamic with useEffect
|
|
512
|
+
this.disconnectSize = watchSize(innerWrapperEl, (width, height) => {
|
|
513
|
+
setRef(props.innerWidthRef, width);
|
|
514
|
+
setRef(props.innerHeightRef, height);
|
|
515
|
+
if (this.align === 'center' && this.isSticky) {
|
|
516
|
+
this.setState({ innerWidth: width });
|
|
517
|
+
}
|
|
476
518
|
});
|
|
477
519
|
}
|
|
478
520
|
componentWillUnmount() {
|
|
479
|
-
this
|
|
480
|
-
|
|
521
|
+
const { props } = this;
|
|
522
|
+
this.disconnectSize();
|
|
523
|
+
setRef(props.innerWidthRef, null);
|
|
524
|
+
setRef(props.innerHeightRef, null);
|
|
481
525
|
}
|
|
482
526
|
}
|
|
527
|
+
// Utils
|
|
528
|
+
// -------------------------------------------------------------------------------------------------
|
|
529
|
+
function renderInnerContent(renderProps) {
|
|
530
|
+
return renderProps.text;
|
|
531
|
+
}
|
|
483
532
|
|
|
484
|
-
class
|
|
533
|
+
class TimelineHeaderRow extends BaseComponent {
|
|
485
534
|
constructor() {
|
|
486
535
|
super(...arguments);
|
|
536
|
+
// refs
|
|
487
537
|
this.innerWidthRefMap = new RefMap(() => {
|
|
488
538
|
afterSize(this.handleInnerWidths);
|
|
489
539
|
});
|
|
540
|
+
this.innerHeightRefMap = new RefMap(() => {
|
|
541
|
+
afterSize(this.handleInnerHeights);
|
|
542
|
+
});
|
|
490
543
|
this.handleInnerWidths = () => {
|
|
491
544
|
const innerWidthMap = this.innerWidthRefMap.current;
|
|
492
545
|
let max = 0;
|
|
493
546
|
for (const innerWidth of innerWidthMap.values()) {
|
|
494
547
|
max = Math.max(max, innerWidth);
|
|
495
548
|
}
|
|
496
|
-
// TODO:
|
|
549
|
+
// TODO: ensure not equal?
|
|
497
550
|
setRef(this.props.innerWidthRef, max);
|
|
498
551
|
};
|
|
552
|
+
this.handleInnerHeights = () => {
|
|
553
|
+
const innerHeightMap = this.innerHeightRefMap.current;
|
|
554
|
+
let max = 0;
|
|
555
|
+
for (const innerHeight of innerHeightMap.values()) {
|
|
556
|
+
max = Math.max(max, innerHeight);
|
|
557
|
+
}
|
|
558
|
+
// TODO: ensure not equal?
|
|
559
|
+
setRef(this.props.innerHeighRef, max);
|
|
560
|
+
this.setState({ innerHeight: max });
|
|
561
|
+
};
|
|
499
562
|
}
|
|
500
563
|
render() {
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
564
|
+
const { props, innerWidthRefMap, innerHeightRefMap, state, context } = this;
|
|
565
|
+
const { options } = context;
|
|
566
|
+
return (createElement("div", { className: joinArrayishClassNames(options.slotHeaderRowClass, classNames.flexRow, classNames.grow, props.rowLevel // not the last row?
|
|
567
|
+
? classNames.borderOnlyB
|
|
568
|
+
: classNames.borderNone), style: {
|
|
569
|
+
// we assign height because we allow cells to have distorted heights for visual effect
|
|
570
|
+
// but we still want to keep the overall extrenal mass
|
|
571
|
+
height: state.innerHeight,
|
|
572
|
+
} }, props.cells.map((cell, cellI) => {
|
|
573
|
+
// TODO: make this part of the cell obj?
|
|
574
|
+
// TODO: rowUnit seems wrong sometimes. says 'month' when it should be day
|
|
575
|
+
// TODO: rowUnit is relevant to whole row. put it on a row object, not the cells
|
|
576
|
+
// TODO: use rowUnit to key the Row itself?
|
|
577
|
+
const key = cell.rowUnit + ':' + cell.date.toISOString();
|
|
578
|
+
return (createElement(TimelineHeaderCell, { key: key, cell: cell, rowLevel: props.rowLevel, dateProfile: props.dateProfile, tDateProfile: props.tDateProfile, todayRange: props.todayRange, nowDate: props.nowDate, isFirst: cellI === 0,
|
|
579
|
+
// refs
|
|
580
|
+
innerWidthRef: innerWidthRefMap.createRef(key), innerHeightRef: innerHeightRefMap.createRef(key),
|
|
510
581
|
// dimensions
|
|
511
|
-
|
|
582
|
+
slotWidth: props.slotWidth }));
|
|
512
583
|
})));
|
|
513
584
|
}
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
TODO: rename this file!
|
|
518
|
-
*/
|
|
519
|
-
// returned value is between 0 and the number of snaps
|
|
520
|
-
function computeDateSnapCoverage$1(date, tDateProfile, dateEnv) {
|
|
521
|
-
let snapDiff = dateEnv.countDurationsBetween(tDateProfile.normalizedRange.start, date, tDateProfile.snapDuration);
|
|
522
|
-
if (snapDiff < 0) {
|
|
523
|
-
return 0;
|
|
524
|
-
}
|
|
525
|
-
if (snapDiff >= tDateProfile.snapDiffToIndex.length) {
|
|
526
|
-
return tDateProfile.snapCnt;
|
|
527
|
-
}
|
|
528
|
-
let snapDiffInt = Math.floor(snapDiff);
|
|
529
|
-
let snapCoverage = tDateProfile.snapDiffToIndex[snapDiffInt];
|
|
530
|
-
if (isInt(snapCoverage)) { // not an in-between value
|
|
531
|
-
snapCoverage += snapDiff - snapDiffInt; // add the remainder
|
|
532
|
-
}
|
|
533
|
-
else {
|
|
534
|
-
// a fractional value, meaning the date is not visible
|
|
535
|
-
// always round up in this case. works for start AND end dates in a range.
|
|
536
|
-
snapCoverage = Math.ceil(snapCoverage);
|
|
537
|
-
}
|
|
538
|
-
return snapCoverage;
|
|
539
|
-
}
|
|
540
|
-
/*
|
|
541
|
-
TODO: DRY up with elsewhere?
|
|
542
|
-
*/
|
|
543
|
-
function horizontalsToCss(hcoord, isRtl) {
|
|
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
|
-
}
|
|
553
|
-
}
|
|
554
|
-
function horizontalCoordToCss(start, isRtl) {
|
|
555
|
-
if (isRtl) {
|
|
556
|
-
return { right: start };
|
|
557
|
-
}
|
|
558
|
-
else {
|
|
559
|
-
return { left: start };
|
|
585
|
+
componentWillUnmount() {
|
|
586
|
+
setRef(this.props.innerWidthRef, null);
|
|
587
|
+
setRef(this.props.innerHeighRef, null);
|
|
560
588
|
}
|
|
561
589
|
}
|
|
562
590
|
|
|
563
|
-
function createVerticalStyle(props) {
|
|
564
|
-
if (props) {
|
|
565
|
-
return {
|
|
566
|
-
top: props.start,
|
|
567
|
-
height: props.size,
|
|
568
|
-
};
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
function createHorizontalStyle(// TODO: DRY up?
|
|
572
|
-
props, isRtl) {
|
|
573
|
-
if (props) {
|
|
574
|
-
return {
|
|
575
|
-
[isRtl ? 'right' : 'left']: props.start,
|
|
576
|
-
width: props.size,
|
|
577
|
-
};
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
591
|
// Timeline-specific
|
|
581
592
|
// -------------------------------------------------------------------------------------------------
|
|
582
593
|
const MIN_SLOT_WIDTH = 30; // for real
|
|
@@ -589,17 +600,17 @@ function computeSlotWidth(slatCnt, slatsPerLabel, slatMinWidth, labelInnerWidth,
|
|
|
589
600
|
}
|
|
590
601
|
slatMinWidth = Math.max(slatMinWidth || 0, (labelInnerWidth + 1) / slatsPerLabel, MIN_SLOT_WIDTH);
|
|
591
602
|
const slatTryWidth = viewportWidth / slatCnt;
|
|
592
|
-
let
|
|
603
|
+
let slotLiquid;
|
|
593
604
|
let slatWidth;
|
|
594
605
|
if (slatTryWidth >= slatMinWidth) {
|
|
595
|
-
|
|
606
|
+
slotLiquid = true;
|
|
596
607
|
slatWidth = slatTryWidth;
|
|
597
608
|
}
|
|
598
609
|
else {
|
|
599
|
-
|
|
610
|
+
slotLiquid = false;
|
|
600
611
|
slatWidth = Math.max(slatMinWidth, slatTryWidth);
|
|
601
612
|
}
|
|
602
|
-
return [slatWidth * slatCnt, slatWidth,
|
|
613
|
+
return [slatWidth * slatCnt, slatWidth, slotLiquid];
|
|
603
614
|
}
|
|
604
615
|
function timeToCoord(// pixels
|
|
605
616
|
time, dateEnv, dateProfile, tDateProfile, slowWidth) {
|
|
@@ -611,13 +622,86 @@ time, dateEnv, dateProfile, tDateProfile, slowWidth) {
|
|
|
611
622
|
}
|
|
612
623
|
function dateToCoord(// pixels
|
|
613
624
|
date, dateEnv, tDateProfile, slotWidth) {
|
|
614
|
-
let snapCoverage = computeDateSnapCoverage(date, tDateProfile, dateEnv);
|
|
625
|
+
let snapCoverage = computeDateSnapCoverage$1(date, tDateProfile, dateEnv);
|
|
615
626
|
let slotCoverage = snapCoverage / tDateProfile.snapsPerSlot;
|
|
616
627
|
return slotCoverage * slotWidth;
|
|
617
628
|
}
|
|
618
629
|
/*
|
|
619
630
|
returned value is between 0 and the number of snaps
|
|
620
631
|
*/
|
|
632
|
+
function computeDateSnapCoverage$1(date, tDateProfile, dateEnv) {
|
|
633
|
+
let snapDiff = dateEnv.countDurationsBetween(tDateProfile.normalizedRange.start, date, tDateProfile.snapDuration);
|
|
634
|
+
if (snapDiff < 0) {
|
|
635
|
+
return 0;
|
|
636
|
+
}
|
|
637
|
+
if (snapDiff >= tDateProfile.snapDiffToIndex.length) {
|
|
638
|
+
return tDateProfile.snapCnt;
|
|
639
|
+
}
|
|
640
|
+
let snapDiffInt = Math.floor(snapDiff);
|
|
641
|
+
let snapCoverage = tDateProfile.snapDiffToIndex[snapDiffInt];
|
|
642
|
+
if (isInt(snapCoverage)) { // not an in-between value
|
|
643
|
+
snapCoverage += snapDiff - snapDiffInt; // add the remainder
|
|
644
|
+
}
|
|
645
|
+
else {
|
|
646
|
+
// a fractional value, meaning the date is not visible
|
|
647
|
+
// always round up in this case. works for start AND end dates in a range.
|
|
648
|
+
snapCoverage = Math.ceil(snapCoverage);
|
|
649
|
+
}
|
|
650
|
+
return snapCoverage;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
class TimelineNowIndicatorLine extends BaseComponent {
|
|
654
|
+
render() {
|
|
655
|
+
const { props, context } = this;
|
|
656
|
+
const xStyle = props.slotWidth == null
|
|
657
|
+
? {}
|
|
658
|
+
: {
|
|
659
|
+
insetInlineStart: dateToCoord(props.nowDate, context.dateEnv, props.tDateProfile, props.slotWidth)
|
|
660
|
+
};
|
|
661
|
+
return (createElement("div", { className: classNames.fill, style: {
|
|
662
|
+
zIndex: 2,
|
|
663
|
+
pointerEvents: 'none', // TODO: className
|
|
664
|
+
} },
|
|
665
|
+
createElement(NowIndicatorLineContainer, { className: joinClassNames(classNames.fillY, classNames.noMarginY, classNames.borderlessY), style: xStyle, date: props.nowDate }),
|
|
666
|
+
createElement("div", { className: joinClassNames(classNames.flexCol, // better for negative margins
|
|
667
|
+
classNames.fillY), style: xStyle },
|
|
668
|
+
createElement("div", {
|
|
669
|
+
// stickiness on NowIndicatorDot misbehaves b/c of negative marginss
|
|
670
|
+
className: classNames.stickyT },
|
|
671
|
+
createElement(NowIndicatorDot, null)))));
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/*
|
|
676
|
+
TODO: DRY with other NowIndicator components
|
|
677
|
+
*/
|
|
678
|
+
class TimelineNowIndicatorArrow extends BaseComponent {
|
|
679
|
+
render() {
|
|
680
|
+
const { props, context } = this;
|
|
681
|
+
const xStyle = props.slotWidth == null
|
|
682
|
+
? {}
|
|
683
|
+
: {
|
|
684
|
+
insetInlineStart: dateToCoord(props.nowDate, context.dateEnv, props.tDateProfile, props.slotWidth)
|
|
685
|
+
};
|
|
686
|
+
return (createElement("div", {
|
|
687
|
+
// crop any overflow that the arrow/line might cause
|
|
688
|
+
// TODO: just do this on the entire canvas within the scroller
|
|
689
|
+
className: joinClassNames(classNames.fill, classNames.crop), style: {
|
|
690
|
+
zIndex: 2,
|
|
691
|
+
pointerEvents: 'none', // TODO: className
|
|
692
|
+
} },
|
|
693
|
+
createElement(NowIndicatorHeaderContainer, { className: classNames.abs, style: xStyle, date: props.nowDate })));
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
function getTimelineSlotEl(parentEl, index) {
|
|
698
|
+
return parentEl.querySelectorAll(`.${classNames.internalTimelineSlot}`)[index];
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/*
|
|
702
|
+
TODO: rename this file!
|
|
703
|
+
*/
|
|
704
|
+
// returned value is between 0 and the number of snaps
|
|
621
705
|
function computeDateSnapCoverage(date, tDateProfile, dateEnv) {
|
|
622
706
|
let snapDiff = dateEnv.countDurationsBetween(tDateProfile.normalizedRange.start, date, tDateProfile.snapDuration);
|
|
623
707
|
if (snapDiff < 0) {
|
|
@@ -639,6 +723,60 @@ function computeDateSnapCoverage(date, tDateProfile, dateEnv) {
|
|
|
639
723
|
return snapCoverage;
|
|
640
724
|
}
|
|
641
725
|
|
|
726
|
+
class TimelineLaneSlicer extends Slicer {
|
|
727
|
+
sliceRange(origRange, dateProfile, dateProfileGenerator, tDateProfile, dateEnv) {
|
|
728
|
+
let normalRange = normalizeRange(origRange, tDateProfile, dateEnv);
|
|
729
|
+
let segs = [];
|
|
730
|
+
// protect against when the span is entirely in an invalid date region
|
|
731
|
+
if (computeDateSnapCoverage(normalRange.start, tDateProfile, dateEnv)
|
|
732
|
+
< computeDateSnapCoverage(normalRange.end, tDateProfile, dateEnv)) {
|
|
733
|
+
// intersect the footprint's range with the grid's range
|
|
734
|
+
let slicedRange = intersectRanges(normalRange, tDateProfile.normalizedRange);
|
|
735
|
+
if (slicedRange) {
|
|
736
|
+
segs.push({
|
|
737
|
+
startDate: slicedRange.start,
|
|
738
|
+
endDate: slicedRange.end,
|
|
739
|
+
isStart: slicedRange.start.valueOf() === normalRange.start.valueOf()
|
|
740
|
+
&& isValidDate(slicedRange.start, tDateProfile, dateProfile, dateProfileGenerator),
|
|
741
|
+
isEnd: slicedRange.end.valueOf() === normalRange.end.valueOf()
|
|
742
|
+
&& isValidDate(addMs(slicedRange.end, -1), tDateProfile, dateProfile, dateProfileGenerator),
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
return segs;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
const DEFAULT_TIME_FORMAT = createFormatter({
|
|
751
|
+
hour: 'numeric',
|
|
752
|
+
minute: '2-digit',
|
|
753
|
+
omitZeroMinute: true,
|
|
754
|
+
meridiem: 'narrow',
|
|
755
|
+
});
|
|
756
|
+
class TimelineEvent extends BaseComponent {
|
|
757
|
+
render() {
|
|
758
|
+
let { props } = this;
|
|
759
|
+
return (createElement(StandardEvent, Object.assign({}, props, { display: 'row', defaultTimeFormat: DEFAULT_TIME_FORMAT, defaultDisplayEventTime: !props.isTimeScale })));
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
class TimelineLaneMoreLink extends BaseComponent {
|
|
764
|
+
render() {
|
|
765
|
+
let { props } = this;
|
|
766
|
+
let { hiddenSegs, resourceId } = props;
|
|
767
|
+
let dateSpanProps = resourceId ? { resourceId } : {};
|
|
768
|
+
return (createElement(MoreLinkContainer, { display: 'row', allDayDate: null, segs: hiddenSegs, hiddenSegs: hiddenSegs, dateProfile: props.dateProfile, todayRange: props.todayRange, dateSpanProps: dateSpanProps, isNarrow: false, isMicro: false, popoverContent: () => (createElement(Fragment, null, hiddenSegs.map((seg) => {
|
|
769
|
+
let { eventRange } = seg;
|
|
770
|
+
let { instanceId } = eventRange.instance;
|
|
771
|
+
let isDragging = Boolean(props.eventDrag && props.eventDrag.affectedInstances[instanceId]);
|
|
772
|
+
let isResizing = Boolean(props.eventResize && props.eventResize.affectedInstances[instanceId]);
|
|
773
|
+
let isInvisible = isDragging || isResizing;
|
|
774
|
+
return (createElement("div", { key: instanceId, style: { visibility: isInvisible ? 'hidden' : undefined } },
|
|
775
|
+
createElement(TimelineEvent, Object.assign({ isTimeScale: props.isTimeScale, eventRange: eventRange, isStart: seg.isStart, isEnd: seg.isEnd, isDragging: isDragging, isResizing: isResizing, isMirror: false, isSelected: instanceId === props.eventSelection }, getEventRangeMeta(eventRange, props.todayRange, props.nowDate)))));
|
|
776
|
+
}))) }));
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
642
780
|
function computeManySegHorizontals(segs, segMinWidth, dateEnv, tDateProfile, slotWidth) {
|
|
643
781
|
const res = {};
|
|
644
782
|
for (const seg of segs) {
|
|
@@ -703,83 +841,6 @@ hiddenGroupHeights, strictOrder, maxDepth) {
|
|
|
703
841
|
];
|
|
704
842
|
}
|
|
705
843
|
|
|
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
844
|
/*
|
|
784
845
|
TODO: make DRY with other Event Harnesses
|
|
785
846
|
*/
|
|
@@ -791,7 +852,7 @@ class TimelineEventHarness extends Component {
|
|
|
791
852
|
}
|
|
792
853
|
render() {
|
|
793
854
|
const { props } = this;
|
|
794
|
-
return (createElement("div", { className:
|
|
855
|
+
return (createElement("div", { className: classNames.abs, style: props.style, ref: this.rootElRef }, props.children));
|
|
795
856
|
}
|
|
796
857
|
componentDidMount() {
|
|
797
858
|
const rootEl = this.rootElRef.current; // TODO: make dynamic with useEffect
|
|
@@ -805,10 +866,7 @@ class TimelineEventHarness extends Component {
|
|
|
805
866
|
}
|
|
806
867
|
}
|
|
807
868
|
|
|
808
|
-
|
|
809
|
-
TODO: split TimelineLaneBg and TimelineLaneFg?
|
|
810
|
-
*/
|
|
811
|
-
class TimelineLane extends BaseComponent {
|
|
869
|
+
class TimelineFg extends BaseComponent {
|
|
812
870
|
constructor() {
|
|
813
871
|
super(...arguments);
|
|
814
872
|
// memo
|
|
@@ -820,8 +878,6 @@ class TimelineLane extends BaseComponent {
|
|
|
820
878
|
this.moreLinkHeightRefMap = new RefMap(() => {
|
|
821
879
|
afterSize(this.handleMoreLinkHeights);
|
|
822
880
|
});
|
|
823
|
-
// internal
|
|
824
|
-
this.slicer = new TimelineLaneSlicer();
|
|
825
881
|
this.handleMoreLinkHeights = () => {
|
|
826
882
|
this.setState({ moreLinkHeightRev: this.moreLinkHeightRefMap.rev }); // will trigger rerender
|
|
827
883
|
};
|
|
@@ -835,212 +891,92 @@ class TimelineLane extends BaseComponent {
|
|
|
835
891
|
render() {
|
|
836
892
|
let { props, context, segHeightRefMap, moreLinkHeightRefMap } = this;
|
|
837
893
|
let { options } = context;
|
|
838
|
-
let {
|
|
839
|
-
let
|
|
840
|
-
|
|
841
|
-
let mirrorSegs = (slicedProps.eventDrag ? slicedProps.eventDrag.segs : null) ||
|
|
842
|
-
(slicedProps.eventResize ? slicedProps.eventResize.segs : null) ||
|
|
894
|
+
let { tDateProfile } = props;
|
|
895
|
+
let mirrorSegs = (props.eventDrag ? props.eventDrag.segs : null) ||
|
|
896
|
+
(props.eventResize ? props.eventResize.segs : null) ||
|
|
843
897
|
[];
|
|
844
|
-
let fgSegs = this.sortEventSegs(
|
|
898
|
+
let fgSegs = this.sortEventSegs(props.fgEventSegs, options.eventOrder);
|
|
845
899
|
let fgSegHorizontals = props.slotWidth != null
|
|
846
900
|
? computeManySegHorizontals(fgSegs, options.eventMinWidth, context.dateEnv, tDateProfile, props.slotWidth)
|
|
847
901
|
: {};
|
|
848
902
|
let [fgSegTops, hiddenGroups, hiddenGroupTops, totalHeight] = computeFgSegPlacements(fgSegs, fgSegHorizontals, segHeightRefMap.current, moreLinkHeightRefMap.current, options.eventOrderStrict, options.eventMaxStack);
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
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))));
|
|
871
|
-
}
|
|
872
|
-
renderFgSegs(segs, segHorizontals, segTops, forcedInvisibleMap, hiddenGroups, hiddenGroupTops, isDragging, isResizing, isDateSelecting) {
|
|
873
|
-
let { props, context, segHeightRefMap, moreLinkHeightRefMap } = this;
|
|
874
|
-
let isMirror = isDragging || isResizing || isDateSelecting;
|
|
903
|
+
this.totalHeight = totalHeight;
|
|
904
|
+
return (createElement("div", { className: joinClassNames(classNames.rel, classNames.noShrink), style: {
|
|
905
|
+
height: totalHeight,
|
|
906
|
+
} },
|
|
907
|
+
this.renderFgSegs(fgSegs, fgSegHorizontals, fgSegTops, hiddenGroups, hiddenGroupTops,
|
|
908
|
+
/* isMirror = */ false),
|
|
909
|
+
this.renderFgSegs(mirrorSegs, props.slotWidth // TODO: memoize
|
|
910
|
+
? computeManySegHorizontals(mirrorSegs, options.eventMinWidth, context.dateEnv, tDateProfile, props.slotWidth)
|
|
911
|
+
: {}, fgSegTops,
|
|
912
|
+
/* hiddenGroups = */ [],
|
|
913
|
+
/* hiddenGroupTops = */ new Map(),
|
|
914
|
+
/* isMirror = */ true)));
|
|
915
|
+
}
|
|
916
|
+
renderFgSegs(segs, segHorizontals, segTops, hiddenGroups, hiddenGroupTops, isMirror) {
|
|
917
|
+
let { props, segHeightRefMap, moreLinkHeightRefMap } = this;
|
|
875
918
|
return (createElement(Fragment, null,
|
|
876
919
|
segs.map((seg) => {
|
|
877
920
|
const { eventRange } = seg;
|
|
878
921
|
const { instanceId } = eventRange.instance;
|
|
879
922
|
const segTop = segTops.get(instanceId);
|
|
880
|
-
const
|
|
881
|
-
const
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
923
|
+
const segHorizontalMaybe = segHorizontals[instanceId];
|
|
924
|
+
const segHorizontal = segHorizontalMaybe || {};
|
|
925
|
+
const isDragging = Boolean(props.eventDrag && props.eventDrag.affectedInstances[instanceId]);
|
|
926
|
+
const isResizing = Boolean(props.eventResize && props.eventResize.affectedInstances[instanceId]);
|
|
927
|
+
const isInvisible = !isMirror && (isDragging || isResizing || !segHorizontalMaybe || segTop == null);
|
|
928
|
+
return (createElement(TimelineEventHarness, { key: instanceId, style: {
|
|
929
|
+
visibility: isInvisible ? 'hidden' : undefined,
|
|
930
|
+
zIndex: 1,
|
|
931
|
+
top: segTop || 0,
|
|
932
|
+
insetInlineStart: segHorizontal.start,
|
|
933
|
+
width: segHorizontal.size,
|
|
934
|
+
}, heightRef: isMirror ? undefined : segHeightRefMap.createRef(instanceId) },
|
|
935
|
+
createElement(TimelineEvent, Object.assign({ isTimeScale: props.tDateProfile.isTimeScale, eventRange: eventRange, isStart: seg.isStart, isEnd: seg.isEnd, isDragging: isDragging, isResizing: isResizing, isMirror: isMirror, isSelected: instanceId === props.eventSelection }, getEventRangeMeta(eventRange, props.todayRange, props.nowDate)))));
|
|
885
936
|
}),
|
|
886
|
-
hiddenGroups.map((hiddenGroup) => (createElement(TimelineEventHarness, { key: hiddenGroup.key, style:
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
class TimelineHeaderCell extends BaseComponent {
|
|
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 }))));
|
|
937
|
+
hiddenGroups.map((hiddenGroup) => (createElement(TimelineEventHarness, { key: hiddenGroup.key, style: {
|
|
938
|
+
top: hiddenGroupTops.get(hiddenGroup.key) || 0,
|
|
939
|
+
insetInlineStart: hiddenGroup.start,
|
|
940
|
+
width: hiddenGroup.end - hiddenGroup.start,
|
|
941
|
+
}, heightRef: moreLinkHeightRefMap.createRef(hiddenGroup.key) },
|
|
942
|
+
createElement(TimelineLaneMoreLink, { hiddenSegs: hiddenGroup.segs, dateProfile: props.dateProfile, nowDate: props.nowDate, todayRange: props.todayRange, isTimeScale: props.tDateProfile.isTimeScale, eventDrag: props.eventDrag, eventResize: props.eventResize, eventSelection: props.eventSelection, resourceId: props.resourceId }))))));
|
|
933
943
|
}
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
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
|
-
};
|
|
944
|
+
/*
|
|
945
|
+
componentDidMount(): void {
|
|
946
|
+
// might want to do firedTotalHeight, but won't be ready on first render
|
|
1001
947
|
}
|
|
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
|
-
})));
|
|
948
|
+
*/
|
|
949
|
+
componentDidUpdate() {
|
|
950
|
+
if (this.totalHeight !== this.firedTotalHeight) {
|
|
951
|
+
this.firedTotalHeight = this.totalHeight;
|
|
952
|
+
setRef(this.props.heightRef, this.totalHeight);
|
|
953
|
+
}
|
|
1019
954
|
}
|
|
1020
955
|
componentWillUnmount() {
|
|
1021
|
-
setRef(this.props.
|
|
1022
|
-
setRef(this.props.innerHeighRef, null);
|
|
956
|
+
setRef(this.props.heightRef, null);
|
|
1023
957
|
}
|
|
1024
958
|
}
|
|
1025
959
|
|
|
1026
|
-
class
|
|
960
|
+
class TimelineBg extends BaseComponent {
|
|
1027
961
|
render() {
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
962
|
+
let { props } = this;
|
|
963
|
+
let highlightSeg = [].concat(props.eventResizeSegs || [], props.dateSelectionSegs);
|
|
964
|
+
return (createElement(Fragment, null,
|
|
965
|
+
this.renderSegs(props.businessHourSegs || [], 'non-business'),
|
|
966
|
+
this.renderSegs(props.bgEventSegs || [], 'bg-event'),
|
|
967
|
+
this.renderSegs(highlightSeg, 'highlight')));
|
|
1034
968
|
}
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
969
|
+
renderSegs(segs, fillType) {
|
|
970
|
+
let { tDateProfile, todayRange, nowDate, slotWidth } = this.props;
|
|
971
|
+
let { dateEnv, options } = this.context;
|
|
972
|
+
return (createElement(Fragment, null, segs.map((seg) => {
|
|
973
|
+
let hStyle = {};
|
|
974
|
+
if (slotWidth != null) {
|
|
975
|
+
let segHorizontal = computeSegHorizontals(seg, undefined, dateEnv, tDateProfile, slotWidth);
|
|
976
|
+
hStyle = { insetInlineStart: segHorizontal.start, width: segHorizontal.size };
|
|
977
|
+
}
|
|
978
|
+
return (createElement("div", { key: buildEventRangeKey(seg.eventRange), className: classNames.fillY, style: hStyle }, fillType === 'bg-event' ? (createElement(BgEvent, Object.assign({ eventRange: seg.eventRange, isStart: seg.isStart, isEnd: seg.isEnd, isVertical: false }, getEventRangeMeta(seg.eventRange, todayRange, nowDate)))) : (renderFill(fillType, options))));
|
|
979
|
+
})));
|
|
1044
980
|
}
|
|
1045
981
|
}
|
|
1046
982
|
|
|
@@ -1058,46 +994,34 @@ class TimelineView extends DateComponent {
|
|
|
1058
994
|
afterSize(this.handleSlotInnerWidths);
|
|
1059
995
|
});
|
|
1060
996
|
this.scrollTime = null;
|
|
997
|
+
this.slicer = new TimelineLaneSlicer();
|
|
1061
998
|
// Sizing
|
|
1062
999
|
// -----------------------------------------------------------------------------------------------
|
|
1063
|
-
this.handleBodySlotInnerWidth = (innerWidth) => {
|
|
1064
|
-
this.bodySlotInnerWidth = innerWidth;
|
|
1065
|
-
afterSize(this.handleSlotInnerWidths);
|
|
1066
|
-
};
|
|
1067
1000
|
this.handleSlotInnerWidths = () => {
|
|
1068
|
-
const
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
this.setState({ slotInnerWidth });
|
|
1001
|
+
const headerSlotInnerWidth = this.headerRowInnerWidthMap.current.get(this.tDateProfile.cellRows.length - 1);
|
|
1002
|
+
if (headerSlotInnerWidth != null && headerSlotInnerWidth !== this.state.slotInnerWidth) {
|
|
1003
|
+
this.setState({ slotInnerWidth: headerSlotInnerWidth });
|
|
1072
1004
|
}
|
|
1073
1005
|
};
|
|
1074
|
-
this.
|
|
1006
|
+
this.handleTotalWidth = (totalWidth) => {
|
|
1075
1007
|
this.setState({
|
|
1076
|
-
|
|
1008
|
+
totalWidth,
|
|
1077
1009
|
});
|
|
1078
1010
|
};
|
|
1079
|
-
this.
|
|
1011
|
+
this.handleClientWidth = (clientWidth) => {
|
|
1080
1012
|
this.setState({
|
|
1081
|
-
|
|
1013
|
+
clientWidth,
|
|
1082
1014
|
});
|
|
1083
1015
|
};
|
|
1084
|
-
this.
|
|
1016
|
+
this.handleTimeScrollRequest = (scrollTime) => {
|
|
1085
1017
|
this.scrollTime = scrollTime;
|
|
1086
|
-
this.
|
|
1018
|
+
this.applyTimeScroll();
|
|
1087
1019
|
};
|
|
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 });
|
|
1020
|
+
this.handleTimeScrollEnd = (isUser) => {
|
|
1021
|
+
if (isUser) {
|
|
1022
|
+
this.scrollTime = null;
|
|
1096
1023
|
}
|
|
1097
1024
|
};
|
|
1098
|
-
this.clearScroll = () => {
|
|
1099
|
-
this.scrollTime = null;
|
|
1100
|
-
};
|
|
1101
1025
|
// Hit System
|
|
1102
1026
|
// -----------------------------------------------------------------------------------------------
|
|
1103
1027
|
this.handeBodyEl = (el) => {
|
|
@@ -1113,6 +1037,10 @@ class TimelineView extends DateComponent {
|
|
|
1113
1037
|
render() {
|
|
1114
1038
|
const { props, state, context } = this;
|
|
1115
1039
|
const { options } = context;
|
|
1040
|
+
const { totalWidth, clientWidth } = state;
|
|
1041
|
+
const endScrollbarWidth = (totalWidth != null && clientWidth != null)
|
|
1042
|
+
? totalWidth - clientWidth
|
|
1043
|
+
: undefined;
|
|
1116
1044
|
/* date */
|
|
1117
1045
|
const tDateProfile = this.tDateProfile = this.buildTimelineDateProfile(props.dateProfile, context.dateEnv, options, context.dateProfileGenerator);
|
|
1118
1046
|
const { cellRows } = tDateProfile;
|
|
@@ -1123,39 +1051,65 @@ class TimelineView extends DateComponent {
|
|
|
1123
1051
|
const stickyFooterScrollbar = !props.forPrint && getStickyFooterScrollbar(options);
|
|
1124
1052
|
/* table positions */
|
|
1125
1053
|
const [canvasWidth, slotWidth] = this.computeSlotWidth(tDateProfile.slotCnt, tDateProfile.slotsPerLabel, options.slotMinWidth, state.slotInnerWidth, // is ACTUALLY the label width. rename?
|
|
1126
|
-
|
|
1054
|
+
clientWidth);
|
|
1127
1055
|
this.slotWidth = slotWidth;
|
|
1056
|
+
/* sliced */
|
|
1057
|
+
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...
|
|
1058
|
+
props.dateProfile, context.dateProfileGenerator, tDateProfile, context.dateEnv);
|
|
1128
1059
|
return (createElement(NowTimer, { unit: timerUnit }, (nowDate, todayRange) => {
|
|
1129
1060
|
const enableNowIndicator = // TODO: DRY
|
|
1130
1061
|
options.nowIndicator &&
|
|
1131
1062
|
slotWidth != null &&
|
|
1132
1063
|
rangeContainsMarker(props.dateProfile.currentRange, nowDate);
|
|
1133
|
-
return (createElement(ViewContainer, { viewSpec: context.viewSpec, className:
|
|
1134
|
-
// HACK for Safari print-mode, where
|
|
1064
|
+
return (createElement(ViewContainer, { viewSpec: context.viewSpec, className: joinArrayishClassNames(
|
|
1065
|
+
// HACK for Safari print-mode, where noScrollbars won't take effect for
|
|
1135
1066
|
// the below Scrollers if they have liquid flex height
|
|
1136
|
-
!props.forPrint &&
|
|
1137
|
-
createElement(
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1067
|
+
!props.forPrint && classNames.flexCol, props.className, options.tableClass, classNames.isolate), borderlessX: props.borderlessX, borderlessTop: props.borderlessTop, borderlessBottom: props.borderlessBottom, noEdgeEffects: props.noEdgeEffects },
|
|
1068
|
+
createElement("div", { className: joinClassNames(generateClassName(options.tableHeaderClass, {
|
|
1069
|
+
isSticky: stickyHeaderDates,
|
|
1070
|
+
}), props.borderlessX && classNames.borderlessX, classNames.flexCol, stickyHeaderDates && classNames.tableHeaderSticky), style: {
|
|
1071
|
+
zIndex: 1,
|
|
1072
|
+
} },
|
|
1073
|
+
createElement(Scroller, { horizontal: true, hideScrollbars: true, className: classNames.flexRow, ref: this.headerScrollerRef },
|
|
1074
|
+
createElement("div", {
|
|
1075
|
+
// TODO: DRY
|
|
1076
|
+
className: joinClassNames(classNames.rel, // origin for now-indicator
|
|
1077
|
+
canvasWidth == null && classNames.liquid), style: { width: canvasWidth } },
|
|
1078
|
+
cellRows.map((cells, rowIndex) => {
|
|
1079
|
+
const rowLevel = cellRows.length - rowIndex - 1;
|
|
1080
|
+
return (createElement(TimelineHeaderRow, { key: rowIndex, dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: nowDate, todayRange: todayRange, rowLevel: rowLevel, cells: cells, slotWidth: slotWidth, innerWidthRef: this.headerRowInnerWidthMap.createRef(rowIndex) }));
|
|
1081
|
+
}),
|
|
1082
|
+
enableNowIndicator && (createElement(TimelineNowIndicatorArrow, { tDateProfile: tDateProfile, nowDate: nowDate, slotWidth: slotWidth }))),
|
|
1083
|
+
Boolean(endScrollbarWidth) && (createElement("div", { className: joinArrayishClassNames(generateClassName(options.fillerClass, { isHeader: true }), classNames.borderOnlyS), style: { minWidth: endScrollbarWidth } }))),
|
|
1084
|
+
createElement("div", { className: generateClassName(options.slotHeaderDividerClass, {
|
|
1085
|
+
isHeader: true,
|
|
1086
|
+
options: { dayMinWidth: options.dayMinWidth },
|
|
1087
|
+
}) })),
|
|
1088
|
+
createElement(Scroller, { vertical: verticalScrolling, horizontal: true, hideScrollbars: stickyFooterScrollbar ||
|
|
1089
|
+
props.forPrint // prevents blank space in print-view on Safari
|
|
1090
|
+
, className: joinArrayishClassNames(options.tableBodyClass, props.borderlessX && classNames.borderlessX, stickyHeaderDates && classNames.borderlessTop, (stickyHeaderDates || props.noEdgeEffects) && classNames.noEdgeEffects, classNames.flexCol, verticalScrolling && classNames.liquid), style: {
|
|
1091
|
+
zIndex: 0,
|
|
1092
|
+
}, ref: this.bodyScrollerRef, clientWidthRef: this.handleClientWidth },
|
|
1093
|
+
createElement("div", { "aria-label": options.eventsHint, className: joinClassNames(classNames.rel, // for canvas origin?
|
|
1094
|
+
classNames.grow), style: { width: canvasWidth }, ref: this.handeBodyEl },
|
|
1150
1095
|
createElement(TimelineSlats, { dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: nowDate, todayRange: todayRange,
|
|
1151
|
-
// ref
|
|
1152
|
-
innerWidthRef: this.handleBodySlotInnerWidth,
|
|
1153
1096
|
// dimensions
|
|
1154
1097
|
slotWidth: slotWidth }),
|
|
1155
|
-
createElement(
|
|
1098
|
+
createElement(TimelineBg, { tDateProfile: tDateProfile, nowDate: nowDate, todayRange: todayRange,
|
|
1099
|
+
// content
|
|
1100
|
+
bgEventSegs: slicedProps.bgEventSegs, businessHourSegs: slicedProps.businessHourSegs, dateSelectionSegs: slicedProps.dateSelectionSegs, eventResizeSegs: slicedProps.eventResize ? slicedProps.eventResize.segs : null,
|
|
1101
|
+
// dimensions
|
|
1102
|
+
slotWidth: slotWidth }),
|
|
1103
|
+
createElement("div", { className: joinArrayishClassNames(options.timelineTopClass) }),
|
|
1104
|
+
createElement(TimelineFg, { dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: nowDate, todayRange: todayRange,
|
|
1105
|
+
// content
|
|
1106
|
+
fgEventSegs: slicedProps.fgEventSegs, eventDrag: slicedProps.eventDrag, eventResize: slicedProps.eventResize, eventSelection: slicedProps.eventSelection,
|
|
1107
|
+
// dimensions
|
|
1108
|
+
slotWidth: slotWidth }),
|
|
1109
|
+
createElement("div", { className: joinArrayishClassNames(options.timelineBottomClass) }),
|
|
1156
1110
|
enableNowIndicator && (createElement(TimelineNowIndicatorLine, { tDateProfile: tDateProfile, nowDate: nowDate, slotWidth: slotWidth })))),
|
|
1157
|
-
stickyFooterScrollbar && (createElement(
|
|
1158
|
-
|
|
1111
|
+
Boolean(stickyFooterScrollbar) && (createElement(FooterScrollbar, { isSticky: true, canvasWidth: canvasWidth, scrollerRef: this.footerScrollerRef })),
|
|
1112
|
+
createElement(Ruler, { widthRef: this.handleTotalWidth })));
|
|
1159
1113
|
}));
|
|
1160
1114
|
}
|
|
1161
1115
|
// Lifecycle
|
|
@@ -1164,8 +1118,8 @@ class TimelineView extends DateComponent {
|
|
|
1164
1118
|
this.syncedScroller = new ScrollerSyncer(true); // horizontal=true
|
|
1165
1119
|
this.updateSyncedScroller();
|
|
1166
1120
|
this.resetScroll();
|
|
1167
|
-
this.context.emitter.on('_timeScrollRequest', this.
|
|
1168
|
-
this.syncedScroller.addScrollEndListener(this.
|
|
1121
|
+
this.context.emitter.on('_timeScrollRequest', this.handleTimeScrollRequest);
|
|
1122
|
+
this.syncedScroller.addScrollEndListener(this.handleTimeScrollEnd);
|
|
1169
1123
|
}
|
|
1170
1124
|
componentDidUpdate(prevProps) {
|
|
1171
1125
|
this.updateSyncedScroller();
|
|
@@ -1174,13 +1128,13 @@ class TimelineView extends DateComponent {
|
|
|
1174
1128
|
}
|
|
1175
1129
|
else {
|
|
1176
1130
|
// TODO: inefficient to update so often
|
|
1177
|
-
this.
|
|
1131
|
+
this.applyTimeScroll();
|
|
1178
1132
|
}
|
|
1179
1133
|
}
|
|
1180
1134
|
componentWillUnmount() {
|
|
1181
1135
|
this.syncedScroller.destroy();
|
|
1182
|
-
this.context.emitter.off('_timeScrollRequest', this.
|
|
1183
|
-
this.syncedScroller.removeScrollEndListener(this.
|
|
1136
|
+
this.context.emitter.off('_timeScrollRequest', this.handleTimeScrollRequest);
|
|
1137
|
+
this.syncedScroller.removeScrollEndListener(this.handleTimeScrollEnd);
|
|
1184
1138
|
}
|
|
1185
1139
|
// Scrolling
|
|
1186
1140
|
// -----------------------------------------------------------------------------------------------
|
|
@@ -1192,13 +1146,23 @@ class TimelineView extends DateComponent {
|
|
|
1192
1146
|
]);
|
|
1193
1147
|
}
|
|
1194
1148
|
resetScroll() {
|
|
1195
|
-
this.
|
|
1149
|
+
this.handleTimeScrollRequest(this.context.options.scrollTime);
|
|
1150
|
+
}
|
|
1151
|
+
applyTimeScroll() {
|
|
1152
|
+
const { props, context, tDateProfile, scrollTime, slotWidth } = this;
|
|
1153
|
+
if (scrollTime != null && slotWidth != null) {
|
|
1154
|
+
let x = timeToCoord(scrollTime, context.dateEnv, props.dateProfile, tDateProfile, slotWidth);
|
|
1155
|
+
if (x) {
|
|
1156
|
+
x += 1; // overcome border. TODO: DRY this up
|
|
1157
|
+
}
|
|
1158
|
+
this.syncedScroller.scrollTo({ x });
|
|
1159
|
+
}
|
|
1196
1160
|
}
|
|
1197
|
-
queryHit(positionLeft, positionTop, elWidth, elHeight) {
|
|
1161
|
+
queryHit(isRtl, positionLeft, positionTop, elWidth, elHeight) {
|
|
1198
1162
|
const { props, context, tDateProfile, slotWidth } = this;
|
|
1199
1163
|
const { dateEnv } = context;
|
|
1200
1164
|
if (slotWidth) {
|
|
1201
|
-
const x =
|
|
1165
|
+
const x = isRtl ? elWidth - positionLeft : positionLeft;
|
|
1202
1166
|
const slatIndex = Math.floor(x / slotWidth);
|
|
1203
1167
|
const slatX = slatIndex * slotWidth;
|
|
1204
1168
|
const partial = (x - slatX) / slotWidth; // floating point number between 0 and 1
|
|
@@ -1210,7 +1174,7 @@ class TimelineView extends DateComponent {
|
|
|
1210
1174
|
let startCoord = slatIndex * slotWidth + (snapWidth * localSnapIndex);
|
|
1211
1175
|
let endCoord = startCoord + snapWidth;
|
|
1212
1176
|
let left, right;
|
|
1213
|
-
if (
|
|
1177
|
+
if (isRtl) {
|
|
1214
1178
|
left = elWidth - endCoord;
|
|
1215
1179
|
right = elWidth - startCoord;
|
|
1216
1180
|
}
|
|
@@ -1230,8 +1194,7 @@ class TimelineView extends DateComponent {
|
|
|
1230
1194
|
top: 0,
|
|
1231
1195
|
bottom: elHeight,
|
|
1232
1196
|
},
|
|
1233
|
-
|
|
1234
|
-
dayEl: this.bodyEl.querySelectorAll('.fc-timeline-slot')[slatIndex],
|
|
1197
|
+
getDayEl: () => getTimelineSlotEl(this.bodyEl, slatIndex),
|
|
1235
1198
|
layer: 0,
|
|
1236
1199
|
};
|
|
1237
1200
|
}
|
|
@@ -1239,7 +1202,4 @@ class TimelineView extends DateComponent {
|
|
|
1239
1202
|
}
|
|
1240
1203
|
}
|
|
1241
1204
|
|
|
1242
|
-
|
|
1243
|
-
injectStyles(css_248z);
|
|
1244
|
-
|
|
1245
|
-
export { TimelineHeaderRow, TimelineLane, TimelineLaneBg, TimelineLaneSlicer, TimelineNowIndicatorArrow, TimelineNowIndicatorLine, TimelineSlats, TimelineView, buildTimelineDateProfile, computeSlotWidth, createHorizontalStyle, createVerticalStyle, timeToCoord };
|
|
1205
|
+
export { TimelineBg, TimelineFg, TimelineHeaderRow, TimelineLaneSlicer, TimelineNowIndicatorArrow, TimelineNowIndicatorLine, TimelineSlats, TimelineView, buildTimelineDateProfile, computeSlotWidth, getTimelineSlotEl, timeToCoord };
|