1 | import { Splitter, hasBgRendering, createFormatter, ViewContextType, ContentContainer, BaseComponent, DateComponent, diffDays, buildNavLinkAttrs, WeekNumberContainer, getStickyHeaderDates, ViewContainer, SimpleScrollGrid, getStickyFooterScrollbar, NowTimer, NowIndicatorContainer, renderScrollShim, rangeContainsMarker, startOfDay, asRoughMs, createDuration, RefMap, PositionCache, MoreLinkContainer, SegHierarchy, groupIntersectingEntries, binarySearch, getEntrySpanEnd, buildEntryKey, StandardEvent, memoize, sortEventSegs, DayCellContainer, hasCustomDayCellContent, getSegMeta, buildIsoString, computeEarliestSegStart, buildEventRangeKey, BgEvent, renderFill, addDurations, multiplyDuration, wholeDivideDurations, Slicer, intersectRanges, formatIsoTimeString, DayHeader, DaySeriesModel, DayTableModel } from '@fullcalendar/core/internal';
|
2 | import { createElement, createRef, Fragment } from '@fullcalendar/core/preact';
|
3 | import { DayTable } from '@fullcalendar/daygrid/internal';
|
4 |
|
5 | class AllDaySplitter extends Splitter {
|
6 | getKeyInfo() {
|
7 | return {
|
8 | allDay: {},
|
9 | timed: {},
|
10 | };
|
11 | }
|
12 | getKeysForDateSpan(dateSpan) {
|
13 | if (dateSpan.allDay) {
|
14 | return ['allDay'];
|
15 | }
|
16 | return ['timed'];
|
17 | }
|
18 | getKeysForEventDef(eventDef) {
|
19 | if (!eventDef.allDay) {
|
20 | return ['timed'];
|
21 | }
|
22 | if (hasBgRendering(eventDef)) {
|
23 | return ['timed', 'allDay'];
|
24 | }
|
25 | return ['allDay'];
|
26 | }
|
27 | }
|
28 |
|
29 | const DEFAULT_SLAT_LABEL_FORMAT = createFormatter({
|
30 | hour: 'numeric',
|
31 | minute: '2-digit',
|
32 | omitZeroMinute: true,
|
33 | meridiem: 'short',
|
34 | });
|
35 | function TimeColsAxisCell(props) {
|
36 | let classNames = [
|
37 | 'fc-timegrid-slot',
|
38 | 'fc-timegrid-slot-label',
|
39 | props.isLabeled ? 'fc-scrollgrid-shrink' : 'fc-timegrid-slot-minor',
|
40 | ];
|
41 | return (createElement(ViewContextType.Consumer, null, (context) => {
|
42 | if (!props.isLabeled) {
|
43 | return (createElement("td", { className: classNames.join(' '), "data-time": props.isoTimeStr }));
|
44 | }
|
45 | let { dateEnv, options, viewApi } = context;
|
46 | let labelFormat =
|
47 | options.slotLabelFormat == null ? DEFAULT_SLAT_LABEL_FORMAT :
|
48 | Array.isArray(options.slotLabelFormat) ? createFormatter(options.slotLabelFormat[0]) :
|
49 | createFormatter(options.slotLabelFormat);
|
50 | let renderProps = {
|
51 | level: 0,
|
52 | time: props.time,
|
53 | date: dateEnv.toDate(props.date),
|
54 | view: viewApi,
|
55 | text: dateEnv.format(props.date, labelFormat),
|
56 | };
|
57 | return (createElement(ContentContainer, { elTag: "td", elClasses: classNames, elAttrs: {
|
58 | 'data-time': props.isoTimeStr,
|
59 | }, renderProps: renderProps, generatorName: "slotLabelContent", customGenerator: options.slotLabelContent, defaultGenerator: renderInnerContent, classNameGenerator: options.slotLabelClassNames, didMount: options.slotLabelDidMount, willUnmount: options.slotLabelWillUnmount }, (InnerContent) => (createElement("div", { className: "fc-timegrid-slot-label-frame fc-scrollgrid-shrink-frame" },
|
60 | createElement(InnerContent, { elTag: "div", elClasses: [
|
61 | 'fc-timegrid-slot-label-cushion',
|
62 | 'fc-scrollgrid-shrink-cushion',
|
63 | ] })))));
|
64 | }));
|
65 | }
|
66 | function renderInnerContent(props) {
|
67 | return props.text;
|
68 | }
|
69 |
|
70 | class TimeBodyAxis extends BaseComponent {
|
71 | render() {
|
72 | return this.props.slatMetas.map((slatMeta) => (createElement("tr", { key: slatMeta.key },
|
73 | createElement(TimeColsAxisCell, Object.assign({}, slatMeta)))));
|
74 | }
|
75 | }
|
76 |
|
77 | const DEFAULT_WEEK_NUM_FORMAT = createFormatter({ week: 'short' });
|
78 | const AUTO_ALL_DAY_MAX_EVENT_ROWS = 5;
|
79 | class TimeColsView extends DateComponent {
|
80 | constructor() {
|
81 | super(...arguments);
|
82 | this.allDaySplitter = new AllDaySplitter();
|
83 | this.headerElRef = createRef();
|
84 | this.rootElRef = createRef();
|
85 | this.scrollerElRef = createRef();
|
86 | this.state = {
|
87 | slatCoords: null,
|
88 | };
|
89 | this.handleScrollTopRequest = (scrollTop) => {
|
90 | let scrollerEl = this.scrollerElRef.current;
|
91 | if (scrollerEl) {
|
92 | scrollerEl.scrollTop = scrollTop;
|
93 | }
|
94 | };
|
95 | |
96 |
|
97 | this.renderHeadAxis = (rowKey, frameHeight = '') => {
|
98 | let { options } = this.context;
|
99 | let { dateProfile } = this.props;
|
100 | let range = dateProfile.renderRange;
|
101 | let dayCnt = diffDays(range.start, range.end);
|
102 |
|
103 | let navLinkAttrs = (dayCnt === 1)
|
104 | ? buildNavLinkAttrs(this.context, range.start, 'week')
|
105 | : {};
|
106 | if (options.weekNumbers && rowKey === 'day') {
|
107 | return (createElement(WeekNumberContainer, { elTag: "th", elClasses: [
|
108 | 'fc-timegrid-axis',
|
109 | 'fc-scrollgrid-shrink',
|
110 | ], elAttrs: {
|
111 | 'aria-hidden': true,
|
112 | }, date: range.start, defaultFormat: DEFAULT_WEEK_NUM_FORMAT }, (InnerContent) => (createElement("div", { className: [
|
113 | 'fc-timegrid-axis-frame',
|
114 | 'fc-scrollgrid-shrink-frame',
|
115 | 'fc-timegrid-axis-frame-liquid',
|
116 | ].join(' '), style: { height: frameHeight } },
|
117 | createElement(InnerContent, { elTag: "a", elClasses: [
|
118 | 'fc-timegrid-axis-cushion',
|
119 | 'fc-scrollgrid-shrink-cushion',
|
120 | 'fc-scrollgrid-sync-inner',
|
121 | ], elAttrs: navLinkAttrs })))));
|
122 | }
|
123 | return (createElement("th", { "aria-hidden": true, className: "fc-timegrid-axis" },
|
124 | createElement("div", { className: "fc-timegrid-axis-frame", style: { height: frameHeight } })));
|
125 | };
|
126 | |
127 |
|
128 |
|
129 |
|
130 | this.renderTableRowAxis = (rowHeight) => {
|
131 | let { options, viewApi } = this.context;
|
132 | let renderProps = {
|
133 | text: options.allDayText,
|
134 | view: viewApi,
|
135 | };
|
136 | return (
|
137 |
|
138 | createElement(ContentContainer, { elTag: "td", elClasses: [
|
139 | 'fc-timegrid-axis',
|
140 | 'fc-scrollgrid-shrink',
|
141 | ], elAttrs: {
|
142 | 'aria-hidden': true,
|
143 | }, renderProps: renderProps, generatorName: "allDayContent", customGenerator: options.allDayContent, defaultGenerator: renderAllDayInner, classNameGenerator: options.allDayClassNames, didMount: options.allDayDidMount, willUnmount: options.allDayWillUnmount }, (InnerContent) => (createElement("div", { className: [
|
144 | 'fc-timegrid-axis-frame',
|
145 | 'fc-scrollgrid-shrink-frame',
|
146 | rowHeight == null ? ' fc-timegrid-axis-frame-liquid' : '',
|
147 | ].join(' '), style: { height: rowHeight } },
|
148 | createElement(InnerContent, { elTag: "span", elClasses: [
|
149 | 'fc-timegrid-axis-cushion',
|
150 | 'fc-scrollgrid-shrink-cushion',
|
151 | 'fc-scrollgrid-sync-inner',
|
152 | ] })))));
|
153 | };
|
154 | this.handleSlatCoords = (slatCoords) => {
|
155 | this.setState({ slatCoords });
|
156 | };
|
157 | }
|
158 |
|
159 |
|
160 | renderSimpleLayout(headerRowContent, allDayContent, timeContent) {
|
161 | let { context, props } = this;
|
162 | let sections = [];
|
163 | let stickyHeaderDates = getStickyHeaderDates(context.options);
|
164 | if (headerRowContent) {
|
165 | sections.push({
|
166 | type: 'header',
|
167 | key: 'header',
|
168 | isSticky: stickyHeaderDates,
|
169 | chunk: {
|
170 | elRef: this.headerElRef,
|
171 | tableClassName: 'fc-col-header',
|
172 | rowContent: headerRowContent,
|
173 | },
|
174 | });
|
175 | }
|
176 | if (allDayContent) {
|
177 | sections.push({
|
178 | type: 'body',
|
179 | key: 'all-day',
|
180 | chunk: { content: allDayContent },
|
181 | });
|
182 | sections.push({
|
183 | type: 'body',
|
184 | key: 'all-day-divider',
|
185 | outerContent: (
|
186 | createElement("tr", { role: "presentation", className: "fc-scrollgrid-section" },
|
187 | createElement("td", { className: 'fc-timegrid-divider ' + context.theme.getClass('tableCellShaded') }))),
|
188 | });
|
189 | }
|
190 | sections.push({
|
191 | type: 'body',
|
192 | key: 'body',
|
193 | liquid: true,
|
194 | expandRows: Boolean(context.options.expandRows),
|
195 | chunk: {
|
196 | scrollerElRef: this.scrollerElRef,
|
197 | content: timeContent,
|
198 | },
|
199 | });
|
200 | return (createElement(ViewContainer, { elRef: this.rootElRef, elClasses: ['fc-timegrid'], viewSpec: context.viewSpec },
|
201 | createElement(SimpleScrollGrid, { liquid: !props.isHeightAuto && !props.forPrint, collapsibleWidth: props.forPrint, cols: [{ width: 'shrink' }], sections: sections })));
|
202 | }
|
203 | renderHScrollLayout(headerRowContent, allDayContent, timeContent, colCnt, dayMinWidth, slatMetas, slatCoords) {
|
204 | let ScrollGrid = this.context.pluginHooks.scrollGridImpl;
|
205 | if (!ScrollGrid) {
|
206 | throw new Error('No ScrollGrid implementation');
|
207 | }
|
208 | let { context, props } = this;
|
209 | let stickyHeaderDates = !props.forPrint && getStickyHeaderDates(context.options);
|
210 | let stickyFooterScrollbar = !props.forPrint && getStickyFooterScrollbar(context.options);
|
211 | let sections = [];
|
212 | if (headerRowContent) {
|
213 | sections.push({
|
214 | type: 'header',
|
215 | key: 'header',
|
216 | isSticky: stickyHeaderDates,
|
217 | syncRowHeights: true,
|
218 | chunks: [
|
219 | {
|
220 | key: 'axis',
|
221 | rowContent: (arg) => (createElement("tr", { role: "presentation" }, this.renderHeadAxis('day', arg.rowSyncHeights[0]))),
|
222 | },
|
223 | {
|
224 | key: 'cols',
|
225 | elRef: this.headerElRef,
|
226 | tableClassName: 'fc-col-header',
|
227 | rowContent: headerRowContent,
|
228 | },
|
229 | ],
|
230 | });
|
231 | }
|
232 | if (allDayContent) {
|
233 | sections.push({
|
234 | type: 'body',
|
235 | key: 'all-day',
|
236 | syncRowHeights: true,
|
237 | chunks: [
|
238 | {
|
239 | key: 'axis',
|
240 | rowContent: (contentArg) => (createElement("tr", { role: "presentation" }, this.renderTableRowAxis(contentArg.rowSyncHeights[0]))),
|
241 | },
|
242 | {
|
243 | key: 'cols',
|
244 | content: allDayContent,
|
245 | },
|
246 | ],
|
247 | });
|
248 | sections.push({
|
249 | key: 'all-day-divider',
|
250 | type: 'body',
|
251 | outerContent: (
|
252 | createElement("tr", { role: "presentation", className: "fc-scrollgrid-section" },
|
253 | createElement("td", { colSpan: 2, className: 'fc-timegrid-divider ' + context.theme.getClass('tableCellShaded') }))),
|
254 | });
|
255 | }
|
256 | let isNowIndicator = context.options.nowIndicator;
|
257 | sections.push({
|
258 | type: 'body',
|
259 | key: 'body',
|
260 | liquid: true,
|
261 | expandRows: Boolean(context.options.expandRows),
|
262 | chunks: [
|
263 | {
|
264 | key: 'axis',
|
265 | content: (arg) => (
|
266 |
|
267 | createElement("div", { className: "fc-timegrid-axis-chunk" },
|
268 | createElement("table", { "aria-hidden": true, style: { height: arg.expandRows ? arg.clientHeight : '' } },
|
269 | arg.tableColGroupNode,
|
270 | createElement("tbody", null,
|
271 | createElement(TimeBodyAxis, { slatMetas: slatMetas }))),
|
272 | createElement("div", { className: "fc-timegrid-now-indicator-container" },
|
273 | createElement(NowTimer, { unit: isNowIndicator ? 'minute' : 'day' }, (nowDate) => {
|
274 | let nowIndicatorTop = isNowIndicator &&
|
275 | slatCoords &&
|
276 | slatCoords.safeComputeTop(nowDate);
|
277 | if (typeof nowIndicatorTop === 'number') {
|
278 | return (createElement(NowIndicatorContainer, { elClasses: ['fc-timegrid-now-indicator-arrow'], elStyle: { top: nowIndicatorTop }, isAxis: true, date: nowDate }));
|
279 | }
|
280 | return null;
|
281 | })))),
|
282 | },
|
283 | {
|
284 | key: 'cols',
|
285 | scrollerElRef: this.scrollerElRef,
|
286 | content: timeContent,
|
287 | },
|
288 | ],
|
289 | });
|
290 | if (stickyFooterScrollbar) {
|
291 | sections.push({
|
292 | key: 'footer',
|
293 | type: 'footer',
|
294 | isSticky: true,
|
295 | chunks: [
|
296 | {
|
297 | key: 'axis',
|
298 | content: renderScrollShim,
|
299 | },
|
300 | {
|
301 | key: 'cols',
|
302 | content: renderScrollShim,
|
303 | },
|
304 | ],
|
305 | });
|
306 | }
|
307 | return (createElement(ViewContainer, { elRef: this.rootElRef, elClasses: ['fc-timegrid'], viewSpec: context.viewSpec },
|
308 | createElement(ScrollGrid, { liquid: !props.isHeightAuto && !props.forPrint, collapsibleWidth: false, colGroups: [
|
309 | { width: 'shrink', cols: [{ width: 'shrink' }] },
|
310 | { cols: [{ span: colCnt, minWidth: dayMinWidth }] },
|
311 | ], sections: sections })));
|
312 | }
|
313 | |
314 |
|
315 | getAllDayMaxEventProps() {
|
316 | let { dayMaxEvents, dayMaxEventRows } = this.context.options;
|
317 | if (dayMaxEvents === true || dayMaxEventRows === true) {
|
318 | dayMaxEvents = undefined;
|
319 | dayMaxEventRows = AUTO_ALL_DAY_MAX_EVENT_ROWS;
|
320 | }
|
321 | return { dayMaxEvents, dayMaxEventRows };
|
322 | }
|
323 | }
|
324 | function renderAllDayInner(renderProps) {
|
325 | return renderProps.text;
|
326 | }
|
327 |
|
328 | class TimeColsSlatsCoords {
|
329 | constructor(positions, dateProfile, slotDuration) {
|
330 | this.positions = positions;
|
331 | this.dateProfile = dateProfile;
|
332 | this.slotDuration = slotDuration;
|
333 | }
|
334 | safeComputeTop(date) {
|
335 | let { dateProfile } = this;
|
336 | if (rangeContainsMarker(dateProfile.currentRange, date)) {
|
337 | let startOfDayDate = startOfDay(date);
|
338 | let timeMs = date.valueOf() - startOfDayDate.valueOf();
|
339 | if (timeMs >= asRoughMs(dateProfile.slotMinTime) &&
|
340 | timeMs < asRoughMs(dateProfile.slotMaxTime)) {
|
341 | return this.computeTimeTop(createDuration(timeMs));
|
342 | }
|
343 | }
|
344 | return null;
|
345 | }
|
346 |
|
347 |
|
348 | computeDateTop(when, startOfDayDate) {
|
349 | if (!startOfDayDate) {
|
350 | startOfDayDate = startOfDay(when);
|
351 | }
|
352 | return this.computeTimeTop(createDuration(when.valueOf() - startOfDayDate.valueOf()));
|
353 | }
|
354 |
|
355 |
|
356 |
|
357 | computeTimeTop(duration) {
|
358 | let { positions, dateProfile } = this;
|
359 | let len = positions.els.length;
|
360 |
|
361 | let slatCoverage = (duration.milliseconds - asRoughMs(dateProfile.slotMinTime)) / asRoughMs(this.slotDuration);
|
362 | let slatIndex;
|
363 | let slatRemainder;
|
364 |
|
365 |
|
366 |
|
367 | slatCoverage = Math.max(0, slatCoverage);
|
368 | slatCoverage = Math.min(len, slatCoverage);
|
369 |
|
370 |
|
371 | slatIndex = Math.floor(slatCoverage);
|
372 | slatIndex = Math.min(slatIndex, len - 1);
|
373 |
|
374 |
|
375 | slatRemainder = slatCoverage - slatIndex;
|
376 | return positions.tops[slatIndex] +
|
377 | positions.getHeight(slatIndex) * slatRemainder;
|
378 | }
|
379 | }
|
380 |
|
381 | class TimeColsSlatsBody extends BaseComponent {
|
382 | render() {
|
383 | let { props, context } = this;
|
384 | let { options } = context;
|
385 | let { slatElRefs } = props;
|
386 | return (createElement("tbody", null, props.slatMetas.map((slatMeta, i) => {
|
387 | let renderProps = {
|
388 | time: slatMeta.time,
|
389 | date: context.dateEnv.toDate(slatMeta.date),
|
390 | view: context.viewApi,
|
391 | };
|
392 | return (createElement("tr", { key: slatMeta.key, ref: slatElRefs.createRef(slatMeta.key) },
|
393 | props.axis && (createElement(TimeColsAxisCell, Object.assign({}, slatMeta))),
|
394 | createElement(ContentContainer, { elTag: "td", elClasses: [
|
395 | 'fc-timegrid-slot',
|
396 | 'fc-timegrid-slot-lane',
|
397 | !slatMeta.isLabeled && 'fc-timegrid-slot-minor',
|
398 | ], elAttrs: {
|
399 | 'data-time': slatMeta.isoTimeStr,
|
400 | }, renderProps: renderProps, generatorName: "slotLaneContent", customGenerator: options.slotLaneContent, classNameGenerator: options.slotLaneClassNames, didMount: options.slotLaneDidMount, willUnmount: options.slotLaneWillUnmount })));
|
401 | })));
|
402 | }
|
403 | }
|
404 |
|
405 |
|
406 |
|
407 |
|
408 | class TimeColsSlats extends BaseComponent {
|
409 | constructor() {
|
410 | super(...arguments);
|
411 | this.rootElRef = createRef();
|
412 | this.slatElRefs = new RefMap();
|
413 | }
|
414 | render() {
|
415 | let { props, context } = this;
|
416 | return (createElement("div", { ref: this.rootElRef, className: "fc-timegrid-slots" },
|
417 | createElement("table", { "aria-hidden": true, className: context.theme.getClass('table'), style: {
|
418 | minWidth: props.tableMinWidth,
|
419 | width: props.clientWidth,
|
420 | height: props.minHeight,
|
421 | } },
|
422 | props.tableColGroupNode ,
|
423 | createElement(TimeColsSlatsBody, { slatElRefs: this.slatElRefs, axis: props.axis, slatMetas: props.slatMetas }))));
|
424 | }
|
425 | componentDidMount() {
|
426 | this.updateSizing();
|
427 | }
|
428 | componentDidUpdate() {
|
429 | this.updateSizing();
|
430 | }
|
431 | componentWillUnmount() {
|
432 | if (this.props.onCoords) {
|
433 | this.props.onCoords(null);
|
434 | }
|
435 | }
|
436 | updateSizing() {
|
437 | let { context, props } = this;
|
438 | if (props.onCoords &&
|
439 | props.clientWidth !== null
|
440 | ) {
|
441 | let rootEl = this.rootElRef.current;
|
442 | if (rootEl.offsetHeight) {
|
443 | props.onCoords(new TimeColsSlatsCoords(new PositionCache(this.rootElRef.current, collectSlatEls(this.slatElRefs.currentMap, props.slatMetas), false, true), this.props.dateProfile, context.options.slotDuration));
|
444 | }
|
445 | }
|
446 | }
|
447 | }
|
448 | function collectSlatEls(elMap, slatMetas) {
|
449 | return slatMetas.map((slatMeta) => elMap[slatMeta.key]);
|
450 | }
|
451 |
|
452 | function splitSegsByCol(segs, colCnt) {
|
453 | let segsByCol = [];
|
454 | let i;
|
455 | for (i = 0; i < colCnt; i += 1) {
|
456 | segsByCol.push([]);
|
457 | }
|
458 | if (segs) {
|
459 | for (i = 0; i < segs.length; i += 1) {
|
460 | segsByCol[segs[i].col].push(segs[i]);
|
461 | }
|
462 | }
|
463 | return segsByCol;
|
464 | }
|
465 | function splitInteractionByCol(ui, colCnt) {
|
466 | let byRow = [];
|
467 | if (!ui) {
|
468 | for (let i = 0; i < colCnt; i += 1) {
|
469 | byRow[i] = null;
|
470 | }
|
471 | }
|
472 | else {
|
473 | for (let i = 0; i < colCnt; i += 1) {
|
474 | byRow[i] = {
|
475 | affectedInstances: ui.affectedInstances,
|
476 | isEvent: ui.isEvent,
|
477 | segs: [],
|
478 | };
|
479 | }
|
480 | for (let seg of ui.segs) {
|
481 | byRow[seg.col].segs.push(seg);
|
482 | }
|
483 | }
|
484 | return byRow;
|
485 | }
|
486 |
|
487 | class TimeColMoreLink extends BaseComponent {
|
488 | render() {
|
489 | let { props } = this;
|
490 | return (createElement(MoreLinkContainer, { elClasses: ['fc-timegrid-more-link'], elStyle: {
|
491 | top: props.top,
|
492 | bottom: props.bottom,
|
493 | }, allDayDate: null, moreCnt: props.hiddenSegs.length, allSegs: props.hiddenSegs, hiddenSegs: props.hiddenSegs, extraDateSpan: props.extraDateSpan, dateProfile: props.dateProfile, todayRange: props.todayRange, popoverContent: () => renderPlainFgSegs(props.hiddenSegs, props), defaultGenerator: renderMoreLinkInner }, (InnerContent) => (createElement(InnerContent, { elTag: "div", elClasses: ['fc-timegrid-more-link-inner', 'fc-sticky'] }))));
|
494 | }
|
495 | }
|
496 | function renderMoreLinkInner(props) {
|
497 | return props.shortText;
|
498 | }
|
499 |
|
500 |
|
501 | function buildPositioning(segInputs, strictOrder, maxStackCnt) {
|
502 | let hierarchy = new SegHierarchy();
|
503 | if (strictOrder != null) {
|
504 | hierarchy.strictOrder = strictOrder;
|
505 | }
|
506 | if (maxStackCnt != null) {
|
507 | hierarchy.maxStackCnt = maxStackCnt;
|
508 | }
|
509 | let hiddenEntries = hierarchy.addSegs(segInputs);
|
510 | let hiddenGroups = groupIntersectingEntries(hiddenEntries);
|
511 | let web = buildWeb(hierarchy);
|
512 | web = stretchWeb(web, 1);
|
513 | let segRects = webToRects(web);
|
514 | return { segRects, hiddenGroups };
|
515 | }
|
516 | function buildWeb(hierarchy) {
|
517 | const { entriesByLevel } = hierarchy;
|
518 | const buildNode = cacheable((level, lateral) => level + ':' + lateral, (level, lateral) => {
|
519 | let siblingRange = findNextLevelSegs(hierarchy, level, lateral);
|
520 | let nextLevelRes = buildNodes(siblingRange, buildNode);
|
521 | let entry = entriesByLevel[level][lateral];
|
522 | return [
|
523 | Object.assign(Object.assign({}, entry), { nextLevelNodes: nextLevelRes[0] }),
|
524 | entry.thickness + nextLevelRes[1],
|
525 | ];
|
526 | });
|
527 | return buildNodes(entriesByLevel.length
|
528 | ? { level: 0, lateralStart: 0, lateralEnd: entriesByLevel[0].length }
|
529 | : null, buildNode)[0];
|
530 | }
|
531 | function buildNodes(siblingRange, buildNode) {
|
532 | if (!siblingRange) {
|
533 | return [[], 0];
|
534 | }
|
535 | let { level, lateralStart, lateralEnd } = siblingRange;
|
536 | let lateral = lateralStart;
|
537 | let pairs = [];
|
538 | while (lateral < lateralEnd) {
|
539 | pairs.push(buildNode(level, lateral));
|
540 | lateral += 1;
|
541 | }
|
542 | pairs.sort(cmpDescPressures);
|
543 | return [
|
544 | pairs.map(extractNode),
|
545 | pairs[0][1],
|
546 | ];
|
547 | }
|
548 | function cmpDescPressures(a, b) {
|
549 | return b[1] - a[1];
|
550 | }
|
551 | function extractNode(a) {
|
552 | return a[0];
|
553 | }
|
554 | function findNextLevelSegs(hierarchy, subjectLevel, subjectLateral) {
|
555 | let { levelCoords, entriesByLevel } = hierarchy;
|
556 | let subjectEntry = entriesByLevel[subjectLevel][subjectLateral];
|
557 | let afterSubject = levelCoords[subjectLevel] + subjectEntry.thickness;
|
558 | let levelCnt = levelCoords.length;
|
559 | let level = subjectLevel;
|
560 |
|
561 | for (; level < levelCnt && levelCoords[level] < afterSubject; level += 1)
|
562 | ;
|
563 | for (; level < levelCnt; level += 1) {
|
564 | let entries = entriesByLevel[level];
|
565 | let entry;
|
566 | let searchIndex = binarySearch(entries, subjectEntry.span.start, getEntrySpanEnd);
|
567 | let lateralStart = searchIndex[0] + searchIndex[1];
|
568 | let lateralEnd = lateralStart;
|
569 | while (
|
570 | (entry = entries[lateralEnd]) &&
|
571 | entry.span.start < subjectEntry.span.end) {
|
572 | lateralEnd += 1;
|
573 | }
|
574 | if (lateralStart < lateralEnd) {
|
575 | return { level, lateralStart, lateralEnd };
|
576 | }
|
577 | }
|
578 | return null;
|
579 | }
|
580 | function stretchWeb(topLevelNodes, totalThickness) {
|
581 | const stretchNode = cacheable((node, startCoord, prevThickness) => buildEntryKey(node), (node, startCoord, prevThickness) => {
|
582 | let { nextLevelNodes, thickness } = node;
|
583 | let allThickness = thickness + prevThickness;
|
584 | let thicknessFraction = thickness / allThickness;
|
585 | let endCoord;
|
586 | let newChildren = [];
|
587 | if (!nextLevelNodes.length) {
|
588 | endCoord = totalThickness;
|
589 | }
|
590 | else {
|
591 | for (let childNode of nextLevelNodes) {
|
592 | if (endCoord === undefined) {
|
593 | let res = stretchNode(childNode, startCoord, allThickness);
|
594 | endCoord = res[0];
|
595 | newChildren.push(res[1]);
|
596 | }
|
597 | else {
|
598 | let res = stretchNode(childNode, endCoord, 0);
|
599 | newChildren.push(res[1]);
|
600 | }
|
601 | }
|
602 | }
|
603 | let newThickness = (endCoord - startCoord) * thicknessFraction;
|
604 | return [endCoord - newThickness, Object.assign(Object.assign({}, node), { thickness: newThickness, nextLevelNodes: newChildren })];
|
605 | });
|
606 | return topLevelNodes.map((node) => stretchNode(node, 0, 0)[1]);
|
607 | }
|
608 |
|
609 | function webToRects(topLevelNodes) {
|
610 | let rects = [];
|
611 | const processNode = cacheable((node, levelCoord, stackDepth) => buildEntryKey(node), (node, levelCoord, stackDepth) => {
|
612 | let rect = Object.assign(Object.assign({}, node), { levelCoord,
|
613 | stackDepth, stackForward: 0 });
|
614 | rects.push(rect);
|
615 | return (rect.stackForward = processNodes(node.nextLevelNodes, levelCoord + node.thickness, stackDepth + 1) + 1);
|
616 | });
|
617 | function processNodes(nodes, levelCoord, stackDepth) {
|
618 | let stackForward = 0;
|
619 | for (let node of nodes) {
|
620 | stackForward = Math.max(processNode(node, levelCoord, stackDepth), stackForward);
|
621 | }
|
622 | return stackForward;
|
623 | }
|
624 | processNodes(topLevelNodes, 0, 0);
|
625 | return rects;
|
626 | }
|
627 |
|
628 | function cacheable(keyFunc, workFunc) {
|
629 | const cache = {};
|
630 | return (...args) => {
|
631 | let key = keyFunc(...args);
|
632 | return (key in cache)
|
633 | ? cache[key]
|
634 | : (cache[key] = workFunc(...args));
|
635 | };
|
636 | }
|
637 |
|
638 | function computeSegVCoords(segs, colDate, slatCoords = null, eventMinHeight = 0) {
|
639 | let vcoords = [];
|
640 | if (slatCoords) {
|
641 | for (let i = 0; i < segs.length; i += 1) {
|
642 | let seg = segs[i];
|
643 | let spanStart = slatCoords.computeDateTop(seg.start, colDate);
|
644 | let spanEnd = Math.max(spanStart + (eventMinHeight || 0),
|
645 | slatCoords.computeDateTop(seg.end, colDate));
|
646 | vcoords.push({
|
647 | start: Math.round(spanStart),
|
648 | end: Math.round(spanEnd),
|
649 | });
|
650 | }
|
651 | }
|
652 | return vcoords;
|
653 | }
|
654 | function computeFgSegPlacements(segs, segVCoords, // might not have for every seg
|
655 | eventOrderStrict, eventMaxStack) {
|
656 | let segInputs = [];
|
657 | let dumbSegs = [];
|
658 | for (let i = 0; i < segs.length; i += 1) {
|
659 | let vcoords = segVCoords[i];
|
660 | if (vcoords) {
|
661 | segInputs.push({
|
662 | index: i,
|
663 | thickness: 1,
|
664 | span: vcoords,
|
665 | });
|
666 | }
|
667 | else {
|
668 | dumbSegs.push(segs[i]);
|
669 | }
|
670 | }
|
671 | let { segRects, hiddenGroups } = buildPositioning(segInputs, eventOrderStrict, eventMaxStack);
|
672 | let segPlacements = [];
|
673 | for (let segRect of segRects) {
|
674 | segPlacements.push({
|
675 | seg: segs[segRect.index],
|
676 | rect: segRect,
|
677 | });
|
678 | }
|
679 | for (let dumbSeg of dumbSegs) {
|
680 | segPlacements.push({ seg: dumbSeg, rect: null });
|
681 | }
|
682 | return { segPlacements, hiddenGroups };
|
683 | }
|
684 |
|
685 | const DEFAULT_TIME_FORMAT = createFormatter({
|
686 | hour: 'numeric',
|
687 | minute: '2-digit',
|
688 | meridiem: false,
|
689 | });
|
690 | class TimeColEvent extends BaseComponent {
|
691 | render() {
|
692 | return (createElement(StandardEvent, Object.assign({}, this.props, { elClasses: [
|
693 | 'fc-timegrid-event',
|
694 | 'fc-v-event',
|
695 | this.props.isShort && 'fc-timegrid-event-short',
|
696 | ], defaultTimeFormat: DEFAULT_TIME_FORMAT })));
|
697 | }
|
698 | }
|
699 |
|
700 | class TimeCol extends BaseComponent {
|
701 | constructor() {
|
702 | super(...arguments);
|
703 | this.sortEventSegs = memoize(sortEventSegs);
|
704 | }
|
705 |
|
706 | render() {
|
707 | let { props, context } = this;
|
708 | let { options } = context;
|
709 | let isSelectMirror = options.selectMirror;
|
710 | let mirrorSegs =
|
711 | (props.eventDrag && props.eventDrag.segs) ||
|
712 | (props.eventResize && props.eventResize.segs) ||
|
713 | (isSelectMirror && props.dateSelectionSegs) ||
|
714 | [];
|
715 | let interactionAffectedInstances =
|
716 | (props.eventDrag && props.eventDrag.affectedInstances) ||
|
717 | (props.eventResize && props.eventResize.affectedInstances) ||
|
718 | {};
|
719 | let sortedFgSegs = this.sortEventSegs(props.fgEventSegs, options.eventOrder);
|
720 | return (createElement(DayCellContainer, { elTag: "td", elRef: props.elRef, elClasses: [
|
721 | 'fc-timegrid-col',
|
722 | ...(props.extraClassNames || []),
|
723 | ], elAttrs: Object.assign({ role: 'gridcell' }, props.extraDataAttrs), date: props.date, dateProfile: props.dateProfile, todayRange: props.todayRange, extraRenderProps: props.extraRenderProps }, (InnerContent) => (createElement("div", { className: "fc-timegrid-col-frame" },
|
724 | createElement("div", { className: "fc-timegrid-col-bg" },
|
725 | this.renderFillSegs(props.businessHourSegs, 'non-business'),
|
726 | this.renderFillSegs(props.bgEventSegs, 'bg-event'),
|
727 | this.renderFillSegs(props.dateSelectionSegs, 'highlight')),
|
728 | createElement("div", { className: "fc-timegrid-col-events" }, this.renderFgSegs(sortedFgSegs, interactionAffectedInstances, false, false, false)),
|
729 | createElement("div", { className: "fc-timegrid-col-events" }, this.renderFgSegs(mirrorSegs, {}, Boolean(props.eventDrag), Boolean(props.eventResize), Boolean(isSelectMirror))),
|
730 | createElement("div", { className: "fc-timegrid-now-indicator-container" }, this.renderNowIndicator(props.nowIndicatorSegs)),
|
731 | hasCustomDayCellContent(options) && (createElement(InnerContent, { elTag: "div", elClasses: ['fc-timegrid-col-misc'] }))))));
|
732 | }
|
733 | renderFgSegs(sortedFgSegs, segIsInvisible, isDragging, isResizing, isDateSelecting) {
|
734 | let { props } = this;
|
735 | if (props.forPrint) {
|
736 | return renderPlainFgSegs(sortedFgSegs, props);
|
737 | }
|
738 | return this.renderPositionedFgSegs(sortedFgSegs, segIsInvisible, isDragging, isResizing, isDateSelecting);
|
739 | }
|
740 | renderPositionedFgSegs(segs,
|
741 | segIsInvisible, isDragging, isResizing, isDateSelecting) {
|
742 | let { eventMaxStack, eventShortHeight, eventOrderStrict, eventMinHeight } = this.context.options;
|
743 | let { date, slatCoords, eventSelection, todayRange, nowDate } = this.props;
|
744 | let isMirror = isDragging || isResizing || isDateSelecting;
|
745 | let segVCoords = computeSegVCoords(segs, date, slatCoords, eventMinHeight);
|
746 | let { segPlacements, hiddenGroups } = computeFgSegPlacements(segs, segVCoords, eventOrderStrict, eventMaxStack);
|
747 | return (createElement(Fragment, null,
|
748 | this.renderHiddenGroups(hiddenGroups, segs),
|
749 | segPlacements.map((segPlacement) => {
|
750 | let { seg, rect } = segPlacement;
|
751 | let instanceId = seg.eventRange.instance.instanceId;
|
752 | let isVisible = isMirror || Boolean(!segIsInvisible[instanceId] && rect);
|
753 | let vStyle = computeSegVStyle(rect && rect.span);
|
754 | let hStyle = (!isMirror && rect) ? this.computeSegHStyle(rect) : { left: 0, right: 0 };
|
755 | let isInset = Boolean(rect) && rect.stackForward > 0;
|
756 | let isShort = Boolean(rect) && (rect.span.end - rect.span.start) < eventShortHeight;
|
757 | return (createElement("div", { className: 'fc-timegrid-event-harness' +
|
758 | (isInset ? ' fc-timegrid-event-harness-inset' : ''), key: instanceId, style: Object.assign(Object.assign({ visibility: isVisible ? '' : 'hidden' }, vStyle), hStyle) },
|
759 | createElement(TimeColEvent, Object.assign({ seg: seg, isDragging: isDragging, isResizing: isResizing, isDateSelecting: isDateSelecting, isSelected: instanceId === eventSelection, isShort: isShort }, getSegMeta(seg, todayRange, nowDate)))));
|
760 | })));
|
761 | }
|
762 |
|
763 | renderHiddenGroups(hiddenGroups, segs) {
|
764 | let { extraDateSpan, dateProfile, todayRange, nowDate, eventSelection, eventDrag, eventResize } = this.props;
|
765 | return (createElement(Fragment, null, hiddenGroups.map((hiddenGroup) => {
|
766 | let positionCss = computeSegVStyle(hiddenGroup.span);
|
767 | let hiddenSegs = compileSegsFromEntries(hiddenGroup.entries, segs);
|
768 | return (createElement(TimeColMoreLink, { key: buildIsoString(computeEarliestSegStart(hiddenSegs)), hiddenSegs: hiddenSegs, top: positionCss.top, bottom: positionCss.bottom, extraDateSpan: extraDateSpan, dateProfile: dateProfile, todayRange: todayRange, nowDate: nowDate, eventSelection: eventSelection, eventDrag: eventDrag, eventResize: eventResize }));
|
769 | })));
|
770 | }
|
771 | renderFillSegs(segs, fillType) {
|
772 | let { props, context } = this;
|
773 | let segVCoords = computeSegVCoords(segs, props.date, props.slatCoords, context.options.eventMinHeight);
|
774 | let children = segVCoords.map((vcoords, i) => {
|
775 | let seg = segs[i];
|
776 | return (createElement("div", { key: buildEventRangeKey(seg.eventRange), className: "fc-timegrid-bg-harness", style: computeSegVStyle(vcoords) }, fillType === 'bg-event' ?
|
777 | createElement(BgEvent, Object.assign({ seg: seg }, getSegMeta(seg, props.todayRange, props.nowDate))) :
|
778 | renderFill(fillType)));
|
779 | });
|
780 | return createElement(Fragment, null, children);
|
781 | }
|
782 | renderNowIndicator(segs) {
|
783 | let { slatCoords, date } = this.props;
|
784 | if (!slatCoords) {
|
785 | return null;
|
786 | }
|
787 | return segs.map((seg, i) => (createElement(NowIndicatorContainer
|
788 |
|
789 | , {
|
790 |
|
791 | key: i, elClasses: ['fc-timegrid-now-indicator-line'], elStyle: {
|
792 | top: slatCoords.computeDateTop(seg.start, date),
|
793 | }, isAxis: false, date: date })));
|
794 | }
|
795 | computeSegHStyle(segHCoords) {
|
796 | let { isRtl, options } = this.context;
|
797 | let shouldOverlap = options.slotEventOverlap;
|
798 | let nearCoord = segHCoords.levelCoord;
|
799 | let farCoord = segHCoords.levelCoord + segHCoords.thickness;
|
800 | let left;
|
801 | let right;
|
802 | if (shouldOverlap) {
|
803 |
|
804 | farCoord = Math.min(1, nearCoord + (farCoord - nearCoord) * 2);
|
805 | }
|
806 | if (isRtl) {
|
807 | left = 1 - farCoord;
|
808 | right = nearCoord;
|
809 | }
|
810 | else {
|
811 | left = nearCoord;
|
812 | right = 1 - farCoord;
|
813 | }
|
814 | let props = {
|
815 | zIndex: segHCoords.stackDepth + 1,
|
816 | left: left * 100 + '%',
|
817 | right: right * 100 + '%',
|
818 | };
|
819 | if (shouldOverlap && !segHCoords.stackForward) {
|
820 |
|
821 | props[isRtl ? 'marginLeft' : 'marginRight'] = 10 * 2;
|
822 | }
|
823 | return props;
|
824 | }
|
825 | }
|
826 | function renderPlainFgSegs(sortedFgSegs, { todayRange, nowDate, eventSelection, eventDrag, eventResize }) {
|
827 | let hiddenInstances = (eventDrag ? eventDrag.affectedInstances : null) ||
|
828 | (eventResize ? eventResize.affectedInstances : null) ||
|
829 | {};
|
830 | return (createElement(Fragment, null, sortedFgSegs.map((seg) => {
|
831 | let instanceId = seg.eventRange.instance.instanceId;
|
832 | return (createElement("div", { key: instanceId, style: { visibility: hiddenInstances[instanceId] ? 'hidden' : '' } },
|
833 | createElement(TimeColEvent, Object.assign({ seg: seg, isDragging: false, isResizing: false, isDateSelecting: false, isSelected: instanceId === eventSelection, isShort: false }, getSegMeta(seg, todayRange, nowDate)))));
|
834 | })));
|
835 | }
|
836 | function computeSegVStyle(segVCoords) {
|
837 | if (!segVCoords) {
|
838 | return { top: '', bottom: '' };
|
839 | }
|
840 | return {
|
841 | top: segVCoords.start,
|
842 | bottom: -segVCoords.end,
|
843 | };
|
844 | }
|
845 | function compileSegsFromEntries(segEntries, allSegs) {
|
846 | return segEntries.map((segEntry) => allSegs[segEntry.index]);
|
847 | }
|
848 |
|
849 | class TimeColsContent extends BaseComponent {
|
850 | constructor() {
|
851 | super(...arguments);
|
852 | this.splitFgEventSegs = memoize(splitSegsByCol);
|
853 | this.splitBgEventSegs = memoize(splitSegsByCol);
|
854 | this.splitBusinessHourSegs = memoize(splitSegsByCol);
|
855 | this.splitNowIndicatorSegs = memoize(splitSegsByCol);
|
856 | this.splitDateSelectionSegs = memoize(splitSegsByCol);
|
857 | this.splitEventDrag = memoize(splitInteractionByCol);
|
858 | this.splitEventResize = memoize(splitInteractionByCol);
|
859 | this.rootElRef = createRef();
|
860 | this.cellElRefs = new RefMap();
|
861 | }
|
862 | render() {
|
863 | let { props, context } = this;
|
864 | let nowIndicatorTop = context.options.nowIndicator &&
|
865 | props.slatCoords &&
|
866 | props.slatCoords.safeComputeTop(props.nowDate);
|
867 | let colCnt = props.cells.length;
|
868 | let fgEventSegsByRow = this.splitFgEventSegs(props.fgEventSegs, colCnt);
|
869 | let bgEventSegsByRow = this.splitBgEventSegs(props.bgEventSegs, colCnt);
|
870 | let businessHourSegsByRow = this.splitBusinessHourSegs(props.businessHourSegs, colCnt);
|
871 | let nowIndicatorSegsByRow = this.splitNowIndicatorSegs(props.nowIndicatorSegs, colCnt);
|
872 | let dateSelectionSegsByRow = this.splitDateSelectionSegs(props.dateSelectionSegs, colCnt);
|
873 | let eventDragByRow = this.splitEventDrag(props.eventDrag, colCnt);
|
874 | let eventResizeByRow = this.splitEventResize(props.eventResize, colCnt);
|
875 | return (createElement("div", { className: "fc-timegrid-cols", ref: this.rootElRef },
|
876 | createElement("table", { role: "presentation", style: {
|
877 | minWidth: props.tableMinWidth,
|
878 | width: props.clientWidth,
|
879 | } },
|
880 | props.tableColGroupNode,
|
881 | createElement("tbody", { role: "presentation" },
|
882 | createElement("tr", { role: "row" },
|
883 | props.axis && (createElement("td", { "aria-hidden": true, className: "fc-timegrid-col fc-timegrid-axis" },
|
884 | createElement("div", { className: "fc-timegrid-col-frame" },
|
885 | createElement("div", { className: "fc-timegrid-now-indicator-container" }, typeof nowIndicatorTop === 'number' && (createElement(NowIndicatorContainer, { elClasses: ['fc-timegrid-now-indicator-arrow'], elStyle: { top: nowIndicatorTop }, isAxis: true, date: props.nowDate })))))),
|
886 | props.cells.map((cell, i) => (createElement(TimeCol, { key: cell.key, elRef: this.cellElRefs.createRef(cell.key), dateProfile: props.dateProfile, date: cell.date, nowDate: props.nowDate, todayRange: props.todayRange, extraRenderProps: cell.extraRenderProps, extraDataAttrs: cell.extraDataAttrs, extraClassNames: cell.extraClassNames, extraDateSpan: cell.extraDateSpan, fgEventSegs: fgEventSegsByRow[i], bgEventSegs: bgEventSegsByRow[i], businessHourSegs: businessHourSegsByRow[i], nowIndicatorSegs: nowIndicatorSegsByRow[i], dateSelectionSegs: dateSelectionSegsByRow[i], eventDrag: eventDragByRow[i], eventResize: eventResizeByRow[i], slatCoords: props.slatCoords, eventSelection: props.eventSelection, forPrint: props.forPrint }))))))));
|
887 | }
|
888 | componentDidMount() {
|
889 | this.updateCoords();
|
890 | }
|
891 | componentDidUpdate() {
|
892 | this.updateCoords();
|
893 | }
|
894 | updateCoords() {
|
895 | let { props } = this;
|
896 | if (props.onColCoords &&
|
897 | props.clientWidth !== null
|
898 | ) {
|
899 | props.onColCoords(new PositionCache(this.rootElRef.current, collectCellEls(this.cellElRefs.currentMap, props.cells), true,
|
900 | false));
|
901 | }
|
902 | }
|
903 | }
|
904 | function collectCellEls(elMap, cells) {
|
905 | return cells.map((cell) => elMap[cell.key]);
|
906 | }
|
907 |
|
908 |
|
909 |
|
910 | class TimeCols extends DateComponent {
|
911 | constructor() {
|
912 | super(...arguments);
|
913 | this.processSlotOptions = memoize(processSlotOptions);
|
914 | this.state = {
|
915 | slatCoords: null,
|
916 | };
|
917 | this.handleRootEl = (el) => {
|
918 | if (el) {
|
919 | this.context.registerInteractiveComponent(this, {
|
920 | el,
|
921 | isHitComboAllowed: this.props.isHitComboAllowed,
|
922 | });
|
923 | }
|
924 | else {
|
925 | this.context.unregisterInteractiveComponent(this);
|
926 | }
|
927 | };
|
928 | this.handleScrollRequest = (request) => {
|
929 | let { onScrollTopRequest } = this.props;
|
930 | let { slatCoords } = this.state;
|
931 | if (onScrollTopRequest && slatCoords) {
|
932 | if (request.time) {
|
933 | let top = slatCoords.computeTimeTop(request.time);
|
934 | top = Math.ceil(top);
|
935 | if (top) {
|
936 | top += 1;
|
937 | }
|
938 | onScrollTopRequest(top);
|
939 | }
|
940 | return true;
|
941 | }
|
942 | return false;
|
943 | };
|
944 | this.handleColCoords = (colCoords) => {
|
945 | this.colCoords = colCoords;
|
946 | };
|
947 | this.handleSlatCoords = (slatCoords) => {
|
948 | this.setState({ slatCoords });
|
949 | if (this.props.onSlatCoords) {
|
950 | this.props.onSlatCoords(slatCoords);
|
951 | }
|
952 | };
|
953 | }
|
954 | render() {
|
955 | let { props, state } = this;
|
956 | return (createElement("div", { className: "fc-timegrid-body", ref: this.handleRootEl, style: {
|
957 |
|
958 |
|
959 | width: props.clientWidth,
|
960 | minWidth: props.tableMinWidth,
|
961 | } },
|
962 | createElement(TimeColsSlats, { axis: props.axis, dateProfile: props.dateProfile, slatMetas: props.slatMetas, clientWidth: props.clientWidth, minHeight: props.expandRows ? props.clientHeight : '', tableMinWidth: props.tableMinWidth, tableColGroupNode: props.axis ? props.tableColGroupNode : null , onCoords: this.handleSlatCoords }),
|
963 | createElement(TimeColsContent, { cells: props.cells, axis: props.axis, dateProfile: props.dateProfile, businessHourSegs: props.businessHourSegs, bgEventSegs: props.bgEventSegs, fgEventSegs: props.fgEventSegs, dateSelectionSegs: props.dateSelectionSegs, eventSelection: props.eventSelection, eventDrag: props.eventDrag, eventResize: props.eventResize, todayRange: props.todayRange, nowDate: props.nowDate, nowIndicatorSegs: props.nowIndicatorSegs, clientWidth: props.clientWidth, tableMinWidth: props.tableMinWidth, tableColGroupNode: props.tableColGroupNode, slatCoords: state.slatCoords, onColCoords: this.handleColCoords, forPrint: props.forPrint })));
|
964 | }
|
965 | componentDidMount() {
|
966 | this.scrollResponder = this.context.createScrollResponder(this.handleScrollRequest);
|
967 | }
|
968 | componentDidUpdate(prevProps) {
|
969 | this.scrollResponder.update(prevProps.dateProfile !== this.props.dateProfile);
|
970 | }
|
971 | componentWillUnmount() {
|
972 | this.scrollResponder.detach();
|
973 | }
|
974 | queryHit(positionLeft, positionTop) {
|
975 | let { dateEnv, options } = this.context;
|
976 | let { colCoords } = this;
|
977 | let { dateProfile } = this.props;
|
978 | let { slatCoords } = this.state;
|
979 | let { snapDuration, snapsPerSlot } = this.processSlotOptions(this.props.slotDuration, options.snapDuration);
|
980 | let colIndex = colCoords.leftToIndex(positionLeft);
|
981 | let slatIndex = slatCoords.positions.topToIndex(positionTop);
|
982 | if (colIndex != null && slatIndex != null) {
|
983 | let cell = this.props.cells[colIndex];
|
984 | let slatTop = slatCoords.positions.tops[slatIndex];
|
985 | let slatHeight = slatCoords.positions.getHeight(slatIndex);
|
986 | let partial = (positionTop - slatTop) / slatHeight;
|
987 | let localSnapIndex = Math.floor(partial * snapsPerSlot);
|
988 | let snapIndex = slatIndex * snapsPerSlot + localSnapIndex;
|
989 | let dayDate = this.props.cells[colIndex].date;
|
990 | let time = addDurations(dateProfile.slotMinTime, multiplyDuration(snapDuration, snapIndex));
|
991 | let start = dateEnv.add(dayDate, time);
|
992 | let end = dateEnv.add(start, snapDuration);
|
993 | return {
|
994 | dateProfile,
|
995 | dateSpan: Object.assign({ range: { start, end }, allDay: false }, cell.extraDateSpan),
|
996 | dayEl: colCoords.els[colIndex],
|
997 | rect: {
|
998 | left: colCoords.lefts[colIndex],
|
999 | right: colCoords.rights[colIndex],
|
1000 | top: slatTop,
|
1001 | bottom: slatTop + slatHeight,
|
1002 | },
|
1003 | layer: 0,
|
1004 | };
|
1005 | }
|
1006 | return null;
|
1007 | }
|
1008 | }
|
1009 | function processSlotOptions(slotDuration, snapDurationOverride) {
|
1010 | let snapDuration = snapDurationOverride || slotDuration;
|
1011 | let snapsPerSlot = wholeDivideDurations(slotDuration, snapDuration);
|
1012 | if (snapsPerSlot === null) {
|
1013 | snapDuration = slotDuration;
|
1014 | snapsPerSlot = 1;
|
1015 |
|
1016 | }
|
1017 | return { snapDuration, snapsPerSlot };
|
1018 | }
|
1019 |
|
1020 | class DayTimeColsSlicer extends Slicer {
|
1021 | sliceRange(range, dayRanges) {
|
1022 | let segs = [];
|
1023 | for (let col = 0; col < dayRanges.length; col += 1) {
|
1024 | let segRange = intersectRanges(range, dayRanges[col]);
|
1025 | if (segRange) {
|
1026 | segs.push({
|
1027 | start: segRange.start,
|
1028 | end: segRange.end,
|
1029 | isStart: segRange.start.valueOf() === range.start.valueOf(),
|
1030 | isEnd: segRange.end.valueOf() === range.end.valueOf(),
|
1031 | col,
|
1032 | });
|
1033 | }
|
1034 | }
|
1035 | return segs;
|
1036 | }
|
1037 | }
|
1038 |
|
1039 | class DayTimeCols extends DateComponent {
|
1040 | constructor() {
|
1041 | super(...arguments);
|
1042 | this.buildDayRanges = memoize(buildDayRanges);
|
1043 | this.slicer = new DayTimeColsSlicer();
|
1044 | this.timeColsRef = createRef();
|
1045 | }
|
1046 | render() {
|
1047 | let { props, context } = this;
|
1048 | let { dateProfile, dayTableModel } = props;
|
1049 | let { nowIndicator, nextDayThreshold } = context.options;
|
1050 | let dayRanges = this.buildDayRanges(dayTableModel, dateProfile, context.dateEnv);
|
1051 |
|
1052 |
|
1053 | return (createElement(NowTimer, { unit: nowIndicator ? 'minute' : 'day' }, (nowDate, todayRange) => (createElement(TimeCols, Object.assign({ ref: this.timeColsRef }, this.slicer.sliceProps(props, dateProfile, null, context, dayRanges), { forPrint: props.forPrint, axis: props.axis, dateProfile: dateProfile, slatMetas: props.slatMetas, slotDuration: props.slotDuration, cells: dayTableModel.cells[0], tableColGroupNode: props.tableColGroupNode, tableMinWidth: props.tableMinWidth, clientWidth: props.clientWidth, clientHeight: props.clientHeight, expandRows: props.expandRows, nowDate: nowDate, nowIndicatorSegs: nowIndicator && this.slicer.sliceNowDate(nowDate, dateProfile, nextDayThreshold, context, dayRanges), todayRange: todayRange, onScrollTopRequest: props.onScrollTopRequest, onSlatCoords: props.onSlatCoords })))));
|
1054 | }
|
1055 | }
|
1056 | function buildDayRanges(dayTableModel, dateProfile, dateEnv) {
|
1057 | let ranges = [];
|
1058 | for (let date of dayTableModel.headerDates) {
|
1059 | ranges.push({
|
1060 | start: dateEnv.add(date, dateProfile.slotMinTime),
|
1061 | end: dateEnv.add(date, dateProfile.slotMaxTime),
|
1062 | });
|
1063 | }
|
1064 | return ranges;
|
1065 | }
|
1066 |
|
1067 |
|
1068 |
|
1069 | const STOCK_SUB_DURATIONS = [
|
1070 | { hours: 1 },
|
1071 | { minutes: 30 },
|
1072 | { minutes: 15 },
|
1073 | { seconds: 30 },
|
1074 | { seconds: 15 },
|
1075 | ];
|
1076 | function buildSlatMetas(slotMinTime, slotMaxTime, explicitLabelInterval, slotDuration, dateEnv) {
|
1077 | let dayStart = new Date(0);
|
1078 | let slatTime = slotMinTime;
|
1079 | let slatIterator = createDuration(0);
|
1080 | let labelInterval = explicitLabelInterval || computeLabelInterval(slotDuration);
|
1081 | let metas = [];
|
1082 | while (asRoughMs(slatTime) < asRoughMs(slotMaxTime)) {
|
1083 | let date = dateEnv.add(dayStart, slatTime);
|
1084 | let isLabeled = wholeDivideDurations(slatIterator, labelInterval) !== null;
|
1085 | metas.push({
|
1086 | date,
|
1087 | time: slatTime,
|
1088 | key: date.toISOString(),
|
1089 | isoTimeStr: formatIsoTimeString(date),
|
1090 | isLabeled,
|
1091 | });
|
1092 | slatTime = addDurations(slatTime, slotDuration);
|
1093 | slatIterator = addDurations(slatIterator, slotDuration);
|
1094 | }
|
1095 | return metas;
|
1096 | }
|
1097 |
|
1098 | function computeLabelInterval(slotDuration) {
|
1099 | let i;
|
1100 | let labelInterval;
|
1101 | let slotsPerLabel;
|
1102 |
|
1103 | for (i = STOCK_SUB_DURATIONS.length - 1; i >= 0; i -= 1) {
|
1104 | labelInterval = createDuration(STOCK_SUB_DURATIONS[i]);
|
1105 | slotsPerLabel = wholeDivideDurations(labelInterval, slotDuration);
|
1106 | if (slotsPerLabel !== null && slotsPerLabel > 1) {
|
1107 | return labelInterval;
|
1108 | }
|
1109 | }
|
1110 | return slotDuration;
|
1111 | }
|
1112 |
|
1113 | class DayTimeColsView extends TimeColsView {
|
1114 | constructor() {
|
1115 | super(...arguments);
|
1116 | this.buildTimeColsModel = memoize(buildTimeColsModel);
|
1117 | this.buildSlatMetas = memoize(buildSlatMetas);
|
1118 | }
|
1119 | render() {
|
1120 | let { options, dateEnv, dateProfileGenerator } = this.context;
|
1121 | let { props } = this;
|
1122 | let { dateProfile } = props;
|
1123 | let dayTableModel = this.buildTimeColsModel(dateProfile, dateProfileGenerator);
|
1124 | let splitProps = this.allDaySplitter.splitProps(props);
|
1125 | let slatMetas = this.buildSlatMetas(dateProfile.slotMinTime, dateProfile.slotMaxTime, options.slotLabelInterval, options.slotDuration, dateEnv);
|
1126 | let { dayMinWidth } = options;
|
1127 | let hasAttachedAxis = !dayMinWidth;
|
1128 | let hasDetachedAxis = dayMinWidth;
|
1129 | let headerContent = options.dayHeaders && (createElement(DayHeader, { dates: dayTableModel.headerDates, dateProfile: dateProfile, datesRepDistinctDays: true, renderIntro: hasAttachedAxis ? this.renderHeadAxis : null }));
|
1130 | let allDayContent = (options.allDaySlot !== false) && ((contentArg) => (createElement(DayTable, Object.assign({}, splitProps.allDay, { dateProfile: dateProfile, dayTableModel: dayTableModel, nextDayThreshold: options.nextDayThreshold, tableMinWidth: contentArg.tableMinWidth, colGroupNode: contentArg.tableColGroupNode, renderRowIntro: hasAttachedAxis ? this.renderTableRowAxis : null, showWeekNumbers: false, expandRows: false, headerAlignElRef: this.headerElRef, clientWidth: contentArg.clientWidth, clientHeight: contentArg.clientHeight, forPrint: props.forPrint }, this.getAllDayMaxEventProps()))));
|
1131 | let timeGridContent = (contentArg) => (createElement(DayTimeCols, Object.assign({}, splitProps.timed, { dayTableModel: dayTableModel, dateProfile: dateProfile, axis: hasAttachedAxis, slotDuration: options.slotDuration, slatMetas: slatMetas, forPrint: props.forPrint, tableColGroupNode: contentArg.tableColGroupNode, tableMinWidth: contentArg.tableMinWidth, clientWidth: contentArg.clientWidth, clientHeight: contentArg.clientHeight, onSlatCoords: this.handleSlatCoords, expandRows: contentArg.expandRows, onScrollTopRequest: this.handleScrollTopRequest })));
|
1132 | return hasDetachedAxis
|
1133 | ? this.renderHScrollLayout(headerContent, allDayContent, timeGridContent, dayTableModel.colCnt, dayMinWidth, slatMetas, this.state.slatCoords)
|
1134 | : this.renderSimpleLayout(headerContent, allDayContent, timeGridContent);
|
1135 | }
|
1136 | }
|
1137 | function buildTimeColsModel(dateProfile, dateProfileGenerator) {
|
1138 | let daySeries = new DaySeriesModel(dateProfile.renderRange, dateProfileGenerator);
|
1139 | return new DayTableModel(daySeries, false);
|
1140 | }
|
1141 |
|
1142 | export { DayTimeCols, DayTimeColsSlicer, DayTimeColsView, TimeCols, TimeColsSlatsCoords, TimeColsView, buildDayRanges, buildSlatMetas, buildTimeColsModel };
|