@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.
@@ -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, buildNavLinkAttrs, watchSize, 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,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, 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: 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: "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) })))));
469
508
  }
470
509
  componentDidMount() {
471
- const innerEl = this.innerElRef.current;
472
- this.disconnectInnerWidth = watchWidth(innerEl, (width) => {
473
- 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
+ }
474
518
  });
475
519
  }
476
520
  componentWillUnmount() {
477
- this.disconnectInnerWidth();
478
- setRef(this.props.innerWidthRef, null);
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 TimelineSlats extends BaseComponent {
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: check to see if changed before firing ref!? YES. do in other places too
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
- let { props, innerWidthRefMap } = this;
500
- let { tDateProfile, slotWidth } = props;
501
- let { slotDates, isWeekStarts } = tDateProfile;
502
- let isDay = !tDateProfile.isTimeScale && !tDateProfile.largeUnit;
503
- return (createElement("div", { "aria-hidden": true, className: "fc-timeline-slots fc-fill fc-flex-row", style: { height: props.height } }, slotDates.map((slotDate, i) => {
504
- let key = slotDate.toISOString();
505
- 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),
506
- // ref
507
- 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),
508
581
  // dimensions
509
- width: slotWidth }));
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 slatLiquid;
603
+ let slotLiquid;
591
604
  let slatWidth;
592
605
  if (slatTryWidth >= slatMinWidth) {
593
- slatLiquid = true;
606
+ slotLiquid = true;
594
607
  slatWidth = slatTryWidth;
595
608
  }
596
609
  else {
597
- slatLiquid = false;
610
+ slotLiquid = false;
598
611
  slatWidth = Math.max(slatMinWidth, slatTryWidth);
599
612
  }
600
- return [slatWidth * slatCnt, slatWidth, slatLiquid];
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: "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));
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 { dateProfile, tDateProfile } = props;
837
- 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...
838
- dateProfile, context.dateProfileGenerator, tDateProfile, context.dateEnv);
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(slicedProps.fgEventSegs, options.eventOrder);
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
- let forcedInvisibleMap = // TODO: more convenient/DRY
848
- (slicedProps.eventDrag ? slicedProps.eventDrag.affectedInstances : null) ||
849
- (slicedProps.eventResize ? slicedProps.eventResize.affectedInstances : null) ||
850
- {};
851
- return (createElement(Fragment, null,
852
- createElement(TimelineLaneBg, { tDateProfile: tDateProfile, nowDate: props.nowDate, todayRange: props.todayRange,
853
- // content
854
- bgEventSegs: slicedProps.bgEventSegs, businessHourSegs: slicedProps.businessHourSegs, dateSelectionSegs: slicedProps.dateSelectionSegs, eventResizeSegs: slicedProps.eventResize ? slicedProps.eventResize.segs : [] /* bad new empty array? */,
855
- // dimensions
856
- slotWidth: props.slotWidth }),
857
- createElement("div", { className: joinClassNames('fc-timeline-events', options.eventOverlap === false // TODO: fix bad default
858
- ? 'fc-timeline-events-overlap-disabled'
859
- : 'fc-timeline-events-overlap-enabled', 'fc-content-box'), style: { height: totalHeight } },
860
- this.renderFgSegs(fgSegs, fgSegHorizontals, fgSegTops, forcedInvisibleMap, hiddenGroups, hiddenGroupTops, false, // isDragging
861
- false, // isResizing
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 segHorizontal = segHorizontals[instanceId];
879
- const isVisible = isMirror ||
880
- (segHorizontal && segTop != null && !forcedInvisibleMap[instanceId]);
881
- return (createElement(TimelineEventHarness, { key: instanceId, style: Object.assign({ visibility: isVisible ? '' : 'hidden', top: segTop || 0 }, horizontalsToCss(segHorizontal, context.isRtl)), heightRef: isMirror ? undefined : segHeightRefMap.createRef(instanceId) },
882
- 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)))));
883
936
  }),
884
- hiddenGroups.map((hiddenGroup) => (createElement(TimelineEventHarness, { key: hiddenGroup.key, style: Object.assign({ top: hiddenGroupTops.get(hiddenGroup.key) || 0 }, horizontalsToCss({
885
- start: hiddenGroup.start,
886
- size: hiddenGroup.end - hiddenGroup.start
887
- }, context.isRtl)), heightRef: moreLinkHeightRefMap.createRef(hiddenGroup.key) },
888
- 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 }))))));
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
- 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 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
- render() {
998
- const { props, innerWidthRefMap, innerHeightRefMap } = this;
999
- const isCentered = !(props.tDateProfile.isTimeScale && props.isLastRow);
1000
- const isSticky = !props.isLastRow;
1001
- return (createElement("div", { className: joinClassNames('fc-flex-row fc-grow', // TODO: move fc-grow to parent?
1002
- !props.isLastRow && 'fc-border-b') }, props.cells.map((cell, cellI) => {
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.innerWidthRef, null);
1017
- setRef(this.props.innerHeighRef, null);
956
+ setRef(this.props.heightRef, null);
1018
957
  }
1019
958
  }
1020
959
 
1021
- class TimelineNowIndicatorLine extends BaseComponent {
960
+ class TimelineBg extends BaseComponent {
1022
961
  render() {
1023
- const { props, context } = this;
1024
- return (createElement("div", { className: "fc-timeline-now-indicator-container" },
1025
- createElement(NowIndicatorContainer // TODO: make separate component?
1026
- , { className: 'fc-timeline-now-indicator-line', style: props.slotWidth != null
1027
- ? horizontalCoordToCss(dateToCoord(props.nowDate, context.dateEnv, props.tDateProfile, props.slotWidth), context.isRtl)
1028
- : {}, 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')));
1029
968
  }
1030
- }
1031
-
1032
- class TimelineNowIndicatorArrow extends BaseComponent {
1033
- render() {
1034
- const { props, context } = this;
1035
- return (createElement("div", { className: "fc-timeline-now-indicator-container" },
1036
- createElement(NowIndicatorContainer, { className: 'fc-timeline-now-indicator-arrow', style: props.slotWidth != null
1037
- ? horizontalCoordToCss(dateToCoord(props.nowDate, context.dateEnv, props.tDateProfile, props.slotWidth), context.isRtl)
1038
- : {}, 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
+ })));
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 { state } = this;
1064
- const slotInnerWidth = Math.max(this.headerRowInnerWidthMap.current.get(this.tDateProfile.cellRows.length - 1) || 0, this.bodySlotInnerWidth);
1065
- if (state.slotInnerWidth !== slotInnerWidth) {
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.handleClientWidth = (clientWidth) => {
1006
+ this.handleTotalWidth = (totalWidth) => {
1070
1007
  this.setState({
1071
- clientWidth,
1008
+ totalWidth,
1072
1009
  });
1073
1010
  };
1074
- this.handleEndScrollbarWidth = (endScrollbarWidth) => {
1011
+ this.handleClientWidth = (clientWidth) => {
1075
1012
  this.setState({
1076
- endScrollbarWidth
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 = ({ isUser }) => {
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
- state.clientWidth);
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: joinClassNames('fc-timeline fc-border',
1121
- // 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
1122
1066
  // the below Scrollers if they have liquid flex height
1123
- !props.forPrint && 'fc-flex-col') },
1124
- 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 },
1125
- createElement("div", {
1126
- // TODO: DRY
1127
- className: joinClassNames('fc-rel', // origin for now-indicator
1128
- canvasWidth == null && 'fc-liquid'), style: { width: canvasWidth } },
1129
- cellRows.map((cells, rowLevel) => {
1130
- const isLast = rowLevel === cellRows.length - 1;
1131
- 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) }));
1132
- }),
1133
- enableNowIndicator && (createElement(TimelineNowIndicatorArrow, { tDateProfile: tDateProfile, nowDate: nowDate, slotWidth: slotWidth }))),
1134
- Boolean(state.endScrollbarWidth) && (createElement("div", { className: 'fc-border-s fc-filler', style: { minWidth: state.endScrollbarWidth } }))),
1135
- 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 },
1136
- createElement("div", { "aria-label": options.eventsHint, 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 },
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(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) }),
1143
1110
  enableNowIndicator && (createElement(TimelineNowIndicatorLine, { tDateProfile: tDateProfile, nowDate: nowDate, slotWidth: slotWidth })))),
1144
- stickyFooterScrollbar && (createElement(Scroller, { ref: this.footerScrollerRef, horizontal: true },
1145
- createElement("div", { style: { width: canvasWidth } })))));
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 = context.isRtl ? elWidth - positionLeft : positionLeft;
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 (context.isRtl) {
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
- // HACK. TODO: This is expensive to do every hit-query
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
- 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}";
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 };