@fullcalendar/timeline 7.0.0-beta.4 → 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} +30 -45
- package/{internal.js → esm/internal.js} +434 -471
- package/{index.global.js → global.js} +443 -476
- 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 -1258
|
@@ -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,159 +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: Object.assign({ '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, {
|
|
464
429
|
omitTimeZoneOffset: true,
|
|
465
430
|
omitTime: !tDateProfile.isTimeScale,
|
|
466
431
|
}) }, (dateMeta.isToday ? { 'aria-current': 'date' } : {})), style: {
|
|
467
432
|
width: props.width,
|
|
468
|
-
}, 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) })))));
|
|
469
508
|
}
|
|
470
509
|
componentDidMount() {
|
|
471
|
-
const
|
|
472
|
-
this.
|
|
473
|
-
|
|
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
|
+
}
|
|
474
518
|
});
|
|
475
519
|
}
|
|
476
520
|
componentWillUnmount() {
|
|
477
|
-
this
|
|
478
|
-
|
|
521
|
+
const { props } = this;
|
|
522
|
+
this.disconnectSize();
|
|
523
|
+
setRef(props.innerWidthRef, null);
|
|
524
|
+
setRef(props.innerHeightRef, null);
|
|
479
525
|
}
|
|
480
526
|
}
|
|
527
|
+
// Utils
|
|
528
|
+
// -------------------------------------------------------------------------------------------------
|
|
529
|
+
function renderInnerContent(renderProps) {
|
|
530
|
+
return renderProps.text;
|
|
531
|
+
}
|
|
481
532
|
|
|
482
|
-
class
|
|
533
|
+
class TimelineHeaderRow extends BaseComponent {
|
|
483
534
|
constructor() {
|
|
484
535
|
super(...arguments);
|
|
536
|
+
// refs
|
|
485
537
|
this.innerWidthRefMap = new RefMap(() => {
|
|
486
538
|
afterSize(this.handleInnerWidths);
|
|
487
539
|
});
|
|
540
|
+
this.innerHeightRefMap = new RefMap(() => {
|
|
541
|
+
afterSize(this.handleInnerHeights);
|
|
542
|
+
});
|
|
488
543
|
this.handleInnerWidths = () => {
|
|
489
544
|
const innerWidthMap = this.innerWidthRefMap.current;
|
|
490
545
|
let max = 0;
|
|
491
546
|
for (const innerWidth of innerWidthMap.values()) {
|
|
492
547
|
max = Math.max(max, innerWidth);
|
|
493
548
|
}
|
|
494
|
-
// TODO:
|
|
549
|
+
// TODO: ensure not equal?
|
|
495
550
|
setRef(this.props.innerWidthRef, max);
|
|
496
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
|
+
};
|
|
497
562
|
}
|
|
498
563
|
render() {
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
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),
|
|
508
581
|
// dimensions
|
|
509
|
-
|
|
582
|
+
slotWidth: props.slotWidth }));
|
|
510
583
|
})));
|
|
511
584
|
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
TODO: rename this file!
|
|
516
|
-
*/
|
|
517
|
-
// returned value is between 0 and the number of snaps
|
|
518
|
-
function computeDateSnapCoverage$1(date, tDateProfile, dateEnv) {
|
|
519
|
-
let snapDiff = dateEnv.countDurationsBetween(tDateProfile.normalizedRange.start, date, tDateProfile.snapDuration);
|
|
520
|
-
if (snapDiff < 0) {
|
|
521
|
-
return 0;
|
|
522
|
-
}
|
|
523
|
-
if (snapDiff >= tDateProfile.snapDiffToIndex.length) {
|
|
524
|
-
return tDateProfile.snapCnt;
|
|
525
|
-
}
|
|
526
|
-
let snapDiffInt = Math.floor(snapDiff);
|
|
527
|
-
let snapCoverage = tDateProfile.snapDiffToIndex[snapDiffInt];
|
|
528
|
-
if (isInt(snapCoverage)) { // not an in-between value
|
|
529
|
-
snapCoverage += snapDiff - snapDiffInt; // add the remainder
|
|
530
|
-
}
|
|
531
|
-
else {
|
|
532
|
-
// a fractional value, meaning the date is not visible
|
|
533
|
-
// always round up in this case. works for start AND end dates in a range.
|
|
534
|
-
snapCoverage = Math.ceil(snapCoverage);
|
|
535
|
-
}
|
|
536
|
-
return snapCoverage;
|
|
537
|
-
}
|
|
538
|
-
/*
|
|
539
|
-
TODO: DRY up with elsewhere?
|
|
540
|
-
*/
|
|
541
|
-
function horizontalsToCss(hcoord, isRtl) {
|
|
542
|
-
if (!hcoord) {
|
|
543
|
-
return {};
|
|
544
|
-
}
|
|
545
|
-
if (isRtl) {
|
|
546
|
-
return { right: hcoord.start, width: hcoord.size };
|
|
547
|
-
}
|
|
548
|
-
else {
|
|
549
|
-
return { left: hcoord.start, width: hcoord.size };
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
function horizontalCoordToCss(start, isRtl) {
|
|
553
|
-
if (isRtl) {
|
|
554
|
-
return { right: start };
|
|
555
|
-
}
|
|
556
|
-
else {
|
|
557
|
-
return { left: start };
|
|
585
|
+
componentWillUnmount() {
|
|
586
|
+
setRef(this.props.innerWidthRef, null);
|
|
587
|
+
setRef(this.props.innerHeighRef, null);
|
|
558
588
|
}
|
|
559
589
|
}
|
|
560
590
|
|
|
561
|
-
function createVerticalStyle(props) {
|
|
562
|
-
if (props) {
|
|
563
|
-
return {
|
|
564
|
-
top: props.start,
|
|
565
|
-
height: props.size,
|
|
566
|
-
};
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
function createHorizontalStyle(// TODO: DRY up?
|
|
570
|
-
props, isRtl) {
|
|
571
|
-
if (props) {
|
|
572
|
-
return {
|
|
573
|
-
[isRtl ? 'right' : 'left']: props.start,
|
|
574
|
-
width: props.size,
|
|
575
|
-
};
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
591
|
// Timeline-specific
|
|
579
592
|
// -------------------------------------------------------------------------------------------------
|
|
580
593
|
const MIN_SLOT_WIDTH = 30; // for real
|
|
@@ -587,17 +600,17 @@ function computeSlotWidth(slatCnt, slatsPerLabel, slatMinWidth, labelInnerWidth,
|
|
|
587
600
|
}
|
|
588
601
|
slatMinWidth = Math.max(slatMinWidth || 0, (labelInnerWidth + 1) / slatsPerLabel, MIN_SLOT_WIDTH);
|
|
589
602
|
const slatTryWidth = viewportWidth / slatCnt;
|
|
590
|
-
let
|
|
603
|
+
let slotLiquid;
|
|
591
604
|
let slatWidth;
|
|
592
605
|
if (slatTryWidth >= slatMinWidth) {
|
|
593
|
-
|
|
606
|
+
slotLiquid = true;
|
|
594
607
|
slatWidth = slatTryWidth;
|
|
595
608
|
}
|
|
596
609
|
else {
|
|
597
|
-
|
|
610
|
+
slotLiquid = false;
|
|
598
611
|
slatWidth = Math.max(slatMinWidth, slatTryWidth);
|
|
599
612
|
}
|
|
600
|
-
return [slatWidth * slatCnt, slatWidth,
|
|
613
|
+
return [slatWidth * slatCnt, slatWidth, slotLiquid];
|
|
601
614
|
}
|
|
602
615
|
function timeToCoord(// pixels
|
|
603
616
|
time, dateEnv, dateProfile, tDateProfile, slowWidth) {
|
|
@@ -609,13 +622,86 @@ time, dateEnv, dateProfile, tDateProfile, slowWidth) {
|
|
|
609
622
|
}
|
|
610
623
|
function dateToCoord(// pixels
|
|
611
624
|
date, dateEnv, tDateProfile, slotWidth) {
|
|
612
|
-
let snapCoverage = computeDateSnapCoverage(date, tDateProfile, dateEnv);
|
|
625
|
+
let snapCoverage = computeDateSnapCoverage$1(date, tDateProfile, dateEnv);
|
|
613
626
|
let slotCoverage = snapCoverage / tDateProfile.snapsPerSlot;
|
|
614
627
|
return slotCoverage * slotWidth;
|
|
615
628
|
}
|
|
616
629
|
/*
|
|
617
630
|
returned value is between 0 and the number of snaps
|
|
618
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
|
|
619
705
|
function computeDateSnapCoverage(date, tDateProfile, dateEnv) {
|
|
620
706
|
let snapDiff = dateEnv.countDurationsBetween(tDateProfile.normalizedRange.start, date, tDateProfile.snapDuration);
|
|
621
707
|
if (snapDiff < 0) {
|
|
@@ -637,6 +723,60 @@ function computeDateSnapCoverage(date, tDateProfile, dateEnv) {
|
|
|
637
723
|
return snapCoverage;
|
|
638
724
|
}
|
|
639
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
|
+
|
|
640
780
|
function computeManySegHorizontals(segs, segMinWidth, dateEnv, tDateProfile, slotWidth) {
|
|
641
781
|
const res = {};
|
|
642
782
|
for (const seg of segs) {
|
|
@@ -701,83 +841,6 @@ hiddenGroupHeights, strictOrder, maxDepth) {
|
|
|
701
841
|
];
|
|
702
842
|
}
|
|
703
843
|
|
|
704
|
-
class TimelineLaneBg extends BaseComponent {
|
|
705
|
-
render() {
|
|
706
|
-
let { props } = this;
|
|
707
|
-
let highlightSeg = [].concat(props.eventResizeSegs, props.dateSelectionSegs);
|
|
708
|
-
return (createElement(Fragment, null,
|
|
709
|
-
this.renderSegs(props.businessHourSegs || [], 'non-business'),
|
|
710
|
-
this.renderSegs(props.bgEventSegs || [], 'bg-event'),
|
|
711
|
-
this.renderSegs(highlightSeg, 'highlight')));
|
|
712
|
-
}
|
|
713
|
-
renderSegs(segs, fillType) {
|
|
714
|
-
let { tDateProfile, todayRange, nowDate, slotWidth } = this.props;
|
|
715
|
-
let { dateEnv, isRtl } = this.context;
|
|
716
|
-
return (createElement(Fragment, null, segs.map((seg) => {
|
|
717
|
-
let hStyle; // TODO
|
|
718
|
-
if (slotWidth != null) {
|
|
719
|
-
let segHorizontal = computeSegHorizontals(seg, undefined, dateEnv, tDateProfile, slotWidth);
|
|
720
|
-
hStyle = horizontalsToCss(segHorizontal, isRtl);
|
|
721
|
-
}
|
|
722
|
-
return (createElement("div", { key: buildEventRangeKey(seg.eventRange), className: "fc-fill-y", style: hStyle }, fillType === 'bg-event' ?
|
|
723
|
-
createElement(BgEvent, Object.assign({ eventRange: seg.eventRange, isStart: seg.isStart, isEnd: seg.isEnd }, getEventRangeMeta(seg.eventRange, todayRange, nowDate))) : (renderFill(fillType))));
|
|
724
|
-
})));
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
class TimelineLaneSlicer extends Slicer {
|
|
729
|
-
sliceRange(origRange, dateProfile, dateProfileGenerator, tDateProfile, dateEnv) {
|
|
730
|
-
let normalRange = normalizeRange(origRange, tDateProfile, dateEnv);
|
|
731
|
-
let segs = [];
|
|
732
|
-
// protect against when the span is entirely in an invalid date region
|
|
733
|
-
if (computeDateSnapCoverage$1(normalRange.start, tDateProfile, dateEnv)
|
|
734
|
-
< computeDateSnapCoverage$1(normalRange.end, tDateProfile, dateEnv)) {
|
|
735
|
-
// intersect the footprint's range with the grid's range
|
|
736
|
-
let slicedRange = intersectRanges(normalRange, tDateProfile.normalizedRange);
|
|
737
|
-
if (slicedRange) {
|
|
738
|
-
segs.push({
|
|
739
|
-
startDate: slicedRange.start,
|
|
740
|
-
endDate: slicedRange.end,
|
|
741
|
-
isStart: slicedRange.start.valueOf() === normalRange.start.valueOf()
|
|
742
|
-
&& isValidDate(slicedRange.start, tDateProfile, dateProfile, dateProfileGenerator),
|
|
743
|
-
isEnd: slicedRange.end.valueOf() === normalRange.end.valueOf()
|
|
744
|
-
&& isValidDate(addMs(slicedRange.end, -1), tDateProfile, dateProfile, dateProfileGenerator),
|
|
745
|
-
});
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
return segs;
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
const DEFAULT_TIME_FORMAT = createFormatter({
|
|
753
|
-
hour: 'numeric',
|
|
754
|
-
minute: '2-digit',
|
|
755
|
-
omitZeroMinute: true,
|
|
756
|
-
meridiem: 'narrow',
|
|
757
|
-
});
|
|
758
|
-
class TimelineEvent extends BaseComponent {
|
|
759
|
-
render() {
|
|
760
|
-
let { props, context } = this;
|
|
761
|
-
let { options } = context;
|
|
762
|
-
return (createElement(StandardEvent, Object.assign({}, props, { className: joinClassNames('fc-timeline-event', options.eventOverlap === false // TODO: fix bad default
|
|
763
|
-
&& 'fc-timeline-event-spacious', 'fc-h-event'), defaultTimeFormat: DEFAULT_TIME_FORMAT, defaultDisplayEventTime: !props.isTimeScale })));
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
class TimelineLaneMoreLink extends BaseComponent {
|
|
768
|
-
render() {
|
|
769
|
-
let { props } = this;
|
|
770
|
-
let { hiddenSegs, resourceId, forcedInvisibleMap } = props;
|
|
771
|
-
let dateSpanProps = resourceId ? { resourceId } : {};
|
|
772
|
-
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) => {
|
|
773
|
-
let { eventRange } = seg;
|
|
774
|
-
let instanceId = eventRange.instance.instanceId;
|
|
775
|
-
return (createElement("div", { key: instanceId, style: { visibility: forcedInvisibleMap[instanceId] ? 'hidden' : '' } },
|
|
776
|
-
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)))));
|
|
777
|
-
}))) }, (InnerContent) => (createElement(InnerContent, { tag: "div", className: 'fc-timeline-more-link-inner fc-sticky-s' }))));
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
|
|
781
844
|
/*
|
|
782
845
|
TODO: make DRY with other Event Harnesses
|
|
783
846
|
*/
|
|
@@ -789,7 +852,7 @@ class TimelineEventHarness extends Component {
|
|
|
789
852
|
}
|
|
790
853
|
render() {
|
|
791
854
|
const { props } = this;
|
|
792
|
-
return (createElement("div", { className:
|
|
855
|
+
return (createElement("div", { className: classNames.abs, style: props.style, ref: this.rootElRef }, props.children));
|
|
793
856
|
}
|
|
794
857
|
componentDidMount() {
|
|
795
858
|
const rootEl = this.rootElRef.current; // TODO: make dynamic with useEffect
|
|
@@ -803,10 +866,7 @@ class TimelineEventHarness extends Component {
|
|
|
803
866
|
}
|
|
804
867
|
}
|
|
805
868
|
|
|
806
|
-
|
|
807
|
-
TODO: split TimelineLaneBg and TimelineLaneFg?
|
|
808
|
-
*/
|
|
809
|
-
class TimelineLane extends BaseComponent {
|
|
869
|
+
class TimelineFg extends BaseComponent {
|
|
810
870
|
constructor() {
|
|
811
871
|
super(...arguments);
|
|
812
872
|
// memo
|
|
@@ -818,8 +878,6 @@ class TimelineLane extends BaseComponent {
|
|
|
818
878
|
this.moreLinkHeightRefMap = new RefMap(() => {
|
|
819
879
|
afterSize(this.handleMoreLinkHeights);
|
|
820
880
|
});
|
|
821
|
-
// internal
|
|
822
|
-
this.slicer = new TimelineLaneSlicer();
|
|
823
881
|
this.handleMoreLinkHeights = () => {
|
|
824
882
|
this.setState({ moreLinkHeightRev: this.moreLinkHeightRefMap.rev }); // will trigger rerender
|
|
825
883
|
};
|
|
@@ -833,209 +891,92 @@ class TimelineLane extends BaseComponent {
|
|
|
833
891
|
render() {
|
|
834
892
|
let { props, context, segHeightRefMap, moreLinkHeightRefMap } = this;
|
|
835
893
|
let { options } = context;
|
|
836
|
-
let {
|
|
837
|
-
let
|
|
838
|
-
|
|
839
|
-
let mirrorSegs = (slicedProps.eventDrag ? slicedProps.eventDrag.segs : null) ||
|
|
840
|
-
(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) ||
|
|
841
897
|
[];
|
|
842
|
-
let fgSegs = this.sortEventSegs(
|
|
898
|
+
let fgSegs = this.sortEventSegs(props.fgEventSegs, options.eventOrder);
|
|
843
899
|
let fgSegHorizontals = props.slotWidth != null
|
|
844
900
|
? computeManySegHorizontals(fgSegs, options.eventMinWidth, context.dateEnv, tDateProfile, props.slotWidth)
|
|
845
901
|
: {};
|
|
846
902
|
let [fgSegTops, hiddenGroups, hiddenGroupTops, totalHeight] = computeFgSegPlacements(fgSegs, fgSegHorizontals, segHeightRefMap.current, moreLinkHeightRefMap.current, options.eventOrderStrict, options.eventMaxStack);
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
false),
|
|
863
|
-
this.renderFgSegs(mirrorSegs, props.slotWidth // TODO: memoize
|
|
864
|
-
? computeManySegHorizontals(mirrorSegs, options.eventMinWidth, context.dateEnv, tDateProfile, props.slotWidth)
|
|
865
|
-
: {}, fgSegTops, {}, // forcedInvisibleMap
|
|
866
|
-
[], // hiddenGroups
|
|
867
|
-
new Map(), // hiddenGroupTops
|
|
868
|
-
Boolean(slicedProps.eventDrag), Boolean(slicedProps.eventResize), false))));
|
|
869
|
-
}
|
|
870
|
-
renderFgSegs(segs, segHorizontals, segTops, forcedInvisibleMap, hiddenGroups, hiddenGroupTops, isDragging, isResizing, isDateSelecting) {
|
|
871
|
-
let { props, context, segHeightRefMap, moreLinkHeightRefMap } = this;
|
|
872
|
-
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;
|
|
873
918
|
return (createElement(Fragment, null,
|
|
874
919
|
segs.map((seg) => {
|
|
875
920
|
const { eventRange } = seg;
|
|
876
921
|
const { instanceId } = eventRange.instance;
|
|
877
922
|
const segTop = segTops.get(instanceId);
|
|
878
|
-
const
|
|
879
|
-
const
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
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)))));
|
|
883
936
|
}),
|
|
884
|
-
hiddenGroups.map((hiddenGroup) => (createElement(TimelineEventHarness, { key: hiddenGroup.key, style:
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
class TimelineHeaderCell extends BaseComponent {
|
|
893
|
-
constructor() {
|
|
894
|
-
super(...arguments);
|
|
895
|
-
// memo
|
|
896
|
-
this.refineRenderProps = memoizeObjArg(refineRenderProps);
|
|
897
|
-
// ref
|
|
898
|
-
this.innerElRef = createRef();
|
|
899
|
-
}
|
|
900
|
-
render() {
|
|
901
|
-
let { props, context } = this;
|
|
902
|
-
let { dateEnv, options } = context;
|
|
903
|
-
let { cell, dateProfile, tDateProfile } = props;
|
|
904
|
-
// the cell.rowUnit is f'd
|
|
905
|
-
// giving 'month' for a 3-day view
|
|
906
|
-
// workaround: to infer day, do NOT time
|
|
907
|
-
let dateMeta = getDateMeta(cell.date, props.todayRange, props.nowDate, dateProfile);
|
|
908
|
-
let renderProps = this.refineRenderProps({
|
|
909
|
-
level: props.rowLevel,
|
|
910
|
-
dateMarker: cell.date,
|
|
911
|
-
text: cell.text,
|
|
912
|
-
dateEnv: context.dateEnv,
|
|
913
|
-
viewApi: context.viewApi,
|
|
914
|
-
});
|
|
915
|
-
let isNavLink = !dateMeta.isDisabled && (cell.rowUnit && cell.rowUnit !== 'time');
|
|
916
|
-
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
|
|
917
|
-
'fc-header-cell fc-cell fc-flex-col fc-justify-center', props.borderStart && 'fc-border-s', props.isCentered ? 'fc-align-center' : 'fc-align-start',
|
|
918
|
-
// TODO: so slot classnames for week/month/bigger. see note above about rowUnit
|
|
919
|
-
cell.rowUnit === 'time' ?
|
|
920
|
-
getSlotClassName(dateMeta) :
|
|
921
|
-
getDayClassName(dateMeta)), attrs: Object.assign({ 'data-date': dateEnv.formatIso(cell.date, {
|
|
922
|
-
omitTime: !tDateProfile.isTimeScale,
|
|
923
|
-
omitTimeZoneOffset: true,
|
|
924
|
-
}) }, (dateMeta.isToday ? { 'aria-current': 'date' } : {})), style: {
|
|
925
|
-
width: props.slotWidth != null
|
|
926
|
-
? props.slotWidth * cell.colspan
|
|
927
|
-
: undefined,
|
|
928
|
-
}, 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
|
|
929
|
-
// not tabbable because parent is aria-hidden
|
|
930
|
-
? buildNavLinkAttrs(context, cell.date, cell.rowUnit, undefined, /* isTabbable = */ false)
|
|
931
|
-
: {} // don't bother with aria-hidden because parent already hidden
|
|
932
|
-
, 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 renderInnerContent(renderProps) {
|
|
957
|
-
return renderProps.text;
|
|
958
|
-
}
|
|
959
|
-
function refineRenderProps(input) {
|
|
960
|
-
return {
|
|
961
|
-
level: input.level,
|
|
962
|
-
date: input.dateEnv.toDate(input.dateMarker),
|
|
963
|
-
view: input.viewApi,
|
|
964
|
-
text: input.text,
|
|
965
|
-
};
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
class TimelineHeaderRow extends BaseComponent {
|
|
969
|
-
constructor() {
|
|
970
|
-
super(...arguments);
|
|
971
|
-
// refs
|
|
972
|
-
this.innerWidthRefMap = new RefMap(() => {
|
|
973
|
-
afterSize(this.handleInnerWidths);
|
|
974
|
-
});
|
|
975
|
-
this.innerHeightRefMap = new RefMap(() => {
|
|
976
|
-
afterSize(this.handleInnerHeights);
|
|
977
|
-
});
|
|
978
|
-
this.handleInnerWidths = () => {
|
|
979
|
-
const innerWidthMap = this.innerWidthRefMap.current;
|
|
980
|
-
let max = 0;
|
|
981
|
-
for (const innerWidth of innerWidthMap.values()) {
|
|
982
|
-
max = Math.max(max, innerWidth);
|
|
983
|
-
}
|
|
984
|
-
// TODO: ensure not equal?
|
|
985
|
-
setRef(this.props.innerWidthRef, max);
|
|
986
|
-
};
|
|
987
|
-
this.handleInnerHeights = () => {
|
|
988
|
-
const innerHeightMap = this.innerHeightRefMap.current;
|
|
989
|
-
let max = 0;
|
|
990
|
-
for (const innerHeight of innerHeightMap.values()) {
|
|
991
|
-
max = Math.max(max, innerHeight);
|
|
992
|
-
}
|
|
993
|
-
// TODO: ensure not equal?
|
|
994
|
-
setRef(this.props.innerHeighRef, max);
|
|
995
|
-
};
|
|
944
|
+
/*
|
|
945
|
+
componentDidMount(): void {
|
|
946
|
+
// might want to do firedTotalHeight, but won't be ready on first render
|
|
996
947
|
}
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
// TODO: make this part of the cell obj?
|
|
1004
|
-
// TODO: rowUnit seems wrong sometimes. says 'month' when it should be day
|
|
1005
|
-
// TODO: rowUnit is relevant to whole row. put it on a row object, not the cells
|
|
1006
|
-
// TODO: use rowUnit to key the Row itself?
|
|
1007
|
-
const key = cell.rowUnit + ':' + cell.date.toISOString();
|
|
1008
|
-
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),
|
|
1009
|
-
// refs
|
|
1010
|
-
innerWidthRef: innerWidthRefMap.createRef(key), innerHeightRef: innerHeightRefMap.createRef(key),
|
|
1011
|
-
// dimensions
|
|
1012
|
-
slotWidth: props.slotWidth }));
|
|
1013
|
-
})));
|
|
948
|
+
*/
|
|
949
|
+
componentDidUpdate() {
|
|
950
|
+
if (this.totalHeight !== this.firedTotalHeight) {
|
|
951
|
+
this.firedTotalHeight = this.totalHeight;
|
|
952
|
+
setRef(this.props.heightRef, this.totalHeight);
|
|
953
|
+
}
|
|
1014
954
|
}
|
|
1015
955
|
componentWillUnmount() {
|
|
1016
|
-
setRef(this.props.
|
|
1017
|
-
setRef(this.props.innerHeighRef, null);
|
|
956
|
+
setRef(this.props.heightRef, null);
|
|
1018
957
|
}
|
|
1019
958
|
}
|
|
1020
959
|
|
|
1021
|
-
class
|
|
960
|
+
class TimelineBg extends BaseComponent {
|
|
1022
961
|
render() {
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
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')));
|
|
1029
968
|
}
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
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
|
+
})));
|
|
1039
980
|
}
|
|
1040
981
|
}
|
|
1041
982
|
|
|
@@ -1053,34 +994,30 @@ class TimelineView extends DateComponent {
|
|
|
1053
994
|
afterSize(this.handleSlotInnerWidths);
|
|
1054
995
|
});
|
|
1055
996
|
this.scrollTime = null;
|
|
997
|
+
this.slicer = new TimelineLaneSlicer();
|
|
1056
998
|
// Sizing
|
|
1057
999
|
// -----------------------------------------------------------------------------------------------
|
|
1058
|
-
this.handleBodySlotInnerWidth = (innerWidth) => {
|
|
1059
|
-
this.bodySlotInnerWidth = innerWidth;
|
|
1060
|
-
afterSize(this.handleSlotInnerWidths);
|
|
1061
|
-
};
|
|
1062
1000
|
this.handleSlotInnerWidths = () => {
|
|
1063
|
-
const
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
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 });
|
|
1067
1004
|
}
|
|
1068
1005
|
};
|
|
1069
|
-
this.
|
|
1006
|
+
this.handleTotalWidth = (totalWidth) => {
|
|
1070
1007
|
this.setState({
|
|
1071
|
-
|
|
1008
|
+
totalWidth,
|
|
1072
1009
|
});
|
|
1073
1010
|
};
|
|
1074
|
-
this.
|
|
1011
|
+
this.handleClientWidth = (clientWidth) => {
|
|
1075
1012
|
this.setState({
|
|
1076
|
-
|
|
1013
|
+
clientWidth,
|
|
1077
1014
|
});
|
|
1078
1015
|
};
|
|
1079
1016
|
this.handleTimeScrollRequest = (scrollTime) => {
|
|
1080
1017
|
this.scrollTime = scrollTime;
|
|
1081
1018
|
this.applyTimeScroll();
|
|
1082
1019
|
};
|
|
1083
|
-
this.handleTimeScrollEnd = (
|
|
1020
|
+
this.handleTimeScrollEnd = (isUser) => {
|
|
1084
1021
|
if (isUser) {
|
|
1085
1022
|
this.scrollTime = null;
|
|
1086
1023
|
}
|
|
@@ -1100,6 +1037,10 @@ class TimelineView extends DateComponent {
|
|
|
1100
1037
|
render() {
|
|
1101
1038
|
const { props, state, context } = this;
|
|
1102
1039
|
const { options } = context;
|
|
1040
|
+
const { totalWidth, clientWidth } = state;
|
|
1041
|
+
const endScrollbarWidth = (totalWidth != null && clientWidth != null)
|
|
1042
|
+
? totalWidth - clientWidth
|
|
1043
|
+
: undefined;
|
|
1103
1044
|
/* date */
|
|
1104
1045
|
const tDateProfile = this.tDateProfile = this.buildTimelineDateProfile(props.dateProfile, context.dateEnv, options, context.dateProfileGenerator);
|
|
1105
1046
|
const { cellRows } = tDateProfile;
|
|
@@ -1110,39 +1051,65 @@ class TimelineView extends DateComponent {
|
|
|
1110
1051
|
const stickyFooterScrollbar = !props.forPrint && getStickyFooterScrollbar(options);
|
|
1111
1052
|
/* table positions */
|
|
1112
1053
|
const [canvasWidth, slotWidth] = this.computeSlotWidth(tDateProfile.slotCnt, tDateProfile.slotsPerLabel, options.slotMinWidth, state.slotInnerWidth, // is ACTUALLY the label width. rename?
|
|
1113
|
-
|
|
1054
|
+
clientWidth);
|
|
1114
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);
|
|
1115
1059
|
return (createElement(NowTimer, { unit: timerUnit }, (nowDate, todayRange) => {
|
|
1116
1060
|
const enableNowIndicator = // TODO: DRY
|
|
1117
1061
|
options.nowIndicator &&
|
|
1118
1062
|
slotWidth != null &&
|
|
1119
1063
|
rangeContainsMarker(props.dateProfile.currentRange, nowDate);
|
|
1120
|
-
return (createElement(ViewContainer, { viewSpec: context.viewSpec, className:
|
|
1121
|
-
// 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
|
|
1122
1066
|
// the below Scrollers if they have liquid flex height
|
|
1123
|
-
!props.forPrint &&
|
|
1124
|
-
createElement(
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
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 },
|
|
1137
1095
|
createElement(TimelineSlats, { dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: nowDate, todayRange: todayRange,
|
|
1138
|
-
// ref
|
|
1139
|
-
innerWidthRef: this.handleBodySlotInnerWidth,
|
|
1140
1096
|
// dimensions
|
|
1141
1097
|
slotWidth: slotWidth }),
|
|
1142
|
-
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) }),
|
|
1143
1110
|
enableNowIndicator && (createElement(TimelineNowIndicatorLine, { tDateProfile: tDateProfile, nowDate: nowDate, slotWidth: slotWidth })))),
|
|
1144
|
-
stickyFooterScrollbar && (createElement(
|
|
1145
|
-
|
|
1111
|
+
Boolean(stickyFooterScrollbar) && (createElement(FooterScrollbar, { isSticky: true, canvasWidth: canvasWidth, scrollerRef: this.footerScrollerRef })),
|
|
1112
|
+
createElement(Ruler, { widthRef: this.handleTotalWidth })));
|
|
1146
1113
|
}));
|
|
1147
1114
|
}
|
|
1148
1115
|
// Lifecycle
|
|
@@ -1191,11 +1158,11 @@ class TimelineView extends DateComponent {
|
|
|
1191
1158
|
this.syncedScroller.scrollTo({ x });
|
|
1192
1159
|
}
|
|
1193
1160
|
}
|
|
1194
|
-
queryHit(positionLeft, positionTop, elWidth, elHeight) {
|
|
1161
|
+
queryHit(isRtl, positionLeft, positionTop, elWidth, elHeight) {
|
|
1195
1162
|
const { props, context, tDateProfile, slotWidth } = this;
|
|
1196
1163
|
const { dateEnv } = context;
|
|
1197
1164
|
if (slotWidth) {
|
|
1198
|
-
const x =
|
|
1165
|
+
const x = isRtl ? elWidth - positionLeft : positionLeft;
|
|
1199
1166
|
const slatIndex = Math.floor(x / slotWidth);
|
|
1200
1167
|
const slatX = slatIndex * slotWidth;
|
|
1201
1168
|
const partial = (x - slatX) / slotWidth; // floating point number between 0 and 1
|
|
@@ -1207,7 +1174,7 @@ class TimelineView extends DateComponent {
|
|
|
1207
1174
|
let startCoord = slatIndex * slotWidth + (snapWidth * localSnapIndex);
|
|
1208
1175
|
let endCoord = startCoord + snapWidth;
|
|
1209
1176
|
let left, right;
|
|
1210
|
-
if (
|
|
1177
|
+
if (isRtl) {
|
|
1211
1178
|
left = elWidth - endCoord;
|
|
1212
1179
|
right = elWidth - startCoord;
|
|
1213
1180
|
}
|
|
@@ -1227,8 +1194,7 @@ class TimelineView extends DateComponent {
|
|
|
1227
1194
|
top: 0,
|
|
1228
1195
|
bottom: elHeight,
|
|
1229
1196
|
},
|
|
1230
|
-
|
|
1231
|
-
dayEl: this.bodyEl.querySelectorAll('.fc-timeline-slot')[slatIndex],
|
|
1197
|
+
getDayEl: () => getTimelineSlotEl(this.bodyEl, slatIndex),
|
|
1232
1198
|
layer: 0,
|
|
1233
1199
|
};
|
|
1234
1200
|
}
|
|
@@ -1236,7 +1202,4 @@ class TimelineView extends DateComponent {
|
|
|
1236
1202
|
}
|
|
1237
1203
|
}
|
|
1238
1204
|
|
|
1239
|
-
|
|
1240
|
-
injectStyles(css_248z);
|
|
1241
|
-
|
|
1242
|
-
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 };
|