@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.
@@ -0,0 +1,1224 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var core = require('@fullcalendar/core');
6
+ var internal = require('@fullcalendar/core/internal');
7
+ var classNames = require('@fullcalendar/core/internal-classnames');
8
+ var preact = require('@fullcalendar/core/preact');
9
+ var internal$1 = require('@fullcalendar/scrollgrid/internal');
10
+
11
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
12
+
13
+ var classNames__default = /*#__PURE__*/_interopDefaultLegacy(classNames);
14
+
15
+ const MIN_AUTO_LABELS = 18; // more than `12` months but less that `24` hours
16
+ const MAX_AUTO_SLOTS_PER_LABEL = 6; // allows 6 10-min slots in an hour
17
+ const MAX_AUTO_CELLS = 200; // allows 4-days to have a :30 slot duration
18
+ internal.config.MAX_TIMELINE_SLOTS = 1000;
19
+ // potential nice values for slot-duration and interval-duration
20
+ const STOCK_SUB_DURATIONS = [
21
+ { years: 1 },
22
+ { months: 1 },
23
+ { days: 1 },
24
+ { hours: 1 },
25
+ { minutes: 30 },
26
+ { minutes: 15 },
27
+ { minutes: 10 },
28
+ { minutes: 5 },
29
+ { minutes: 1 },
30
+ { seconds: 30 },
31
+ { seconds: 15 },
32
+ { seconds: 10 },
33
+ { seconds: 5 },
34
+ { seconds: 1 },
35
+ { milliseconds: 500 },
36
+ { milliseconds: 100 },
37
+ { milliseconds: 10 },
38
+ { milliseconds: 1 },
39
+ ];
40
+ function buildTimelineDateProfile(dateProfile, dateEnv, allOptions, dateProfileGenerator) {
41
+ let tDateProfile = {
42
+ labelInterval: allOptions.slotHeaderInterval,
43
+ slotDuration: allOptions.slotDuration,
44
+ };
45
+ validateLabelAndSlot(tDateProfile, dateProfile, dateEnv); // validate after computed grid duration
46
+ ensureLabelInterval(tDateProfile, dateProfile, dateEnv);
47
+ ensureSlotDuration(tDateProfile, dateProfile, dateEnv);
48
+ let input = allOptions.slotHeaderFormat;
49
+ let rawFormats = Array.isArray(input) ? input :
50
+ (input != null) ? [input] :
51
+ computeHeaderFormats(tDateProfile, dateProfile, dateEnv, allOptions);
52
+ tDateProfile.headerFormats = rawFormats.map((rawFormat) => internal.createFormatter(rawFormat));
53
+ tDateProfile.isTimeScale = Boolean(tDateProfile.slotDuration.milliseconds);
54
+ let largeUnit = null;
55
+ if (!tDateProfile.isTimeScale) {
56
+ const slotUnit = internal.greatestDurationDenominator(tDateProfile.slotDuration).unit;
57
+ if (/year|month|week/.test(slotUnit)) {
58
+ largeUnit = slotUnit;
59
+ }
60
+ }
61
+ tDateProfile.largeUnit = largeUnit;
62
+ /*
63
+ console.log('label interval =', timelineView.labelInterval.humanize())
64
+ console.log('slot duration =', timelineView.slotDuration.humanize())
65
+ console.log('header formats =', timelineView.headerFormats)
66
+ console.log('isTimeScale', timelineView.isTimeScale)
67
+ console.log('largeUnit', timelineView.largeUnit)
68
+ */
69
+ let rawSnapDuration = allOptions.snapDuration;
70
+ let snapDuration;
71
+ let snapsPerSlot;
72
+ if (rawSnapDuration) {
73
+ snapDuration = internal.createDuration(rawSnapDuration);
74
+ snapsPerSlot = internal.wholeDivideDurations(tDateProfile.slotDuration, snapDuration);
75
+ // ^ TODO: warning if not whole?
76
+ }
77
+ if (snapsPerSlot == null) {
78
+ snapDuration = tDateProfile.slotDuration;
79
+ snapsPerSlot = 1;
80
+ }
81
+ tDateProfile.snapDuration = snapDuration;
82
+ tDateProfile.snapsPerSlot = snapsPerSlot;
83
+ // more...
84
+ let timeWindowMs = internal.asRoughMs(dateProfile.slotMaxTime) - internal.asRoughMs(dateProfile.slotMinTime);
85
+ // TODO: why not use normalizeRange!?
86
+ let normalizedStart = normalizeDate(dateProfile.renderRange.start, tDateProfile, dateEnv);
87
+ let normalizedEnd = normalizeDate(dateProfile.renderRange.end, tDateProfile, dateEnv);
88
+ // apply slotMinTime/slotMaxTime
89
+ // TODO: View should be responsible.
90
+ if (tDateProfile.isTimeScale) {
91
+ normalizedStart = dateEnv.add(normalizedStart, dateProfile.slotMinTime);
92
+ normalizedEnd = dateEnv.add(internal.addDays(normalizedEnd, -1), dateProfile.slotMaxTime);
93
+ }
94
+ tDateProfile.timeWindowMs = timeWindowMs;
95
+ tDateProfile.normalizedRange = { start: normalizedStart, end: normalizedEnd };
96
+ let slotDates = [];
97
+ let slotDatesMajor = [];
98
+ let date = normalizedStart;
99
+ let majorUnit = internal.computeMajorUnit(dateProfile, dateEnv);
100
+ while (date < normalizedEnd) {
101
+ if (isValidDate(date, tDateProfile, dateProfile, dateProfileGenerator)) {
102
+ slotDates.push(date);
103
+ slotDatesMajor.push(internal.isMajorUnit(date, majorUnit, dateEnv));
104
+ }
105
+ date = dateEnv.add(date, tDateProfile.slotDuration);
106
+ }
107
+ tDateProfile.slotDates = slotDates;
108
+ tDateProfile.slotDatesMajor = slotDatesMajor;
109
+ // more...
110
+ let snapIndex = -1;
111
+ let snapDiff = 0; // index of the diff :(
112
+ const snapDiffToIndex = [];
113
+ const snapIndexToDiff = [];
114
+ date = normalizedStart;
115
+ while (date < normalizedEnd) {
116
+ if (isValidDate(date, tDateProfile, dateProfile, dateProfileGenerator)) {
117
+ snapIndex += 1;
118
+ snapDiffToIndex.push(snapIndex);
119
+ snapIndexToDiff.push(snapDiff);
120
+ }
121
+ else {
122
+ snapDiffToIndex.push(snapIndex + 0.5);
123
+ }
124
+ date = dateEnv.add(date, tDateProfile.snapDuration);
125
+ snapDiff += 1;
126
+ }
127
+ tDateProfile.snapDiffToIndex = snapDiffToIndex;
128
+ tDateProfile.snapIndexToDiff = snapIndexToDiff;
129
+ tDateProfile.snapCnt = snapIndex + 1; // is always one behind
130
+ tDateProfile.slotCnt = tDateProfile.snapCnt / tDateProfile.snapsPerSlot;
131
+ // more...
132
+ tDateProfile.cellRows = buildCellRows(tDateProfile, dateEnv, majorUnit);
133
+ tDateProfile.slotsPerLabel = internal.wholeDivideDurations(tDateProfile.labelInterval, tDateProfile.slotDuration);
134
+ return tDateProfile;
135
+ }
136
+ /*
137
+ snaps to appropriate unit
138
+ */
139
+ function normalizeDate(date, tDateProfile, dateEnv) {
140
+ let normalDate = date;
141
+ if (!tDateProfile.isTimeScale) {
142
+ normalDate = internal.startOfDay(normalDate);
143
+ if (tDateProfile.largeUnit) {
144
+ normalDate = dateEnv.startOf(normalDate, tDateProfile.largeUnit);
145
+ }
146
+ }
147
+ return normalDate;
148
+ }
149
+ /*
150
+ snaps to appropriate unit
151
+ */
152
+ function normalizeRange(range, tDateProfile, dateEnv) {
153
+ if (!tDateProfile.isTimeScale) {
154
+ range = internal.computeVisibleDayRange(range);
155
+ if (tDateProfile.largeUnit) {
156
+ let dayRange = range; // preserve original result
157
+ range = {
158
+ start: dateEnv.startOf(range.start, tDateProfile.largeUnit),
159
+ end: dateEnv.startOf(range.end, tDateProfile.largeUnit),
160
+ };
161
+ // if date is partially through the interval, or is in the same interval as the start,
162
+ // make the exclusive end be the *next* interval
163
+ if (range.end.valueOf() !== dayRange.end.valueOf() || range.end <= range.start) {
164
+ range = {
165
+ start: range.start,
166
+ end: dateEnv.add(range.end, tDateProfile.slotDuration),
167
+ };
168
+ }
169
+ }
170
+ }
171
+ return range;
172
+ }
173
+ function isValidDate(date, tDateProfile, dateProfile, dateProfileGenerator) {
174
+ if (dateProfileGenerator.isHiddenDay(date)) {
175
+ return false;
176
+ }
177
+ if (tDateProfile.isTimeScale) {
178
+ // determine if the time is within slotMinTime/slotMaxTime, which may have wacky values
179
+ let day = internal.startOfDay(date);
180
+ let timeMs = date.valueOf() - day.valueOf();
181
+ let ms = timeMs - internal.asRoughMs(dateProfile.slotMinTime); // milliseconds since slotMinTime
182
+ ms = ((ms % 86400000) + 86400000) % 86400000; // make negative values wrap to 24hr clock
183
+ return ms < tDateProfile.timeWindowMs; // before the slotMaxTime?
184
+ }
185
+ return true;
186
+ }
187
+ function validateLabelAndSlot(tDateProfile, dateProfile, dateEnv) {
188
+ const { currentRange } = dateProfile;
189
+ // make sure labelInterval doesn't exceed the max number of cells
190
+ if (tDateProfile.labelInterval) {
191
+ const labelCnt = dateEnv.countDurationsBetween(currentRange.start, currentRange.end, tDateProfile.labelInterval);
192
+ if (labelCnt > internal.config.MAX_TIMELINE_SLOTS) {
193
+ console.warn('slotHeaderInterval results in too many cells');
194
+ tDateProfile.labelInterval = null;
195
+ }
196
+ }
197
+ // make sure slotDuration doesn't exceed the maximum number of cells
198
+ if (tDateProfile.slotDuration) {
199
+ const slotCnt = dateEnv.countDurationsBetween(currentRange.start, currentRange.end, tDateProfile.slotDuration);
200
+ if (slotCnt > internal.config.MAX_TIMELINE_SLOTS) {
201
+ console.warn('slotDuration results in too many cells');
202
+ tDateProfile.slotDuration = null;
203
+ }
204
+ }
205
+ // make sure labelInterval is a multiple of slotDuration
206
+ if (tDateProfile.labelInterval && tDateProfile.slotDuration) {
207
+ const slotsPerLabel = internal.wholeDivideDurations(tDateProfile.labelInterval, tDateProfile.slotDuration);
208
+ if (slotsPerLabel === null || slotsPerLabel < 1) {
209
+ console.warn('slotHeaderInterval must be a multiple of slotDuration');
210
+ tDateProfile.slotDuration = null;
211
+ }
212
+ }
213
+ }
214
+ function ensureLabelInterval(tDateProfile, dateProfile, dateEnv) {
215
+ const { currentRange } = dateProfile;
216
+ let { labelInterval } = tDateProfile;
217
+ if (!labelInterval) {
218
+ // compute based off the slot duration
219
+ // find the largest label interval with an acceptable slots-per-label
220
+ let input;
221
+ if (tDateProfile.slotDuration) {
222
+ for (input of STOCK_SUB_DURATIONS) {
223
+ const tryLabelInterval = internal.createDuration(input);
224
+ const slotsPerLabel = internal.wholeDivideDurations(tryLabelInterval, tDateProfile.slotDuration);
225
+ if (slotsPerLabel !== null && slotsPerLabel <= MAX_AUTO_SLOTS_PER_LABEL) {
226
+ labelInterval = tryLabelInterval;
227
+ break;
228
+ }
229
+ }
230
+ // use the slot duration as a last resort
231
+ if (!labelInterval) {
232
+ labelInterval = tDateProfile.slotDuration;
233
+ }
234
+ // compute based off the view's duration
235
+ // find the largest label interval that yields the minimum number of labels
236
+ }
237
+ else {
238
+ for (input of STOCK_SUB_DURATIONS) {
239
+ labelInterval = internal.createDuration(input);
240
+ const labelCnt = dateEnv.countDurationsBetween(currentRange.start, currentRange.end, labelInterval);
241
+ if (labelCnt >= MIN_AUTO_LABELS) {
242
+ break;
243
+ }
244
+ }
245
+ }
246
+ tDateProfile.labelInterval = labelInterval;
247
+ }
248
+ return labelInterval;
249
+ }
250
+ function ensureSlotDuration(tDateProfile, dateProfile, dateEnv) {
251
+ const { currentRange } = dateProfile;
252
+ let { slotDuration } = tDateProfile;
253
+ if (!slotDuration) {
254
+ const labelInterval = ensureLabelInterval(tDateProfile, dateProfile, dateEnv); // will compute if necessary
255
+ // compute based off the label interval
256
+ // find the largest slot duration that is different from labelInterval, but still acceptable
257
+ for (let input of STOCK_SUB_DURATIONS) {
258
+ const trySlotDuration = internal.createDuration(input);
259
+ const slotsPerLabel = internal.wholeDivideDurations(labelInterval, trySlotDuration);
260
+ if (slotsPerLabel !== null && slotsPerLabel > 1 && slotsPerLabel <= MAX_AUTO_SLOTS_PER_LABEL) {
261
+ slotDuration = trySlotDuration;
262
+ break;
263
+ }
264
+ }
265
+ // only allow the value if it won't exceed the view's # of slots limit
266
+ if (slotDuration) {
267
+ const slotCnt = dateEnv.countDurationsBetween(currentRange.start, currentRange.end, slotDuration);
268
+ if (slotCnt > MAX_AUTO_CELLS) {
269
+ slotDuration = null;
270
+ }
271
+ }
272
+ // use the label interval as a last resort
273
+ if (!slotDuration) {
274
+ slotDuration = labelInterval;
275
+ }
276
+ tDateProfile.slotDuration = slotDuration;
277
+ }
278
+ return slotDuration;
279
+ }
280
+ function computeHeaderFormats(tDateProfile, dateProfile, dateEnv, allOptions) {
281
+ let format1;
282
+ let format2;
283
+ const { labelInterval } = tDateProfile;
284
+ const { currentRange } = dateProfile;
285
+ let unit = internal.greatestDurationDenominator(labelInterval).unit;
286
+ const weekNumbersVisible = allOptions.weekNumbers;
287
+ let format0 = (format1 = (format2 = null));
288
+ // NOTE: weekNumber computation function wont work
289
+ if ((unit === 'week') && !weekNumbersVisible) {
290
+ unit = 'day';
291
+ }
292
+ switch (unit) {
293
+ case 'year':
294
+ format0 = { year: 'numeric' }; // '2015'
295
+ break;
296
+ case 'month':
297
+ if (dateEnv.diffWholeYears(currentRange.start, currentRange.end) > 1) {
298
+ format0 = { year: 'numeric' }; // '2015'
299
+ }
300
+ format1 = { month: 'short' }; // 'Jan'
301
+ break;
302
+ case 'week':
303
+ if (dateEnv.diffWholeYears(currentRange.start, currentRange.end) > 1) {
304
+ format0 = { year: 'numeric' }; // '2015'
305
+ }
306
+ format1 = { week: 'narrow' }; // 'Wk4'
307
+ break;
308
+ case 'day':
309
+ if (dateEnv.diffWholeYears(currentRange.start, currentRange.end) > 1) {
310
+ format0 = { year: 'numeric', month: 'long' }; // 'January 2014'
311
+ }
312
+ else if (dateEnv.diffWholeMonths(currentRange.start, currentRange.end) > 1) {
313
+ format0 = { month: 'long' }; // 'January'
314
+ }
315
+ if (weekNumbersVisible) {
316
+ format1 = { week: 'short' }; // 'Wk 4'
317
+ }
318
+ format2 = { weekday: 'narrow', day: 'numeric' }; // 'Su 9'
319
+ break;
320
+ case 'hour':
321
+ if (weekNumbersVisible) {
322
+ format0 = { week: 'short' }; // 'Wk 4'
323
+ }
324
+ if (internal.diffWholeDays(currentRange.start, currentRange.end) > 1) {
325
+ format1 = { weekday: 'short', day: 'numeric', month: 'numeric', omitCommas: true }; // Sat 4/7
326
+ }
327
+ format2 = {
328
+ hour: 'numeric',
329
+ minute: '2-digit',
330
+ omitZeroMinute: true,
331
+ meridiem: 'short',
332
+ };
333
+ break;
334
+ case 'minute':
335
+ // sufficiently large number of different minute cells?
336
+ if ((internal.asRoughMinutes(labelInterval) / 60) >= MAX_AUTO_SLOTS_PER_LABEL) {
337
+ format0 = {
338
+ hour: 'numeric',
339
+ meridiem: 'short',
340
+ };
341
+ format1 = (params) => (':' + internal.padStart(params.date.minute, 2) // ':30'
342
+ );
343
+ }
344
+ else {
345
+ format0 = {
346
+ hour: 'numeric',
347
+ minute: 'numeric',
348
+ meridiem: 'short',
349
+ };
350
+ }
351
+ break;
352
+ case 'second':
353
+ // sufficiently large number of different second cells?
354
+ if ((internal.asRoughSeconds(labelInterval) / 60) >= MAX_AUTO_SLOTS_PER_LABEL) {
355
+ format0 = { hour: 'numeric', minute: '2-digit', meridiem: 'lowercase' }; // '8:30 PM'
356
+ format1 = (params) => (':' + internal.padStart(params.date.second, 2) // ':30'
357
+ );
358
+ }
359
+ else {
360
+ format0 = { hour: 'numeric', minute: '2-digit', second: '2-digit', meridiem: 'lowercase' }; // '8:30:45 PM'
361
+ }
362
+ break;
363
+ case 'millisecond':
364
+ format0 = { hour: 'numeric', minute: '2-digit', second: '2-digit', meridiem: 'lowercase' }; // '8:30:45 PM'
365
+ format1 = (params) => ('.' + internal.padStart(params.millisecond, 3));
366
+ break;
367
+ }
368
+ return [].concat(format0 || [], format1 || [], format2 || []);
369
+ }
370
+ function buildCellRows(tDateProfile, dateEnv, majorUnit) {
371
+ let slotDates = tDateProfile.slotDates;
372
+ let formats = tDateProfile.headerFormats;
373
+ let cellRows = formats.map(() => []); // indexed by row,col
374
+ let slotAsDays = internal.asCleanDays(tDateProfile.slotDuration);
375
+ let guessedSlotUnit = slotAsDays === 7 ? 'week' :
376
+ slotAsDays === 1 ? 'day' :
377
+ null;
378
+ // specifically for navclicks
379
+ let rowUnitsFromFormats = formats.map((format) => (format.getSmallestUnit ? format.getSmallestUnit() : null));
380
+ // builds cellRows and slotCells
381
+ for (let i = 0; i < slotDates.length; i += 1) {
382
+ let date = slotDates[i];
383
+ for (let row = 0; row < formats.length; row += 1) {
384
+ let format = formats[row];
385
+ let rowCells = cellRows[row];
386
+ let leadingCell = rowCells[rowCells.length - 1];
387
+ let isLastRow = row === formats.length - 1;
388
+ let isSuperRow = formats.length > 1 && !isLastRow; // more than one row and not the last
389
+ let isMajor = internal.isMajorUnit(date, majorUnit, dateEnv);
390
+ let newCell = null;
391
+ let rowUnit = rowUnitsFromFormats[row] || (isLastRow ? guessedSlotUnit : null);
392
+ if (isSuperRow) {
393
+ let [text] = dateEnv.format(date, format);
394
+ if (!leadingCell || (leadingCell.text !== text)) {
395
+ newCell = buildCellObject(date, isMajor, text, rowUnit);
396
+ }
397
+ else {
398
+ leadingCell.colspan += 1;
399
+ }
400
+ }
401
+ else if (!leadingCell ||
402
+ internal.isInt(dateEnv.countDurationsBetween(tDateProfile.normalizedRange.start, date, tDateProfile.labelInterval))) {
403
+ let [text] = dateEnv.format(date, format);
404
+ newCell = buildCellObject(date, isMajor, text, rowUnit);
405
+ }
406
+ else {
407
+ leadingCell.colspan += 1;
408
+ }
409
+ if (newCell) {
410
+ rowCells.push(newCell);
411
+ }
412
+ }
413
+ }
414
+ return cellRows;
415
+ }
416
+ function buildCellObject(date, isMajor, text, rowUnit) {
417
+ return { date, isMajor, text, rowUnit, colspan: 1 }; // colspan mutated later
418
+ }
419
+
420
+ class TimelineSlatCell extends internal.BaseComponent {
421
+ constructor() {
422
+ super(...arguments);
423
+ // memo
424
+ this.getDateMeta = internal.memoize(internal.getDateMeta);
425
+ }
426
+ render() {
427
+ let { props, context } = this;
428
+ let { dateEnv, options } = context;
429
+ let { date, tDateProfile, isMajor } = props;
430
+ let dateMeta = this.getDateMeta(props.date, dateEnv, props.dateProfile, props.todayRange, props.nowDate);
431
+ let isMinor = tDateProfile.isTimeScale &&
432
+ !internal.isInt(dateEnv.countDurationsBetween(tDateProfile.normalizedRange.start, props.date, tDateProfile.labelInterval));
433
+ let renderProps = Object.assign(Object.assign({}, dateMeta), { isMajor,
434
+ isMinor, view: context.viewApi });
435
+ return (preact.createElement(internal.ContentContainer, { tag: "div", className: core.joinClassNames(classNames__default["default"].tight, classNames__default["default"].alignStart, // shrinks width of InnerContent
436
+ props.borderStart ? classNames__default["default"].borderOnlyS : classNames__default["default"].borderNone, classNames__default["default"].internalTimelineSlot), attrs: Object.assign({ 'data-date': dateEnv.formatIso(date, {
437
+ omitTimeZoneOffset: true,
438
+ omitTime: !tDateProfile.isTimeScale,
439
+ }) }, (dateMeta.isToday ? { 'aria-current': 'date' } : {})), style: {
440
+ width: props.width,
441
+ }, renderProps: renderProps, generatorName: undefined, classNameGenerator: options.slotLaneClass, didMount: options.slotLaneDidMount, willUnmount: options.slotLaneWillUnmount }));
442
+ }
443
+ }
444
+
445
+ class TimelineSlats extends internal.BaseComponent {
446
+ render() {
447
+ let { props } = this;
448
+ let { tDateProfile, slotWidth } = props;
449
+ let { slotDates, slotDatesMajor } = tDateProfile;
450
+ return (preact.createElement("div", { "aria-hidden": true, className: core.joinClassNames(classNames__default["default"].flexRow, classNames__default["default"].fill), style: { height: props.height } }, slotDates.map((slotDate, i) => {
451
+ let key = slotDate.toISOString();
452
+ return (preact.createElement(TimelineSlatCell, { key: key, date: slotDate, dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: props.nowDate, todayRange: props.todayRange, isMajor: slotDatesMajor[i], borderStart: Boolean(i),
453
+ // dimensions
454
+ width: slotWidth }));
455
+ })));
456
+ }
457
+ }
458
+
459
+ class TimelineHeaderCell extends internal.BaseComponent {
460
+ constructor() {
461
+ super(...arguments);
462
+ // memo
463
+ this.getDateMeta = internal.memoize(internal.getDateMeta);
464
+ // ref
465
+ this.innerWrapperElRef = preact.createRef();
466
+ }
467
+ render() {
468
+ let { props, state, context } = this;
469
+ let { dateEnv, options } = context;
470
+ let { cell, dateProfile, tDateProfile } = props;
471
+ // the cell.rowUnit is f'd
472
+ // giving 'month' for a 3-day view
473
+ // workaround: to infer day, do NOT time
474
+ let dateMeta = this.getDateMeta(cell.date, dateEnv, dateProfile, props.todayRange, props.nowDate);
475
+ let hasNavLink = options.navLinks && !dateMeta.isDisabled && (cell.rowUnit && cell.rowUnit !== 'time');
476
+ let isTime = tDateProfile.isTimeScale && !props.rowLevel; // HACK: faulty way of determining this
477
+ let renderProps = Object.assign(Object.assign({}, dateMeta), { level: props.rowLevel, isMajor: cell.isMajor, isMinor: false, isNarrow: false, isTime,
478
+ hasNavLink, text: cell.text, isFirst: props.isFirst, view: context.viewApi });
479
+ const { slotHeaderAlign } = options;
480
+ const align = this.align =
481
+ typeof slotHeaderAlign === 'function'
482
+ ? slotHeaderAlign({ level: props.rowLevel, isTime })
483
+ : slotHeaderAlign;
484
+ const isSticky = this.isSticky =
485
+ props.rowLevel && options.slotHeaderSticky !== false;
486
+ let edgeCoord;
487
+ if (isSticky) {
488
+ if (align === 'center') {
489
+ if (state.innerWidth != null) {
490
+ edgeCoord = `calc(50% - ${state.innerWidth / 2}px)`;
491
+ }
492
+ }
493
+ else {
494
+ edgeCoord = (typeof options.slotHeaderSticky === 'number' ||
495
+ typeof options.slotHeaderSticky === 'string') ? options.slotHeaderSticky : 0;
496
+ }
497
+ }
498
+ return (preact.createElement(internal.ContentContainer, { tag: "div", className: internal.joinArrayishClassNames(classNames__default["default"].tight, classNames__default["default"].flexCol, props.isFirst ? classNames__default["default"].borderNone : classNames__default["default"].borderOnlyS, align === 'center' ? classNames__default["default"].alignCenter :
499
+ align === 'end' ? classNames__default["default"].alignEnd :
500
+ classNames__default["default"].alignStart, classNames__default["default"].internalTimelineSlot), attrs: Object.assign({ 'data-date': dateEnv.formatIso(cell.date, {
501
+ omitTime: !tDateProfile.isTimeScale,
502
+ omitTimeZoneOffset: true,
503
+ }) }, (dateMeta.isToday ? { 'aria-current': 'date' } : {})), style: {
504
+ width: props.slotWidth != null
505
+ ? props.slotWidth * cell.colspan
506
+ : undefined,
507
+ }, renderProps: renderProps, generatorName: "slotHeaderContent", customGenerator: options.slotHeaderContent, defaultGenerator: renderInnerContent, classNameGenerator: options.slotHeaderClass, didMount: options.slotHeaderDidMount, willUnmount: options.slotHeaderWillUnmount }, (InnerContent) => (preact.createElement("div", { ref: this.innerWrapperElRef, className: core.joinClassNames(classNames__default["default"].flexCol, classNames__default["default"].rigid, isSticky && classNames__default["default"].sticky), style: {
508
+ left: edgeCoord,
509
+ right: edgeCoord,
510
+ } },
511
+ preact.createElement(InnerContent, { tag: 'div', attrs: hasNavLink
512
+ // not tabbable because parent is aria-hidden
513
+ ? internal.buildNavLinkAttrs(context, cell.date, cell.rowUnit, undefined, /* isTabbable = */ false)
514
+ : {} // don't bother with aria-hidden because parent already hidden
515
+ , className: internal.generateClassName(options.slotHeaderInnerClass, renderProps) })))));
516
+ }
517
+ componentDidMount() {
518
+ const { props } = this;
519
+ const innerWrapperEl = this.innerWrapperElRef.current; // TODO: make dynamic with useEffect
520
+ this.disconnectSize = internal.watchSize(innerWrapperEl, (width, height) => {
521
+ internal.setRef(props.innerWidthRef, width);
522
+ internal.setRef(props.innerHeightRef, height);
523
+ if (this.align === 'center' && this.isSticky) {
524
+ this.setState({ innerWidth: width });
525
+ }
526
+ });
527
+ }
528
+ componentWillUnmount() {
529
+ const { props } = this;
530
+ this.disconnectSize();
531
+ internal.setRef(props.innerWidthRef, null);
532
+ internal.setRef(props.innerHeightRef, null);
533
+ }
534
+ }
535
+ // Utils
536
+ // -------------------------------------------------------------------------------------------------
537
+ function renderInnerContent(renderProps) {
538
+ return renderProps.text;
539
+ }
540
+
541
+ class TimelineHeaderRow extends internal.BaseComponent {
542
+ constructor() {
543
+ super(...arguments);
544
+ // refs
545
+ this.innerWidthRefMap = new internal.RefMap(() => {
546
+ internal.afterSize(this.handleInnerWidths);
547
+ });
548
+ this.innerHeightRefMap = new internal.RefMap(() => {
549
+ internal.afterSize(this.handleInnerHeights);
550
+ });
551
+ this.handleInnerWidths = () => {
552
+ const innerWidthMap = this.innerWidthRefMap.current;
553
+ let max = 0;
554
+ for (const innerWidth of innerWidthMap.values()) {
555
+ max = Math.max(max, innerWidth);
556
+ }
557
+ // TODO: ensure not equal?
558
+ internal.setRef(this.props.innerWidthRef, max);
559
+ };
560
+ this.handleInnerHeights = () => {
561
+ const innerHeightMap = this.innerHeightRefMap.current;
562
+ let max = 0;
563
+ for (const innerHeight of innerHeightMap.values()) {
564
+ max = Math.max(max, innerHeight);
565
+ }
566
+ // TODO: ensure not equal?
567
+ internal.setRef(this.props.innerHeighRef, max);
568
+ this.setState({ innerHeight: max });
569
+ };
570
+ }
571
+ render() {
572
+ const { props, innerWidthRefMap, innerHeightRefMap, state, context } = this;
573
+ const { options } = context;
574
+ return (preact.createElement("div", { className: internal.joinArrayishClassNames(options.slotHeaderRowClass, classNames__default["default"].flexRow, classNames__default["default"].grow, props.rowLevel // not the last row?
575
+ ? classNames__default["default"].borderOnlyB
576
+ : classNames__default["default"].borderNone), style: {
577
+ // we assign height because we allow cells to have distorted heights for visual effect
578
+ // but we still want to keep the overall extrenal mass
579
+ height: state.innerHeight,
580
+ } }, props.cells.map((cell, cellI) => {
581
+ // TODO: make this part of the cell obj?
582
+ // TODO: rowUnit seems wrong sometimes. says 'month' when it should be day
583
+ // TODO: rowUnit is relevant to whole row. put it on a row object, not the cells
584
+ // TODO: use rowUnit to key the Row itself?
585
+ const key = cell.rowUnit + ':' + cell.date.toISOString();
586
+ return (preact.createElement(TimelineHeaderCell, { key: key, cell: cell, rowLevel: props.rowLevel, dateProfile: props.dateProfile, tDateProfile: props.tDateProfile, todayRange: props.todayRange, nowDate: props.nowDate, isFirst: cellI === 0,
587
+ // refs
588
+ innerWidthRef: innerWidthRefMap.createRef(key), innerHeightRef: innerHeightRefMap.createRef(key),
589
+ // dimensions
590
+ slotWidth: props.slotWidth }));
591
+ })));
592
+ }
593
+ componentWillUnmount() {
594
+ internal.setRef(this.props.innerWidthRef, null);
595
+ internal.setRef(this.props.innerHeighRef, null);
596
+ }
597
+ }
598
+
599
+ // Timeline-specific
600
+ // -------------------------------------------------------------------------------------------------
601
+ const MIN_SLOT_WIDTH = 30; // for real
602
+ /*
603
+ TODO: DRY with computeSlatHeight?
604
+ */
605
+ function computeSlotWidth(slatCnt, slatsPerLabel, slatMinWidth, labelInnerWidth, viewportWidth) {
606
+ if (labelInnerWidth == null || viewportWidth == null) {
607
+ return [undefined, undefined, false];
608
+ }
609
+ slatMinWidth = Math.max(slatMinWidth || 0, (labelInnerWidth + 1) / slatsPerLabel, MIN_SLOT_WIDTH);
610
+ const slatTryWidth = viewportWidth / slatCnt;
611
+ let slotLiquid;
612
+ let slatWidth;
613
+ if (slatTryWidth >= slatMinWidth) {
614
+ slotLiquid = true;
615
+ slatWidth = slatTryWidth;
616
+ }
617
+ else {
618
+ slotLiquid = false;
619
+ slatWidth = Math.max(slatMinWidth, slatTryWidth);
620
+ }
621
+ return [slatWidth * slatCnt, slatWidth, slotLiquid];
622
+ }
623
+ function timeToCoord(// pixels
624
+ time, dateEnv, dateProfile, tDateProfile, slowWidth) {
625
+ let date = dateEnv.add(dateProfile.activeRange.start, time);
626
+ if (!tDateProfile.isTimeScale) {
627
+ date = internal.startOfDay(date);
628
+ }
629
+ return dateToCoord(date, dateEnv, tDateProfile, slowWidth);
630
+ }
631
+ function dateToCoord(// pixels
632
+ date, dateEnv, tDateProfile, slotWidth) {
633
+ let snapCoverage = computeDateSnapCoverage$1(date, tDateProfile, dateEnv);
634
+ let slotCoverage = snapCoverage / tDateProfile.snapsPerSlot;
635
+ return slotCoverage * slotWidth;
636
+ }
637
+ /*
638
+ returned value is between 0 and the number of snaps
639
+ */
640
+ function computeDateSnapCoverage$1(date, tDateProfile, dateEnv) {
641
+ let snapDiff = dateEnv.countDurationsBetween(tDateProfile.normalizedRange.start, date, tDateProfile.snapDuration);
642
+ if (snapDiff < 0) {
643
+ return 0;
644
+ }
645
+ if (snapDiff >= tDateProfile.snapDiffToIndex.length) {
646
+ return tDateProfile.snapCnt;
647
+ }
648
+ let snapDiffInt = Math.floor(snapDiff);
649
+ let snapCoverage = tDateProfile.snapDiffToIndex[snapDiffInt];
650
+ if (internal.isInt(snapCoverage)) { // not an in-between value
651
+ snapCoverage += snapDiff - snapDiffInt; // add the remainder
652
+ }
653
+ else {
654
+ // a fractional value, meaning the date is not visible
655
+ // always round up in this case. works for start AND end dates in a range.
656
+ snapCoverage = Math.ceil(snapCoverage);
657
+ }
658
+ return snapCoverage;
659
+ }
660
+
661
+ class TimelineNowIndicatorLine extends internal.BaseComponent {
662
+ render() {
663
+ const { props, context } = this;
664
+ const xStyle = props.slotWidth == null
665
+ ? {}
666
+ : {
667
+ insetInlineStart: dateToCoord(props.nowDate, context.dateEnv, props.tDateProfile, props.slotWidth)
668
+ };
669
+ return (preact.createElement("div", { className: classNames__default["default"].fill, style: {
670
+ zIndex: 2,
671
+ pointerEvents: 'none', // TODO: className
672
+ } },
673
+ preact.createElement(internal.NowIndicatorLineContainer, { className: core.joinClassNames(classNames__default["default"].fillY, classNames__default["default"].noMarginY, classNames__default["default"].borderlessY), style: xStyle, date: props.nowDate }),
674
+ preact.createElement("div", { className: core.joinClassNames(classNames__default["default"].flexCol, // better for negative margins
675
+ classNames__default["default"].fillY), style: xStyle },
676
+ preact.createElement("div", {
677
+ // stickiness on NowIndicatorDot misbehaves b/c of negative marginss
678
+ className: classNames__default["default"].stickyT },
679
+ preact.createElement(internal.NowIndicatorDot, null)))));
680
+ }
681
+ }
682
+
683
+ /*
684
+ TODO: DRY with other NowIndicator components
685
+ */
686
+ class TimelineNowIndicatorArrow extends internal.BaseComponent {
687
+ render() {
688
+ const { props, context } = this;
689
+ const xStyle = props.slotWidth == null
690
+ ? {}
691
+ : {
692
+ insetInlineStart: dateToCoord(props.nowDate, context.dateEnv, props.tDateProfile, props.slotWidth)
693
+ };
694
+ return (preact.createElement("div", {
695
+ // crop any overflow that the arrow/line might cause
696
+ // TODO: just do this on the entire canvas within the scroller
697
+ className: core.joinClassNames(classNames__default["default"].fill, classNames__default["default"].crop), style: {
698
+ zIndex: 2,
699
+ pointerEvents: 'none', // TODO: className
700
+ } },
701
+ preact.createElement(internal.NowIndicatorHeaderContainer, { className: classNames__default["default"].abs, style: xStyle, date: props.nowDate })));
702
+ }
703
+ }
704
+
705
+ function getTimelineSlotEl(parentEl, index) {
706
+ return parentEl.querySelectorAll(`.${classNames__default["default"].internalTimelineSlot}`)[index];
707
+ }
708
+
709
+ /*
710
+ TODO: rename this file!
711
+ */
712
+ // returned value is between 0 and the number of snaps
713
+ function computeDateSnapCoverage(date, tDateProfile, dateEnv) {
714
+ let snapDiff = dateEnv.countDurationsBetween(tDateProfile.normalizedRange.start, date, tDateProfile.snapDuration);
715
+ if (snapDiff < 0) {
716
+ return 0;
717
+ }
718
+ if (snapDiff >= tDateProfile.snapDiffToIndex.length) {
719
+ return tDateProfile.snapCnt;
720
+ }
721
+ let snapDiffInt = Math.floor(snapDiff);
722
+ let snapCoverage = tDateProfile.snapDiffToIndex[snapDiffInt];
723
+ if (internal.isInt(snapCoverage)) { // not an in-between value
724
+ snapCoverage += snapDiff - snapDiffInt; // add the remainder
725
+ }
726
+ else {
727
+ // a fractional value, meaning the date is not visible
728
+ // always round up in this case. works for start AND end dates in a range.
729
+ snapCoverage = Math.ceil(snapCoverage);
730
+ }
731
+ return snapCoverage;
732
+ }
733
+
734
+ class TimelineLaneSlicer extends internal.Slicer {
735
+ sliceRange(origRange, dateProfile, dateProfileGenerator, tDateProfile, dateEnv) {
736
+ let normalRange = normalizeRange(origRange, tDateProfile, dateEnv);
737
+ let segs = [];
738
+ // protect against when the span is entirely in an invalid date region
739
+ if (computeDateSnapCoverage(normalRange.start, tDateProfile, dateEnv)
740
+ < computeDateSnapCoverage(normalRange.end, tDateProfile, dateEnv)) {
741
+ // intersect the footprint's range with the grid's range
742
+ let slicedRange = internal.intersectRanges(normalRange, tDateProfile.normalizedRange);
743
+ if (slicedRange) {
744
+ segs.push({
745
+ startDate: slicedRange.start,
746
+ endDate: slicedRange.end,
747
+ isStart: slicedRange.start.valueOf() === normalRange.start.valueOf()
748
+ && isValidDate(slicedRange.start, tDateProfile, dateProfile, dateProfileGenerator),
749
+ isEnd: slicedRange.end.valueOf() === normalRange.end.valueOf()
750
+ && isValidDate(internal.addMs(slicedRange.end, -1), tDateProfile, dateProfile, dateProfileGenerator),
751
+ });
752
+ }
753
+ }
754
+ return segs;
755
+ }
756
+ }
757
+
758
+ const DEFAULT_TIME_FORMAT = internal.createFormatter({
759
+ hour: 'numeric',
760
+ minute: '2-digit',
761
+ omitZeroMinute: true,
762
+ meridiem: 'narrow',
763
+ });
764
+ class TimelineEvent extends internal.BaseComponent {
765
+ render() {
766
+ let { props } = this;
767
+ return (preact.createElement(internal.StandardEvent, Object.assign({}, props, { display: 'row', defaultTimeFormat: DEFAULT_TIME_FORMAT, defaultDisplayEventTime: !props.isTimeScale })));
768
+ }
769
+ }
770
+
771
+ class TimelineLaneMoreLink extends internal.BaseComponent {
772
+ render() {
773
+ let { props } = this;
774
+ let { hiddenSegs, resourceId } = props;
775
+ let dateSpanProps = resourceId ? { resourceId } : {};
776
+ return (preact.createElement(internal.MoreLinkContainer, { display: 'row', allDayDate: null, segs: hiddenSegs, hiddenSegs: hiddenSegs, dateProfile: props.dateProfile, todayRange: props.todayRange, dateSpanProps: dateSpanProps, isNarrow: false, isMicro: false, popoverContent: () => (preact.createElement(preact.Fragment, null, hiddenSegs.map((seg) => {
777
+ let { eventRange } = seg;
778
+ let { instanceId } = eventRange.instance;
779
+ let isDragging = Boolean(props.eventDrag && props.eventDrag.affectedInstances[instanceId]);
780
+ let isResizing = Boolean(props.eventResize && props.eventResize.affectedInstances[instanceId]);
781
+ let isInvisible = isDragging || isResizing;
782
+ return (preact.createElement("div", { key: instanceId, style: { visibility: isInvisible ? 'hidden' : undefined } },
783
+ preact.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 }, internal.getEventRangeMeta(eventRange, props.todayRange, props.nowDate)))));
784
+ }))) }));
785
+ }
786
+ }
787
+
788
+ function computeManySegHorizontals(segs, segMinWidth, dateEnv, tDateProfile, slotWidth) {
789
+ const res = {};
790
+ for (const seg of segs) {
791
+ res[internal.getEventKey(seg)] = computeSegHorizontals(seg, segMinWidth, dateEnv, tDateProfile, slotWidth);
792
+ }
793
+ return res;
794
+ }
795
+ function computeSegHorizontals(seg, segMinWidth, dateEnv, tDateProfile, slotWidth) {
796
+ const startCoord = dateToCoord(seg.startDate, dateEnv, tDateProfile, slotWidth);
797
+ const endCoord = dateToCoord(seg.endDate, dateEnv, tDateProfile, slotWidth);
798
+ let size = endCoord - startCoord;
799
+ if (segMinWidth) {
800
+ size = Math.max(size, segMinWidth);
801
+ }
802
+ return { start: startCoord, size };
803
+ }
804
+ function computeFgSegPlacements(// mostly horizontals
805
+ segs, segHorizontals, // TODO: use Map
806
+ segHeights, // keyed by instanceId
807
+ hiddenGroupHeights, strictOrder, maxDepth) {
808
+ const segRanges = [];
809
+ // isn't it true that there will either be ALL hcoords or NONE? can optimize
810
+ for (const seg of segs) {
811
+ const hcoords = segHorizontals[internal.getEventKey(seg)];
812
+ if (hcoords) {
813
+ segRanges.push(Object.assign(Object.assign({}, seg), { start: hcoords.start, end: hcoords.start + hcoords.size }));
814
+ }
815
+ }
816
+ const hierarchy = new internal.SegHierarchy(segRanges, (seg) => segHeights.get(internal.getEventKey(seg)), strictOrder, undefined, // maxCoord
817
+ maxDepth);
818
+ const segTops = new Map();
819
+ hierarchy.traverseSegs((seg, segTop) => {
820
+ segTops.set(internal.getEventKey(seg), segTop);
821
+ });
822
+ const { hiddenSegs } = hierarchy;
823
+ let totalHeight = 0;
824
+ for (const segRange of segRanges) {
825
+ const segKey = internal.getEventKey(segRange);
826
+ const segHeight = segHeights.get(segKey);
827
+ const segTop = segTops.get(segKey);
828
+ if (segHeight != null) {
829
+ if (segTop != null) {
830
+ totalHeight = Math.max(totalHeight, segTop + segHeight);
831
+ }
832
+ }
833
+ }
834
+ const hiddenGroups = internal.groupIntersectingSegs(hiddenSegs);
835
+ const hiddenGroupTops = new Map();
836
+ // HACK for hiddenGroup findInsertion() call
837
+ hierarchy.strictOrder = true;
838
+ for (const hiddenGroup of hiddenGroups) {
839
+ const { levelCoord: top } = hierarchy.findInsertion(hiddenGroup, 0);
840
+ const hiddenGroupHeight = hiddenGroupHeights.get(hiddenGroup.key) || 0;
841
+ hiddenGroupTops.set(hiddenGroup.key, top);
842
+ totalHeight = Math.max(totalHeight, top + hiddenGroupHeight);
843
+ }
844
+ return [
845
+ segTops,
846
+ hiddenGroups,
847
+ hiddenGroupTops,
848
+ totalHeight,
849
+ ];
850
+ }
851
+
852
+ /*
853
+ TODO: make DRY with other Event Harnesses
854
+ */
855
+ class TimelineEventHarness extends preact.Component {
856
+ constructor() {
857
+ super(...arguments);
858
+ // ref
859
+ this.rootElRef = preact.createRef();
860
+ }
861
+ render() {
862
+ const { props } = this;
863
+ return (preact.createElement("div", { className: classNames__default["default"].abs, style: props.style, ref: this.rootElRef }, props.children));
864
+ }
865
+ componentDidMount() {
866
+ const rootEl = this.rootElRef.current; // TODO: make dynamic with useEffect
867
+ this.disconnectHeight = internal.watchHeight(rootEl, (height) => {
868
+ internal.setRef(this.props.heightRef, height);
869
+ });
870
+ }
871
+ componentWillUnmount() {
872
+ this.disconnectHeight();
873
+ internal.setRef(this.props.heightRef, null);
874
+ }
875
+ }
876
+
877
+ class TimelineFg extends internal.BaseComponent {
878
+ constructor() {
879
+ super(...arguments);
880
+ // memo
881
+ this.sortEventSegs = internal.memoize(internal.sortEventSegs);
882
+ // refs
883
+ this.segHeightRefMap = new internal.RefMap(() => {
884
+ internal.afterSize(this.handleSegHeights);
885
+ });
886
+ this.moreLinkHeightRefMap = new internal.RefMap(() => {
887
+ internal.afterSize(this.handleMoreLinkHeights);
888
+ });
889
+ this.handleMoreLinkHeights = () => {
890
+ this.setState({ moreLinkHeightRev: this.moreLinkHeightRefMap.rev }); // will trigger rerender
891
+ };
892
+ this.handleSegHeights = () => {
893
+ this.setState({ segHeightRev: this.segHeightRefMap.rev }); // will trigger rerender
894
+ };
895
+ }
896
+ /*
897
+ TODO: lots of memoization needed here!
898
+ */
899
+ render() {
900
+ let { props, context, segHeightRefMap, moreLinkHeightRefMap } = this;
901
+ let { options } = context;
902
+ let { tDateProfile } = props;
903
+ let mirrorSegs = (props.eventDrag ? props.eventDrag.segs : null) ||
904
+ (props.eventResize ? props.eventResize.segs : null) ||
905
+ [];
906
+ let fgSegs = this.sortEventSegs(props.fgEventSegs, options.eventOrder);
907
+ let fgSegHorizontals = props.slotWidth != null
908
+ ? computeManySegHorizontals(fgSegs, options.eventMinWidth, context.dateEnv, tDateProfile, props.slotWidth)
909
+ : {};
910
+ let [fgSegTops, hiddenGroups, hiddenGroupTops, totalHeight] = computeFgSegPlacements(fgSegs, fgSegHorizontals, segHeightRefMap.current, moreLinkHeightRefMap.current, options.eventOrderStrict, options.eventMaxStack);
911
+ this.totalHeight = totalHeight;
912
+ return (preact.createElement("div", { className: core.joinClassNames(classNames__default["default"].rel, classNames__default["default"].noShrink), style: {
913
+ height: totalHeight,
914
+ } },
915
+ this.renderFgSegs(fgSegs, fgSegHorizontals, fgSegTops, hiddenGroups, hiddenGroupTops,
916
+ /* isMirror = */ false),
917
+ this.renderFgSegs(mirrorSegs, props.slotWidth // TODO: memoize
918
+ ? computeManySegHorizontals(mirrorSegs, options.eventMinWidth, context.dateEnv, tDateProfile, props.slotWidth)
919
+ : {}, fgSegTops,
920
+ /* hiddenGroups = */ [],
921
+ /* hiddenGroupTops = */ new Map(),
922
+ /* isMirror = */ true)));
923
+ }
924
+ renderFgSegs(segs, segHorizontals, segTops, hiddenGroups, hiddenGroupTops, isMirror) {
925
+ let { props, segHeightRefMap, moreLinkHeightRefMap } = this;
926
+ return (preact.createElement(preact.Fragment, null,
927
+ segs.map((seg) => {
928
+ const { eventRange } = seg;
929
+ const { instanceId } = eventRange.instance;
930
+ const segTop = segTops.get(instanceId);
931
+ const segHorizontalMaybe = segHorizontals[instanceId];
932
+ const segHorizontal = segHorizontalMaybe || {};
933
+ const isDragging = Boolean(props.eventDrag && props.eventDrag.affectedInstances[instanceId]);
934
+ const isResizing = Boolean(props.eventResize && props.eventResize.affectedInstances[instanceId]);
935
+ const isInvisible = !isMirror && (isDragging || isResizing || !segHorizontalMaybe || segTop == null);
936
+ return (preact.createElement(TimelineEventHarness, { key: instanceId, style: {
937
+ visibility: isInvisible ? 'hidden' : undefined,
938
+ zIndex: 1,
939
+ top: segTop || 0,
940
+ insetInlineStart: segHorizontal.start,
941
+ width: segHorizontal.size,
942
+ }, heightRef: isMirror ? undefined : segHeightRefMap.createRef(instanceId) },
943
+ preact.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 }, internal.getEventRangeMeta(eventRange, props.todayRange, props.nowDate)))));
944
+ }),
945
+ hiddenGroups.map((hiddenGroup) => (preact.createElement(TimelineEventHarness, { key: hiddenGroup.key, style: {
946
+ top: hiddenGroupTops.get(hiddenGroup.key) || 0,
947
+ insetInlineStart: hiddenGroup.start,
948
+ width: hiddenGroup.end - hiddenGroup.start,
949
+ }, heightRef: moreLinkHeightRefMap.createRef(hiddenGroup.key) },
950
+ preact.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 }))))));
951
+ }
952
+ /*
953
+ componentDidMount(): void {
954
+ // might want to do firedTotalHeight, but won't be ready on first render
955
+ }
956
+ */
957
+ componentDidUpdate() {
958
+ if (this.totalHeight !== this.firedTotalHeight) {
959
+ this.firedTotalHeight = this.totalHeight;
960
+ internal.setRef(this.props.heightRef, this.totalHeight);
961
+ }
962
+ }
963
+ componentWillUnmount() {
964
+ internal.setRef(this.props.heightRef, null);
965
+ }
966
+ }
967
+
968
+ class TimelineBg extends internal.BaseComponent {
969
+ render() {
970
+ let { props } = this;
971
+ let highlightSeg = [].concat(props.eventResizeSegs || [], props.dateSelectionSegs);
972
+ return (preact.createElement(preact.Fragment, null,
973
+ this.renderSegs(props.businessHourSegs || [], 'non-business'),
974
+ this.renderSegs(props.bgEventSegs || [], 'bg-event'),
975
+ this.renderSegs(highlightSeg, 'highlight')));
976
+ }
977
+ renderSegs(segs, fillType) {
978
+ let { tDateProfile, todayRange, nowDate, slotWidth } = this.props;
979
+ let { dateEnv, options } = this.context;
980
+ return (preact.createElement(preact.Fragment, null, segs.map((seg) => {
981
+ let hStyle = {};
982
+ if (slotWidth != null) {
983
+ let segHorizontal = computeSegHorizontals(seg, undefined, dateEnv, tDateProfile, slotWidth);
984
+ hStyle = { insetInlineStart: segHorizontal.start, width: segHorizontal.size };
985
+ }
986
+ return (preact.createElement("div", { key: internal.buildEventRangeKey(seg.eventRange), className: classNames__default["default"].fillY, style: hStyle }, fillType === 'bg-event' ? (preact.createElement(internal.BgEvent, Object.assign({ eventRange: seg.eventRange, isStart: seg.isStart, isEnd: seg.isEnd, isVertical: false }, internal.getEventRangeMeta(seg.eventRange, todayRange, nowDate)))) : (internal.renderFill(fillType, options))));
987
+ })));
988
+ }
989
+ }
990
+
991
+ class TimelineView extends internal.DateComponent {
992
+ constructor() {
993
+ super(...arguments);
994
+ // memoized
995
+ this.buildTimelineDateProfile = internal.memoize(buildTimelineDateProfile);
996
+ this.computeSlotWidth = internal.memoize(computeSlotWidth);
997
+ // refs
998
+ this.headerScrollerRef = preact.createRef();
999
+ this.bodyScrollerRef = preact.createRef();
1000
+ this.footerScrollerRef = preact.createRef();
1001
+ this.headerRowInnerWidthMap = new internal.RefMap(() => {
1002
+ internal.afterSize(this.handleSlotInnerWidths);
1003
+ });
1004
+ this.scrollTime = null;
1005
+ this.slicer = new TimelineLaneSlicer();
1006
+ // Sizing
1007
+ // -----------------------------------------------------------------------------------------------
1008
+ this.handleSlotInnerWidths = () => {
1009
+ const headerSlotInnerWidth = this.headerRowInnerWidthMap.current.get(this.tDateProfile.cellRows.length - 1);
1010
+ if (headerSlotInnerWidth != null && headerSlotInnerWidth !== this.state.slotInnerWidth) {
1011
+ this.setState({ slotInnerWidth: headerSlotInnerWidth });
1012
+ }
1013
+ };
1014
+ this.handleTotalWidth = (totalWidth) => {
1015
+ this.setState({
1016
+ totalWidth,
1017
+ });
1018
+ };
1019
+ this.handleClientWidth = (clientWidth) => {
1020
+ this.setState({
1021
+ clientWidth,
1022
+ });
1023
+ };
1024
+ this.handleTimeScrollRequest = (scrollTime) => {
1025
+ this.scrollTime = scrollTime;
1026
+ this.applyTimeScroll();
1027
+ };
1028
+ this.handleTimeScrollEnd = (isUser) => {
1029
+ if (isUser) {
1030
+ this.scrollTime = null;
1031
+ }
1032
+ };
1033
+ // Hit System
1034
+ // -----------------------------------------------------------------------------------------------
1035
+ this.handeBodyEl = (el) => {
1036
+ this.bodyEl = el;
1037
+ if (el) {
1038
+ this.context.registerInteractiveComponent(this, { el });
1039
+ }
1040
+ else {
1041
+ this.context.unregisterInteractiveComponent(this);
1042
+ }
1043
+ };
1044
+ }
1045
+ render() {
1046
+ const { props, state, context } = this;
1047
+ const { options } = context;
1048
+ const { totalWidth, clientWidth } = state;
1049
+ const endScrollbarWidth = (totalWidth != null && clientWidth != null)
1050
+ ? totalWidth - clientWidth
1051
+ : undefined;
1052
+ /* date */
1053
+ const tDateProfile = this.tDateProfile = this.buildTimelineDateProfile(props.dateProfile, context.dateEnv, options, context.dateProfileGenerator);
1054
+ const { cellRows } = tDateProfile;
1055
+ const timerUnit = internal.greatestDurationDenominator(tDateProfile.slotDuration).unit;
1056
+ /* table settings */
1057
+ const verticalScrolling = !props.forPrint && !internal.getIsHeightAuto(options);
1058
+ const stickyHeaderDates = !props.forPrint && internal.getStickyHeaderDates(options);
1059
+ const stickyFooterScrollbar = !props.forPrint && internal.getStickyFooterScrollbar(options);
1060
+ /* table positions */
1061
+ const [canvasWidth, slotWidth] = this.computeSlotWidth(tDateProfile.slotCnt, tDateProfile.slotsPerLabel, options.slotMinWidth, state.slotInnerWidth, // is ACTUALLY the label width. rename?
1062
+ clientWidth);
1063
+ this.slotWidth = slotWidth;
1064
+ /* sliced */
1065
+ 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...
1066
+ props.dateProfile, context.dateProfileGenerator, tDateProfile, context.dateEnv);
1067
+ return (preact.createElement(internal.NowTimer, { unit: timerUnit }, (nowDate, todayRange) => {
1068
+ const enableNowIndicator = // TODO: DRY
1069
+ options.nowIndicator &&
1070
+ slotWidth != null &&
1071
+ internal.rangeContainsMarker(props.dateProfile.currentRange, nowDate);
1072
+ return (preact.createElement(internal.ViewContainer, { viewSpec: context.viewSpec, className: internal.joinArrayishClassNames(
1073
+ // HACK for Safari print-mode, where noScrollbars won't take effect for
1074
+ // the below Scrollers if they have liquid flex height
1075
+ !props.forPrint && classNames__default["default"].flexCol, props.className, options.tableClass, classNames__default["default"].isolate), borderlessX: props.borderlessX, borderlessTop: props.borderlessTop, borderlessBottom: props.borderlessBottom, noEdgeEffects: props.noEdgeEffects },
1076
+ preact.createElement("div", { className: core.joinClassNames(internal.generateClassName(options.tableHeaderClass, {
1077
+ isSticky: stickyHeaderDates,
1078
+ }), props.borderlessX && classNames__default["default"].borderlessX, classNames__default["default"].flexCol, stickyHeaderDates && classNames__default["default"].tableHeaderSticky), style: {
1079
+ zIndex: 1,
1080
+ } },
1081
+ preact.createElement(internal.Scroller, { horizontal: true, hideScrollbars: true, className: classNames__default["default"].flexRow, ref: this.headerScrollerRef },
1082
+ preact.createElement("div", {
1083
+ // TODO: DRY
1084
+ className: core.joinClassNames(classNames__default["default"].rel, // origin for now-indicator
1085
+ canvasWidth == null && classNames__default["default"].liquid), style: { width: canvasWidth } },
1086
+ cellRows.map((cells, rowIndex) => {
1087
+ const rowLevel = cellRows.length - rowIndex - 1;
1088
+ return (preact.createElement(TimelineHeaderRow, { key: rowIndex, dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: nowDate, todayRange: todayRange, rowLevel: rowLevel, cells: cells, slotWidth: slotWidth, innerWidthRef: this.headerRowInnerWidthMap.createRef(rowIndex) }));
1089
+ }),
1090
+ enableNowIndicator && (preact.createElement(TimelineNowIndicatorArrow, { tDateProfile: tDateProfile, nowDate: nowDate, slotWidth: slotWidth }))),
1091
+ Boolean(endScrollbarWidth) && (preact.createElement("div", { className: internal.joinArrayishClassNames(internal.generateClassName(options.fillerClass, { isHeader: true }), classNames__default["default"].borderOnlyS), style: { minWidth: endScrollbarWidth } }))),
1092
+ preact.createElement("div", { className: internal.generateClassName(options.slotHeaderDividerClass, {
1093
+ isHeader: true,
1094
+ options: { dayMinWidth: options.dayMinWidth },
1095
+ }) })),
1096
+ preact.createElement(internal.Scroller, { vertical: verticalScrolling, horizontal: true, hideScrollbars: stickyFooterScrollbar ||
1097
+ props.forPrint // prevents blank space in print-view on Safari
1098
+ , className: internal.joinArrayishClassNames(options.tableBodyClass, props.borderlessX && classNames__default["default"].borderlessX, stickyHeaderDates && classNames__default["default"].borderlessTop, (stickyHeaderDates || props.noEdgeEffects) && classNames__default["default"].noEdgeEffects, classNames__default["default"].flexCol, verticalScrolling && classNames__default["default"].liquid), style: {
1099
+ zIndex: 0,
1100
+ }, ref: this.bodyScrollerRef, clientWidthRef: this.handleClientWidth },
1101
+ preact.createElement("div", { "aria-label": options.eventsHint, className: core.joinClassNames(classNames__default["default"].rel, // for canvas origin?
1102
+ classNames__default["default"].grow), style: { width: canvasWidth }, ref: this.handeBodyEl },
1103
+ preact.createElement(TimelineSlats, { dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: nowDate, todayRange: todayRange,
1104
+ // dimensions
1105
+ slotWidth: slotWidth }),
1106
+ preact.createElement(TimelineBg, { tDateProfile: tDateProfile, nowDate: nowDate, todayRange: todayRange,
1107
+ // content
1108
+ bgEventSegs: slicedProps.bgEventSegs, businessHourSegs: slicedProps.businessHourSegs, dateSelectionSegs: slicedProps.dateSelectionSegs, eventResizeSegs: slicedProps.eventResize ? slicedProps.eventResize.segs : null,
1109
+ // dimensions
1110
+ slotWidth: slotWidth }),
1111
+ preact.createElement("div", { className: internal.joinArrayishClassNames(options.timelineTopClass) }),
1112
+ preact.createElement(TimelineFg, { dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: nowDate, todayRange: todayRange,
1113
+ // content
1114
+ fgEventSegs: slicedProps.fgEventSegs, eventDrag: slicedProps.eventDrag, eventResize: slicedProps.eventResize, eventSelection: slicedProps.eventSelection,
1115
+ // dimensions
1116
+ slotWidth: slotWidth }),
1117
+ preact.createElement("div", { className: internal.joinArrayishClassNames(options.timelineBottomClass) }),
1118
+ enableNowIndicator && (preact.createElement(TimelineNowIndicatorLine, { tDateProfile: tDateProfile, nowDate: nowDate, slotWidth: slotWidth })))),
1119
+ Boolean(stickyFooterScrollbar) && (preact.createElement(internal.FooterScrollbar, { isSticky: true, canvasWidth: canvasWidth, scrollerRef: this.footerScrollerRef })),
1120
+ preact.createElement(internal.Ruler, { widthRef: this.handleTotalWidth })));
1121
+ }));
1122
+ }
1123
+ // Lifecycle
1124
+ // -----------------------------------------------------------------------------------------------
1125
+ componentDidMount() {
1126
+ this.syncedScroller = new internal$1.ScrollerSyncer(true); // horizontal=true
1127
+ this.updateSyncedScroller();
1128
+ this.resetScroll();
1129
+ this.context.emitter.on('_timeScrollRequest', this.handleTimeScrollRequest);
1130
+ this.syncedScroller.addScrollEndListener(this.handleTimeScrollEnd);
1131
+ }
1132
+ componentDidUpdate(prevProps) {
1133
+ this.updateSyncedScroller();
1134
+ if (prevProps.dateProfile !== this.props.dateProfile && this.context.options.scrollTimeReset) {
1135
+ this.resetScroll();
1136
+ }
1137
+ else {
1138
+ // TODO: inefficient to update so often
1139
+ this.applyTimeScroll();
1140
+ }
1141
+ }
1142
+ componentWillUnmount() {
1143
+ this.syncedScroller.destroy();
1144
+ this.context.emitter.off('_timeScrollRequest', this.handleTimeScrollRequest);
1145
+ this.syncedScroller.removeScrollEndListener(this.handleTimeScrollEnd);
1146
+ }
1147
+ // Scrolling
1148
+ // -----------------------------------------------------------------------------------------------
1149
+ updateSyncedScroller() {
1150
+ this.syncedScroller.handleChildren([
1151
+ this.headerScrollerRef.current,
1152
+ this.bodyScrollerRef.current,
1153
+ this.footerScrollerRef.current
1154
+ ]);
1155
+ }
1156
+ resetScroll() {
1157
+ this.handleTimeScrollRequest(this.context.options.scrollTime);
1158
+ }
1159
+ applyTimeScroll() {
1160
+ const { props, context, tDateProfile, scrollTime, slotWidth } = this;
1161
+ if (scrollTime != null && slotWidth != null) {
1162
+ let x = timeToCoord(scrollTime, context.dateEnv, props.dateProfile, tDateProfile, slotWidth);
1163
+ if (x) {
1164
+ x += 1; // overcome border. TODO: DRY this up
1165
+ }
1166
+ this.syncedScroller.scrollTo({ x });
1167
+ }
1168
+ }
1169
+ queryHit(isRtl, positionLeft, positionTop, elWidth, elHeight) {
1170
+ const { props, context, tDateProfile, slotWidth } = this;
1171
+ const { dateEnv } = context;
1172
+ if (slotWidth) {
1173
+ const x = isRtl ? elWidth - positionLeft : positionLeft;
1174
+ const slatIndex = Math.floor(x / slotWidth);
1175
+ const slatX = slatIndex * slotWidth;
1176
+ const partial = (x - slatX) / slotWidth; // floating point number between 0 and 1
1177
+ const localSnapIndex = Math.floor(partial * tDateProfile.snapsPerSlot); // the snap # relative to start of slat
1178
+ let startDate = dateEnv.add(tDateProfile.slotDates[slatIndex], internal.multiplyDuration(tDateProfile.snapDuration, localSnapIndex));
1179
+ let endDate = dateEnv.add(startDate, tDateProfile.snapDuration);
1180
+ // TODO: generalize this coord stuff to TimeGrid?
1181
+ let snapWidth = slotWidth / tDateProfile.snapsPerSlot;
1182
+ let startCoord = slatIndex * slotWidth + (snapWidth * localSnapIndex);
1183
+ let endCoord = startCoord + snapWidth;
1184
+ let left, right;
1185
+ if (isRtl) {
1186
+ left = elWidth - endCoord;
1187
+ right = elWidth - startCoord;
1188
+ }
1189
+ else {
1190
+ left = startCoord;
1191
+ right = endCoord;
1192
+ }
1193
+ return {
1194
+ dateProfile: props.dateProfile,
1195
+ dateSpan: {
1196
+ range: { start: startDate, end: endDate },
1197
+ allDay: !tDateProfile.isTimeScale,
1198
+ },
1199
+ rect: {
1200
+ left,
1201
+ right,
1202
+ top: 0,
1203
+ bottom: elHeight,
1204
+ },
1205
+ getDayEl: () => getTimelineSlotEl(this.bodyEl, slatIndex),
1206
+ layer: 0,
1207
+ };
1208
+ }
1209
+ return null;
1210
+ }
1211
+ }
1212
+
1213
+ exports.TimelineBg = TimelineBg;
1214
+ exports.TimelineFg = TimelineFg;
1215
+ exports.TimelineHeaderRow = TimelineHeaderRow;
1216
+ exports.TimelineLaneSlicer = TimelineLaneSlicer;
1217
+ exports.TimelineNowIndicatorArrow = TimelineNowIndicatorArrow;
1218
+ exports.TimelineNowIndicatorLine = TimelineNowIndicatorLine;
1219
+ exports.TimelineSlats = TimelineSlats;
1220
+ exports.TimelineView = TimelineView;
1221
+ exports.buildTimelineDateProfile = buildTimelineDateProfile;
1222
+ exports.computeSlotWidth = computeSlotWidth;
1223
+ exports.getTimelineSlotEl = getTimelineSlotEl;
1224
+ exports.timeToCoord = timeToCoord;