@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.
@@ -1,6 +1,8 @@
1
- import { config, createFormatter, greatestDurationDenominator, asCleanDays, createDuration, wholeDivideDurations, asRoughMs, addDays, startOfDay, asRoughSeconds, asRoughMinutes, diffWholeDays, isInt, computeVisibleDayRange, padStart, BaseComponent, getDateMeta, ContentContainer, joinClassNames, getDayClassName, getSlotClassName, watchWidth, setRef, RefMap, afterSize, getEventKey, SegHierarchy, groupIntersectingSegs, buildEventRangeKey, BgEvent, getEventRangeMeta, renderFill, Slicer, intersectRanges, addMs, StandardEvent, MoreLinkContainer, watchHeight, memoize, sortEventSegs, memoizeObjArg, watchSize, buildNavLinkAttrs, NowIndicatorContainer, DateComponent, getIsHeightAuto, getStickyHeaderDates, getStickyFooterScrollbar, NowTimer, rangeContainsMarker, ViewContainer, Scroller, multiplyDuration, injectStyles } from '@fullcalendar/core/internal.js';
2
- import { createRef, createElement, Fragment, Component } from '@fullcalendar/core/preact.js';
3
- import { ScrollerSyncer } from '@fullcalendar/scrollgrid/internal.js';
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.slotLabelInterval,
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.slotLabelFormat;
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.isWeekStarts = buildIsWeekStarts(tDateProfile, dateEnv);
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('slotLabelInterval results in too many cells');
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('slotLabelInterval must be a multiple of slotDuration');
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 (currentRangeAs('years', dateProfile, dateEnv) > 1) {
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 (currentRangeAs('years', dateProfile, dateEnv) > 1) {
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 (currentRangeAs('years', dateProfile, dateEnv) > 1) {
301
+ if (dateEnv.diffWholeYears(currentRange.start, currentRange.end) > 1) {
300
302
  format0 = { year: 'numeric', month: 'long' }; // 'January 2014'
301
303
  }
302
- else if (currentRangeAs('months', dateProfile, dateEnv) > 1) {
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 (currentRangeAs('days', dateProfile, dateEnv) > 1) {
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
- // Compute the number of the give units in the "current" range.
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.getLargestUnit ? format.getLargestUnit() : null));
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, isWeekStart: false };
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
- // ref
447
- this.innerElRef = createRef();
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, isEm } = props;
453
- let dateMeta = getDateMeta(props.date, props.todayRange, props.nowDate, props.dateProfile);
454
- let renderProps = Object.assign(Object.assign({ date: dateEnv.toDate(props.date) }, dateMeta), { view: context.viewApi });
455
- return (createElement(ContentContainer, { tag: "div",
456
- // fc-align-start shrinks width of InnerContent
457
- // TODO: document this semantic className fc-timeline-slot-em
458
- className: joinClassNames('fc-timeline-slot', isEm && 'fc-timeline-slot-em', tDateProfile.isTimeScale && (isInt(dateEnv.countDurationsBetween(// best to do this here?
459
- tDateProfile.normalizedRange.start, props.date, tDateProfile.labelInterval)) ?
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: "slotLaneContent", customGenerator: options.slotLaneContent, classNameGenerator: options.slotLaneClassNames, didMount: options.slotLaneDidMount, willUnmount: options.slotLaneWillUnmount }, (InnerContent) => (createElement(InnerContent, { tag: "div", className: 'fc-cell-inner', elRef: this.innerElRef }))));
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 innerEl = this.innerElRef.current;
474
- this.disconnectInnerWidth = watchWidth(innerEl, (width) => {
475
- setRef(this.props.innerWidthRef, width);
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.disconnectInnerWidth();
480
- setRef(this.props.innerWidthRef, null);
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 TimelineSlats extends BaseComponent {
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: check to see if changed before firing ref!? YES. do in other places too
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
- let { props, innerWidthRefMap } = this;
502
- let { tDateProfile, slotWidth } = props;
503
- let { slotDates, isWeekStarts } = tDateProfile;
504
- let isDay = !tDateProfile.isTimeScale && !tDateProfile.largeUnit;
505
- return (createElement("div", { className: "fc-timeline-slots fc-fill fc-flex-row", style: { height: props.height } }, slotDates.map((slotDate, i) => {
506
- let key = slotDate.toISOString();
507
- return (createElement(TimelineSlatCell, { key: key, date: slotDate, dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: props.nowDate, todayRange: props.todayRange, isEm: isWeekStarts[i], isDay: isDay, borderStart: Boolean(i),
508
- // ref
509
- innerWidthRef: innerWidthRefMap.createRef(key),
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
- width: slotWidth }));
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 slatLiquid;
603
+ let slotLiquid;
593
604
  let slatWidth;
594
605
  if (slatTryWidth >= slatMinWidth) {
595
- slatLiquid = true;
606
+ slotLiquid = true;
596
607
  slatWidth = slatTryWidth;
597
608
  }
598
609
  else {
599
- slatLiquid = false;
610
+ slotLiquid = false;
600
611
  slatWidth = Math.max(slatMinWidth, slatTryWidth);
601
612
  }
602
- return [slatWidth * slatCnt, slatWidth, slatLiquid];
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: "fc-abs", style: props.style, ref: this.rootElRef }, props.children));
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 { dateProfile, tDateProfile } = props;
839
- let slicedProps = this.slicer.sliceProps(props, dateProfile, tDateProfile.isTimeScale ? null : props.nextDayThreshold, context, // wish we didn't have to pass in the rest of the args...
840
- dateProfile, context.dateProfileGenerator, tDateProfile, context.dateEnv);
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(slicedProps.fgEventSegs, options.eventOrder);
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
- let forcedInvisibleMap = // TODO: more convenient/DRY
850
- (slicedProps.eventDrag ? slicedProps.eventDrag.affectedInstances : null) ||
851
- (slicedProps.eventResize ? slicedProps.eventResize.affectedInstances : null) ||
852
- {};
853
- return (createElement(Fragment, null,
854
- createElement(TimelineLaneBg, { tDateProfile: tDateProfile, nowDate: props.nowDate, todayRange: props.todayRange,
855
- // content
856
- bgEventSegs: slicedProps.bgEventSegs, businessHourSegs: slicedProps.businessHourSegs, dateSelectionSegs: slicedProps.dateSelectionSegs, eventResizeSegs: slicedProps.eventResize ? slicedProps.eventResize.segs : [] /* bad new empty array? */,
857
- // dimensions
858
- slotWidth: props.slotWidth }),
859
- createElement("div", { className: joinClassNames('fc-timeline-events', options.eventOverlap === false // TODO: fix bad default
860
- ? 'fc-timeline-events-overlap-disabled'
861
- : 'fc-timeline-events-overlap-enabled', 'fc-content-box'), style: { height: totalHeight } },
862
- this.renderFgSegs(fgSegs, fgSegHorizontals, fgSegTops, forcedInvisibleMap, hiddenGroups, hiddenGroupTops, false, // isDragging
863
- false, // isResizing
864
- false),
865
- this.renderFgSegs(mirrorSegs, props.slotWidth // TODO: memoize
866
- ? computeManySegHorizontals(mirrorSegs, options.eventMinWidth, context.dateEnv, tDateProfile, props.slotWidth)
867
- : {}, fgSegTops, {}, // forcedInvisibleMap
868
- [], // hiddenGroups
869
- new Map(), // hiddenGroupTops
870
- Boolean(slicedProps.eventDrag), Boolean(slicedProps.eventResize), false))));
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 segHorizontal = segHorizontals[instanceId];
881
- const isVisible = isMirror ||
882
- (segHorizontal && segTop != null && !forcedInvisibleMap[instanceId]);
883
- return (createElement(TimelineEventHarness, { key: instanceId, style: Object.assign({ visibility: isVisible ? '' : 'hidden', top: segTop || 0 }, horizontalsToCss(segHorizontal, context.isRtl)), heightRef: isMirror ? undefined : segHeightRefMap.createRef(instanceId) },
884
- createElement(TimelineEvent, Object.assign({ isTimeScale: props.tDateProfile.isTimeScale, eventRange: eventRange, isStart: seg.isStart, isEnd: seg.isEnd, isDragging: isDragging, isResizing: isResizing, isDateSelecting: isDateSelecting, isSelected: instanceId === props.eventSelection /* TODO: bad for mirror? */ }, getEventRangeMeta(eventRange, props.todayRange, props.nowDate)))));
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: Object.assign({ top: hiddenGroupTops.get(hiddenGroup.key) || 0 }, horizontalsToCss({
887
- start: hiddenGroup.start,
888
- size: hiddenGroup.end - hiddenGroup.start
889
- }, context.isRtl)), heightRef: moreLinkHeightRefMap.createRef(hiddenGroup.key) },
890
- createElement(TimelineLaneMoreLink, { hiddenSegs: hiddenGroup.segs, dateProfile: props.dateProfile, nowDate: props.nowDate, todayRange: props.todayRange, isTimeScale: props.tDateProfile.isTimeScale, eventSelection: props.eventSelection, resourceId: props.resourceId, forcedInvisibleMap: forcedInvisibleMap }))))));
891
- }
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
- componentDidMount() {
935
- const { props } = this;
936
- const innerEl = this.innerElRef.current; // TODO: make dynamic with useEffect
937
- this.detachSize = watchSize(innerEl, (width, height) => {
938
- setRef(props.innerWidthRef, width);
939
- setRef(props.innerHeightRef, height);
940
- // HACK for sticky-centering
941
- innerEl.style.left = innerEl.style.right =
942
- (props.isCentered && props.isSticky)
943
- ? `calc(50% - ${width / 2}px)`
944
- : '';
945
- });
946
- }
947
- componentWillUnmount() {
948
- const { props } = this;
949
- this.detachSize();
950
- setRef(props.innerWidthRef, null);
951
- setRef(props.innerHeightRef, null);
952
- }
953
- }
954
- // Utils
955
- // -------------------------------------------------------------------------------------------------
956
- function buildCellNavLinkAttrs(context, cellDate, rowUnit) {
957
- return (rowUnit && rowUnit !== 'time')
958
- ? buildNavLinkAttrs(context, cellDate, rowUnit)
959
- : {};
960
- }
961
- function renderInnerContent(renderProps) {
962
- return renderProps.text;
963
- }
964
- function refineRenderProps(input) {
965
- return {
966
- level: input.level,
967
- date: input.dateEnv.toDate(input.dateMarker),
968
- view: input.viewApi,
969
- text: input.text,
970
- };
971
- }
972
-
973
- class TimelineHeaderRow extends BaseComponent {
974
- constructor() {
975
- super(...arguments);
976
- // refs
977
- this.innerWidthRefMap = new RefMap(() => {
978
- afterSize(this.handleInnerWidths);
979
- });
980
- this.innerHeightRefMap = new RefMap(() => {
981
- afterSize(this.handleInnerHeights);
982
- });
983
- this.handleInnerWidths = () => {
984
- const innerWidthMap = this.innerWidthRefMap.current;
985
- let max = 0;
986
- for (const innerWidth of innerWidthMap.values()) {
987
- max = Math.max(max, innerWidth);
988
- }
989
- // TODO: ensure not equal?
990
- setRef(this.props.innerWidthRef, max);
991
- };
992
- this.handleInnerHeights = () => {
993
- const innerHeightMap = this.innerHeightRefMap.current;
994
- let max = 0;
995
- for (const innerHeight of innerHeightMap.values()) {
996
- max = Math.max(max, innerHeight);
997
- }
998
- // TODO: ensure not equal?
999
- setRef(this.props.innerHeighRef, max);
1000
- };
944
+ /*
945
+ componentDidMount(): void {
946
+ // might want to do firedTotalHeight, but won't be ready on first render
1001
947
  }
1002
- render() {
1003
- const { props, innerWidthRefMap, innerHeightRefMap } = this;
1004
- const isCentered = !(props.tDateProfile.isTimeScale && props.isLastRow);
1005
- const isSticky = !props.isLastRow;
1006
- return (createElement("div", { className: joinClassNames('fc-flex-row fc-grow', // TODO: move fc-grow to parent?
1007
- !props.isLastRow && 'fc-border-b') }, props.cells.map((cell, cellI) => {
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.innerWidthRef, null);
1022
- setRef(this.props.innerHeighRef, null);
956
+ setRef(this.props.heightRef, null);
1023
957
  }
1024
958
  }
1025
959
 
1026
- class TimelineNowIndicatorLine extends BaseComponent {
960
+ class TimelineBg extends BaseComponent {
1027
961
  render() {
1028
- const { props, context } = this;
1029
- return (createElement("div", { className: "fc-timeline-now-indicator-container" },
1030
- createElement(NowIndicatorContainer // TODO: make separate component?
1031
- , { className: 'fc-timeline-now-indicator-line', style: props.slotWidth != null
1032
- ? horizontalCoordToCss(dateToCoord(props.nowDate, context.dateEnv, props.tDateProfile, props.slotWidth), context.isRtl)
1033
- : {}, isAxis: false, date: props.nowDate })));
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
- class TimelineNowIndicatorArrow extends BaseComponent {
1038
- render() {
1039
- const { props, context } = this;
1040
- return (createElement("div", { className: "fc-timeline-now-indicator-container" },
1041
- createElement(NowIndicatorContainer, { className: 'fc-timeline-now-indicator-arrow', style: props.slotWidth != null
1042
- ? horizontalCoordToCss(dateToCoord(props.nowDate, context.dateEnv, props.tDateProfile, props.slotWidth), context.isRtl)
1043
- : {}, isAxis: true, date: props.nowDate })));
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 { state } = this;
1069
- const slotInnerWidth = Math.max(this.headerRowInnerWidthMap.current.get(this.tDateProfile.cellRows.length - 1) || 0, this.bodySlotInnerWidth);
1070
- if (state.slotInnerWidth !== slotInnerWidth) {
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.handleClientWidth = (clientWidth) => {
1006
+ this.handleTotalWidth = (totalWidth) => {
1075
1007
  this.setState({
1076
- clientWidth,
1008
+ totalWidth,
1077
1009
  });
1078
1010
  };
1079
- this.handleEndScrollbarWidth = (endScrollbarWidth) => {
1011
+ this.handleClientWidth = (clientWidth) => {
1080
1012
  this.setState({
1081
- endScrollbarWidth
1013
+ clientWidth,
1082
1014
  });
1083
1015
  };
1084
- this.handleTimeScroll = (scrollTime) => {
1016
+ this.handleTimeScrollRequest = (scrollTime) => {
1085
1017
  this.scrollTime = scrollTime;
1086
- this.updateScroll();
1018
+ this.applyTimeScroll();
1087
1019
  };
1088
- this.updateScroll = () => {
1089
- const { props, context, tDateProfile, scrollTime, slotWidth } = this;
1090
- if (scrollTime != null && slotWidth != null) {
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
- state.clientWidth);
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: joinClassNames('fc-timeline fc-border',
1134
- // HACK for Safari print-mode, where fc-scroller-no-bars won't take effect for
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 && 'fc-flex-col') },
1137
- createElement(Scroller, { horizontal: true, hideScrollbars: true, className: joinClassNames('fc-timeline-header fc-flex-row fc-border-b', stickyHeaderDates && 'fc-table-header-sticky'), ref: this.headerScrollerRef },
1138
- createElement("div", {
1139
- // TODO: DRY
1140
- className: joinClassNames('fc-rel', // origin for now-indicator
1141
- canvasWidth == null && 'fc-liquid'), style: { width: canvasWidth } },
1142
- cellRows.map((cells, rowLevel) => {
1143
- const isLast = rowLevel === cellRows.length - 1;
1144
- return (createElement(TimelineHeaderRow, { key: rowLevel, dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: nowDate, todayRange: todayRange, rowLevel: rowLevel, isLastRow: isLast, cells: cells, slotWidth: slotWidth, innerWidthRef: this.headerRowInnerWidthMap.createRef(rowLevel) }));
1145
- }),
1146
- enableNowIndicator && (createElement(TimelineNowIndicatorArrow, { tDateProfile: tDateProfile, nowDate: nowDate, slotWidth: slotWidth }))),
1147
- Boolean(state.endScrollbarWidth) && (createElement("div", { className: 'fc-border-s fc-filler', style: { minWidth: state.endScrollbarWidth } }))),
1148
- createElement(Scroller, { vertical: verticalScrolling, horizontal: true, hideScrollbars: props.forPrint, className: joinClassNames('fc-timeline-body fc-flex-col', verticalScrolling && 'fc-liquid'), ref: this.bodyScrollerRef, clientWidthRef: this.handleClientWidth, endScrollbarWidthRef: this.handleEndScrollbarWidth },
1149
- createElement("div", { className: "fc-rel fc-grow", style: { width: canvasWidth }, ref: this.handeBodyEl },
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(TimelineLane, { dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: nowDate, todayRange: todayRange, nextDayThreshold: options.nextDayThreshold, eventStore: props.eventStore, eventUiBases: props.eventUiBases, businessHours: props.businessHours, dateSelection: props.dateSelection, eventDrag: props.eventDrag, eventResize: props.eventResize, eventSelection: props.eventSelection, slotWidth: slotWidth }),
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(Scroller, { ref: this.footerScrollerRef, horizontal: true },
1158
- createElement("div", { style: { width: canvasWidth } })))));
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.handleTimeScroll);
1168
- this.syncedScroller.addScrollEndListener(this.clearScroll);
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.updateScroll();
1131
+ this.applyTimeScroll();
1178
1132
  }
1179
1133
  }
1180
1134
  componentWillUnmount() {
1181
1135
  this.syncedScroller.destroy();
1182
- this.context.emitter.off('_timeScrollRequest', this.handleTimeScroll);
1183
- this.syncedScroller.removeScrollEndListener(this.clearScroll);
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.handleTimeScroll(this.context.options.scrollTime);
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 = context.isRtl ? elWidth - positionLeft : positionLeft;
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 (context.isRtl) {
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
- // HACK. TODO: This is expensive to do every hit-query
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
- var css_248z = ".fc-timeline-slots{z-index:1}.fc-timeline-events{position:relative;z-index:2}.fc-timeline-slot-minor{border-style:dotted}.fc-timeline-events-overlap-enabled{padding-bottom:10px}.fc-timeline-event{border-radius:0;font-size:var(--fc-small-font-size);margin-bottom:1px}.fc-direction-ltr .fc-timeline-event.fc-event-end{margin-right:1px}.fc-direction-rtl .fc-timeline-event.fc-event-end{margin-left:1px}.fc-timeline-event .fc-event-inner{align-items:center;display:flex;flex-direction:row;padding:2px 1px}.fc-timeline-event-spacious .fc-event-inner{padding-bottom:5px;padding-top:5px}.fc-timeline-event .fc-event-time{font-weight:700}.fc-timeline-event .fc-event-time,.fc-timeline-event .fc-event-title{padding:0 2px}.fc-timeline-event:not(.fc-event-end) .fc-event-inner:after,.fc-timeline-event:not(.fc-event-start) .fc-event-inner:before{border-color:transparent #000;border-style:solid;border-width:5px;content:\"\";flex-grow:0;flex-shrink:0;height:0;margin:0 1px;opacity:.5;width:0}.fc-direction-ltr .fc-timeline-event:not(.fc-event-start) .fc-event-inner:before,.fc-direction-rtl .fc-timeline-event:not(.fc-event-end) .fc-event-inner:after{border-left:0}.fc-direction-ltr .fc-timeline-event:not(.fc-event-end) .fc-event-inner:after,.fc-direction-rtl .fc-timeline-event:not(.fc-event-start) .fc-event-inner:before{border-right:0}.fc-timeline-more-link{align-items:flex-start;background:var(--fc-more-link-bg-color);color:var(--fc-more-link-text-color);cursor:pointer;display:flex;flex-direction:column;font-size:var(--fc-small-font-size);padding:1px}.fc-direction-ltr .fc-timeline-more-link{margin-right:1px}.fc-direction-rtl .fc-timeline-more-link{margin-left:1px}.fc-timeline-more-link-inner{padding:2px}.fc-timeline-now-indicator-container{bottom:0;left:0;overflow:hidden;pointer-events:none;position:absolute;right:0;top:0;z-index:4}.fc-timeline-now-indicator-arrow{border-bottom-style:solid;border-bottom-width:0;border-color:var(--fc-now-indicator-color);border-left:5px solid transparent;border-right:5px solid transparent;border-top-style:solid;border-top-width:6px;height:0;margin:0 -5px;position:absolute;top:0;width:0}.fc-timeline-now-indicator-line{border-left:1px solid var(--fc-now-indicator-color);bottom:0;position:absolute;top:0}";
1243
- 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 };