@fullcalendar/multimonth 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.
- package/LICENSE.md +1 -1
- package/README.md +6 -1
- package/cjs/index.cjs +322 -0
- package/esm/index.d.ts +41 -0
- package/esm/index.js +314 -0
- package/global.js +328 -0
- package/global.min.js +6 -0
- package/package.json +18 -15
- package/index.cjs +0 -232
- package/index.d.ts +0 -20
- package/index.global.js +0 -239
- package/index.global.min.js +0 -6
- package/index.js +0 -228
package/esm/index.js
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import { joinClassNames, createPlugin } from '@fullcalendar/core';
|
|
2
|
+
import { buildDayTableModel, createDayHeaderFormatter, buildDateRowConfig, DayTableSlicer, DayGridHeaderRow, DayGridRows, dayMicroWidth, buildDayTableRenderRange, TableDateProfileGenerator } from '@fullcalendar/daygrid/internal';
|
|
3
|
+
import { DateComponent, memoize, getUniqueDomId, buildNavLinkAttrs, generateClassName, joinArrayishClassNames, fracToCssDim, watchWidth, watchHeight, getIsHeightAuto, NowTimer, ViewContainer, Scroller, formatIsoMonthStr, afterSize, createDuration, intersectRanges, createFormatter, identity } from '@fullcalendar/core/internal';
|
|
4
|
+
import classNames from '@fullcalendar/core/internal-classnames';
|
|
5
|
+
import { createRef, createElement } from '@fullcalendar/core/preact';
|
|
6
|
+
|
|
7
|
+
class SingleMonth extends DateComponent {
|
|
8
|
+
constructor() {
|
|
9
|
+
super(...arguments);
|
|
10
|
+
// memo
|
|
11
|
+
this.buildDayTableModel = memoize(buildDayTableModel);
|
|
12
|
+
this.createDayHeaderFormatter = memoize(createDayHeaderFormatter);
|
|
13
|
+
this.buildDateRowConfig = memoize(buildDateRowConfig);
|
|
14
|
+
// ref
|
|
15
|
+
this.gridElRef = createRef();
|
|
16
|
+
this.titleElRef = createRef();
|
|
17
|
+
this.tableHeaderElRef = createRef();
|
|
18
|
+
// internal
|
|
19
|
+
this.slicer = new DayTableSlicer();
|
|
20
|
+
this.titleId = getUniqueDomId();
|
|
21
|
+
this.handleEl = (el) => {
|
|
22
|
+
var _a;
|
|
23
|
+
const { options } = this.context;
|
|
24
|
+
if (el) {
|
|
25
|
+
this.rootEl = el;
|
|
26
|
+
(_a = options.singleMonthDidMount) === null || _a === void 0 ? void 0 : _a.call(options, Object.assign({ el: this.rootEl }, this.renderProps));
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
render() {
|
|
31
|
+
const { props, state, context } = this;
|
|
32
|
+
const { dateProfile, forPrint } = props;
|
|
33
|
+
const { options, dateEnv } = context;
|
|
34
|
+
const dayTableModel = this.buildDayTableModel(dateProfile, context.dateProfileGenerator, dateEnv);
|
|
35
|
+
const slicedProps = this.slicer.sliceProps(props, dateProfile, options.nextDayThreshold, context, dayTableModel);
|
|
36
|
+
const dayHeaderFormat = this.createDayHeaderFormatter(options.dayHeaderFormat, false, // datesRepDistinctDays
|
|
37
|
+
dayTableModel.colCount);
|
|
38
|
+
const rowConfig = this.buildDateRowConfig(dayTableModel.headerDates, false, // datesRepDistinctDays
|
|
39
|
+
dateProfile, props.todayRange, dayHeaderFormat, context);
|
|
40
|
+
const isTitleAndHeaderSticky = !forPrint && props.colCount === 1;
|
|
41
|
+
const isAspectRatio = !forPrint || props.hasLateralSiblings;
|
|
42
|
+
const invAspectRatio = 1 / options.aspectRatio;
|
|
43
|
+
const cellColCnt = dayTableModel.cellRows[0].length;
|
|
44
|
+
const colWidth = state.gridWidth != null ? state.gridWidth / cellColCnt : undefined;
|
|
45
|
+
const cellIsMicro = colWidth != null && colWidth <= dayMicroWidth;
|
|
46
|
+
const cellIsNarrow = cellIsMicro || (colWidth != null && colWidth <= options.dayNarrowWidth);
|
|
47
|
+
const rowHeightGuess = state.gridWidth != null
|
|
48
|
+
? invAspectRatio * state.gridWidth / 6
|
|
49
|
+
: undefined;
|
|
50
|
+
const headerStickyBottom = isTitleAndHeaderSticky
|
|
51
|
+
? rowHeightGuess
|
|
52
|
+
: undefined;
|
|
53
|
+
const titleStickyBottom = isTitleAndHeaderSticky && rowHeightGuess != null && state.tableHeaderHeight != null
|
|
54
|
+
? rowHeightGuess + state.tableHeaderHeight + 1
|
|
55
|
+
: undefined;
|
|
56
|
+
const hasNavLink = options.navLinks && props.colCount > 1;
|
|
57
|
+
const headerRenderProps = {
|
|
58
|
+
colCount: props.colCount,
|
|
59
|
+
isSticky: isTitleAndHeaderSticky,
|
|
60
|
+
isNarrow: cellIsNarrow,
|
|
61
|
+
hasNavLink,
|
|
62
|
+
};
|
|
63
|
+
const monthStartDate = props.dateProfile.currentRange.start;
|
|
64
|
+
const navLinkAttrs = hasNavLink
|
|
65
|
+
? buildNavLinkAttrs(context, monthStartDate, 'month', props.isoDateStr)
|
|
66
|
+
: {};
|
|
67
|
+
return (createElement("div", { role: 'listitem', style: { width: props.width } },
|
|
68
|
+
createElement("div", { ref: this.gridElRef, role: 'grid', "aria-labelledby": this.titleId, "data-date": props.isoDateStr, className: joinClassNames(generateClassName(options.singleMonthClass, {
|
|
69
|
+
colCount: props.colCount || 0,
|
|
70
|
+
}), props.borderlessX && classNames.borderlessX, props.borderlessTop && classNames.borderlessTop, props.borderlessBottom && classNames.borderlessBottom, props.colCount === 1 && classNames.noMargin, classNames.flexCol, props.hasLateralSiblings && classNames.breakInsideAvoid) },
|
|
71
|
+
createElement("div", { id: this.titleId, ref: this.titleElRef, className: joinClassNames(generateClassName(options.singleMonthHeaderClass, headerRenderProps), isTitleAndHeaderSticky && classNames.stickyT, classNames.flexCol), style: {
|
|
72
|
+
// HACK to keep zIndex above table-header,
|
|
73
|
+
// because in Chrome, something about position:sticky on this title div
|
|
74
|
+
// causes its bottom border to no be considered part of its mass,
|
|
75
|
+
// and would get overlapped and hidden by the table-header div
|
|
76
|
+
zIndex: isTitleAndHeaderSticky ? 3 : undefined,
|
|
77
|
+
marginBottom: titleStickyBottom,
|
|
78
|
+
} },
|
|
79
|
+
createElement("div", Object.assign({}, navLinkAttrs, { className: joinClassNames(generateClassName(options.singleMonthHeaderInnerClass, headerRenderProps), navLinkAttrs.className) }), dateEnv.format(monthStartDate, props.titleFormat))),
|
|
80
|
+
createElement("div", { className: joinArrayishClassNames(options.tableClass, props.borderlessX && classNames.borderlessX, isTitleAndHeaderSticky && classNames.borderlessTop, props.borderlessBottom && classNames.borderlessBottom, classNames.flexCol), style: {
|
|
81
|
+
marginTop: titleStickyBottom != null ? -titleStickyBottom : undefined,
|
|
82
|
+
} },
|
|
83
|
+
createElement("div", { ref: this.tableHeaderElRef, className: joinClassNames(generateClassName(options.tableHeaderClass, {
|
|
84
|
+
isSticky: isTitleAndHeaderSticky,
|
|
85
|
+
}), props.borderlessX && classNames.borderlessX, classNames.flexCol, isTitleAndHeaderSticky && classNames.sticky), style: {
|
|
86
|
+
zIndex: isTitleAndHeaderSticky ? 2 : undefined,
|
|
87
|
+
top: isTitleAndHeaderSticky ? state.titleHeight : 0,
|
|
88
|
+
marginBottom: headerStickyBottom,
|
|
89
|
+
} },
|
|
90
|
+
createElement(DayGridHeaderRow, Object.assign({}, rowConfig, { role: 'row', borderBottom: false, cellIsNarrow: cellIsNarrow, cellIsMicro: cellIsMicro, rowLevel: 0 })),
|
|
91
|
+
createElement("div", { className: generateClassName(options.dayHeaderDividerClass, {
|
|
92
|
+
isSticky: isTitleAndHeaderSticky,
|
|
93
|
+
options: { allDaySlot: Boolean(options.allDaySlot) },
|
|
94
|
+
}) })),
|
|
95
|
+
createElement("div", { className: joinArrayishClassNames(options.tableBodyClass, classNames.flexCol, isAspectRatio && classNames.rel, props.borderlessX && classNames.borderlessX, isTitleAndHeaderSticky && classNames.borderlessTop, (isTitleAndHeaderSticky || props.noEdgeEffects) && classNames.noEdgeEffects), style: {
|
|
96
|
+
zIndex: isTitleAndHeaderSticky ? 1 : undefined,
|
|
97
|
+
marginTop: headerStickyBottom != null ? -headerStickyBottom : undefined,
|
|
98
|
+
paddingBottom: isAspectRatio ? fracToCssDim(invAspectRatio) : undefined,
|
|
99
|
+
} },
|
|
100
|
+
createElement(DayGridRows, { dateProfile: props.dateProfile, todayRange: props.todayRange, cellRows: dayTableModel.cellRows, className: isAspectRatio ? classNames.fill : '', forPrint: forPrint && !props.hasLateralSiblings, dayMaxEventRows: (forPrint && props.hasLateralSiblings)
|
|
101
|
+
? 1 // for side-by-side multimonths, limit to one row
|
|
102
|
+
: true // otherwise, always do +more link, never expand rows
|
|
103
|
+
,
|
|
104
|
+
// content
|
|
105
|
+
fgEventSegs: slicedProps.fgEventSegs, bgEventSegs: slicedProps.bgEventSegs, businessHourSegs: slicedProps.businessHourSegs, dateSelectionSegs: slicedProps.dateSelectionSegs, eventDrag: slicedProps.eventDrag, eventResize: slicedProps.eventResize, eventSelection: slicedProps.eventSelection,
|
|
106
|
+
// dimensions
|
|
107
|
+
visibleWidth: state.gridWidth, cellIsNarrow: cellIsNarrow, cellIsMicro: cellIsMicro }))))));
|
|
108
|
+
}
|
|
109
|
+
componentDidMount() {
|
|
110
|
+
this.disconnectGridWidth = watchWidth(this.gridElRef.current, (width) => {
|
|
111
|
+
this.setState({ gridWidth: width });
|
|
112
|
+
});
|
|
113
|
+
this.disconnectTitleHeight = watchHeight(this.titleElRef.current, (height) => {
|
|
114
|
+
this.setState({ titleHeight: height });
|
|
115
|
+
});
|
|
116
|
+
this.disconnectTableHeaderHeight = watchHeight(this.tableHeaderElRef.current, (height) => {
|
|
117
|
+
this.setState({ tableHeaderHeight: height });
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
componentWillUnmount() {
|
|
121
|
+
var _a;
|
|
122
|
+
const { options } = this.context;
|
|
123
|
+
this.disconnectGridWidth();
|
|
124
|
+
this.disconnectTitleHeight();
|
|
125
|
+
this.disconnectTableHeaderHeight();
|
|
126
|
+
(_a = options.singleMonthWillUnmount) === null || _a === void 0 ? void 0 : _a.call(options, Object.assign({ el: this.rootEl }, this.renderProps));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
class MultiMonthView extends DateComponent {
|
|
131
|
+
constructor() {
|
|
132
|
+
super(...arguments);
|
|
133
|
+
// memo
|
|
134
|
+
this.splitDateProfileByMonth = memoize(splitDateProfileByMonth);
|
|
135
|
+
this.buildMonthFormat = memoize(buildMonthFormat);
|
|
136
|
+
// ref
|
|
137
|
+
this.scrollerRef = createRef();
|
|
138
|
+
this.innerElRef = createRef();
|
|
139
|
+
this.scrollDate = null;
|
|
140
|
+
this.handleScrollEnd = (isUser) => {
|
|
141
|
+
if (isUser) {
|
|
142
|
+
this.scrollDate = null;
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
render() {
|
|
147
|
+
const { context, props, state } = this;
|
|
148
|
+
const { options } = context;
|
|
149
|
+
const verticalScrolling = !props.forPrint && !getIsHeightAuto(options);
|
|
150
|
+
const monthDateProfiles = this.splitDateProfileByMonth(context.dateProfileGenerator, props.dateProfile, context.dateEnv, options.fixedWeekCount, options.showNonCurrentDates);
|
|
151
|
+
const monthTitleFormat = this.buildMonthFormat(options.singleMonthTitleFormat, monthDateProfiles);
|
|
152
|
+
const { multiMonthMaxColumns, singleMonthMinWidth } = options;
|
|
153
|
+
const { innerWidth } = state;
|
|
154
|
+
let cols;
|
|
155
|
+
let cssMonthWidth;
|
|
156
|
+
let hasLateralSiblings = false;
|
|
157
|
+
if (innerWidth != null) {
|
|
158
|
+
cols = Math.max(1, Math.min(multiMonthMaxColumns, Math.floor(innerWidth / singleMonthMinWidth)));
|
|
159
|
+
if (props.forPrint) {
|
|
160
|
+
cols = Math.min(cols, 2);
|
|
161
|
+
}
|
|
162
|
+
cssMonthWidth = fracToCssDim(1 / cols);
|
|
163
|
+
hasLateralSiblings = cols > 1;
|
|
164
|
+
}
|
|
165
|
+
return (createElement(NowTimer, { unit: "day" }, (nowDate, todayRange) => (createElement(ViewContainer, { viewSpec: context.viewSpec, className: joinClassNames(
|
|
166
|
+
// HACK for Safari. Can't do break-inside:avoid with flexbox items, likely b/c it's not standard:
|
|
167
|
+
// https://stackoverflow.com/a/60256345
|
|
168
|
+
!props.forPrint && classNames.flexCol, props.className), borderlessX: props.borderlessX, borderlessTop: props.borderlessTop, borderlessBottom: props.borderlessBottom, noEdgeEffects: props.noEdgeEffects },
|
|
169
|
+
createElement(Scroller, { vertical: verticalScrolling, className: verticalScrolling ? classNames.liquid : '', ref: this.scrollerRef },
|
|
170
|
+
createElement("div", { role: 'list', "aria-labelledby": props.labelId, "aria-label": props.labelStr, className: classNames.safeTiles, ref: this.innerElRef }, monthDateProfiles.map((monthDateProfile, i) => {
|
|
171
|
+
const monthStr = formatIsoMonthStr(monthDateProfile.currentRange.start);
|
|
172
|
+
return (createElement(SingleMonth, Object.assign({}, props, { key: monthStr, todayRange: todayRange, isoDateStr: monthStr, titleFormat: monthTitleFormat, dateProfile: monthDateProfile, width: cssMonthWidth, colCount: cols,
|
|
173
|
+
// when single-col, kill X border on all items
|
|
174
|
+
borderlessX: cols === 1,
|
|
175
|
+
// when single-col, kill top border on all items
|
|
176
|
+
borderlessTop: cols === 1,
|
|
177
|
+
// when single-col, kill bottom border on last item
|
|
178
|
+
borderlessBottom: cols === 1 && i === monthDateProfiles.length - 1, hasLateralSiblings: hasLateralSiblings })));
|
|
179
|
+
})))))));
|
|
180
|
+
}
|
|
181
|
+
// Lifecycle
|
|
182
|
+
// -----------------------------------------------------------------------------------------------
|
|
183
|
+
componentDidMount() {
|
|
184
|
+
this.resetScroll();
|
|
185
|
+
this.scrollerRef.current.addScrollEndListener(this.handleScrollEnd);
|
|
186
|
+
this.disconnectInnerWidth = watchWidth(this.innerElRef.current, (innerWidth) => {
|
|
187
|
+
afterSize(() => {
|
|
188
|
+
this.setState({ innerWidth });
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
componentDidUpdate(prevProps) {
|
|
193
|
+
if (prevProps.dateProfile !== this.props.dateProfile && this.context.options.scrollTimeReset) {
|
|
194
|
+
this.resetScroll();
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
// NOT optimal to update so often
|
|
198
|
+
// TODO: isolate dependencies of scroll coordinate
|
|
199
|
+
this.applyScroll();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
componentWillUnmount() {
|
|
203
|
+
this.scrollerRef.current.removeScrollEndListener(this.handleScrollEnd);
|
|
204
|
+
this.disconnectInnerWidth();
|
|
205
|
+
}
|
|
206
|
+
// Scrolling
|
|
207
|
+
// -----------------------------------------------------------------------------------------------
|
|
208
|
+
resetScroll() {
|
|
209
|
+
this.scrollDate = this.props.dateProfile.currentDate;
|
|
210
|
+
this.applyScroll();
|
|
211
|
+
}
|
|
212
|
+
applyScroll() {
|
|
213
|
+
if (this.scrollDate != null &&
|
|
214
|
+
this.state.innerWidth != null // render completed?
|
|
215
|
+
) {
|
|
216
|
+
const scroller = this.scrollerRef.current;
|
|
217
|
+
const innerEl = this.innerElRef.current;
|
|
218
|
+
const monthEl = innerEl.querySelector(`[data-date="${formatIsoMonthStr(this.scrollDate)}"]`);
|
|
219
|
+
const scrollTop = Math.ceil(// for fractions, err on the side of scrolling further
|
|
220
|
+
monthEl.parentNode.getBoundingClientRect().top -
|
|
221
|
+
innerEl.getBoundingClientRect().top);
|
|
222
|
+
scroller.scrollTo({ y: scrollTop });
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// date profile
|
|
227
|
+
// -------------------------------------------------------------------------------------------------
|
|
228
|
+
const oneMonthDuration = createDuration(1, 'month');
|
|
229
|
+
function splitDateProfileByMonth(dateProfileGenerator, dateProfile, dateEnv, fixedWeekCount, showNonCurrentDates) {
|
|
230
|
+
const { start, end } = dateProfile.currentRange;
|
|
231
|
+
let monthStart = start;
|
|
232
|
+
const monthDateProfiles = [];
|
|
233
|
+
while (monthStart.valueOf() < end.valueOf()) {
|
|
234
|
+
const monthEnd = dateEnv.add(monthStart, oneMonthDuration);
|
|
235
|
+
const currentRange = {
|
|
236
|
+
// yuck
|
|
237
|
+
start: dateProfileGenerator.skipHiddenDays(monthStart),
|
|
238
|
+
end: dateProfileGenerator.skipHiddenDays(monthEnd, -1, true),
|
|
239
|
+
};
|
|
240
|
+
let renderRange = buildDayTableRenderRange({
|
|
241
|
+
currentRange,
|
|
242
|
+
snapToWeek: true,
|
|
243
|
+
fixedWeekCount,
|
|
244
|
+
dateEnv,
|
|
245
|
+
});
|
|
246
|
+
renderRange = {
|
|
247
|
+
// yuck
|
|
248
|
+
start: dateProfileGenerator.skipHiddenDays(renderRange.start),
|
|
249
|
+
end: dateProfileGenerator.skipHiddenDays(renderRange.end, -1, true),
|
|
250
|
+
};
|
|
251
|
+
const activeRange = dateProfile.activeRange ?
|
|
252
|
+
intersectRanges(dateProfile.activeRange, showNonCurrentDates ? renderRange : currentRange) :
|
|
253
|
+
null;
|
|
254
|
+
monthDateProfiles.push({
|
|
255
|
+
currentDate: dateProfile.currentDate,
|
|
256
|
+
isValid: dateProfile.isValid,
|
|
257
|
+
validRange: dateProfile.validRange,
|
|
258
|
+
renderRange,
|
|
259
|
+
activeRange,
|
|
260
|
+
currentRange,
|
|
261
|
+
currentRangeUnit: 'month',
|
|
262
|
+
isRangeAllDay: true,
|
|
263
|
+
dateIncrement: dateProfile.dateIncrement,
|
|
264
|
+
slotMinTime: dateProfile.slotMaxTime,
|
|
265
|
+
slotMaxTime: dateProfile.slotMinTime,
|
|
266
|
+
});
|
|
267
|
+
monthStart = monthEnd;
|
|
268
|
+
}
|
|
269
|
+
return monthDateProfiles;
|
|
270
|
+
}
|
|
271
|
+
// date formatting
|
|
272
|
+
// -------------------------------------------------------------------------------------------------
|
|
273
|
+
const YEAR_MONTH_FORMATTER = createFormatter({ year: 'numeric', month: 'long' });
|
|
274
|
+
const YEAR_FORMATTER = createFormatter({ month: 'long' });
|
|
275
|
+
function buildMonthFormat(formatOverride, monthDateProfiles) {
|
|
276
|
+
return formatOverride ||
|
|
277
|
+
((monthDateProfiles[0].currentRange.start.getUTCFullYear() !==
|
|
278
|
+
monthDateProfiles[monthDateProfiles.length - 1].currentRange.start.getUTCFullYear())
|
|
279
|
+
? YEAR_MONTH_FORMATTER
|
|
280
|
+
: YEAR_FORMATTER);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const OPTION_REFINERS = {
|
|
284
|
+
multiMonthMaxColumns: Number,
|
|
285
|
+
singleMonthMinWidth: Number,
|
|
286
|
+
singleMonthTitleFormat: createFormatter,
|
|
287
|
+
singleMonthDidMount: identity,
|
|
288
|
+
singleMonthWillUnmount: identity,
|
|
289
|
+
singleMonthClass: identity,
|
|
290
|
+
singleMonthHeaderClass: identity,
|
|
291
|
+
singleMonthHeaderInnerClass: identity,
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
var index = createPlugin({
|
|
295
|
+
name: '@fullcalendar/multimonth',
|
|
296
|
+
initialView: 'multiMonthYear',
|
|
297
|
+
optionRefiners: OPTION_REFINERS,
|
|
298
|
+
views: {
|
|
299
|
+
multiMonth: {
|
|
300
|
+
component: MultiMonthView,
|
|
301
|
+
dateProfileGeneratorClass: TableDateProfileGenerator,
|
|
302
|
+
multiMonthMaxColumns: 3,
|
|
303
|
+
singleMonthMinWidth: 350,
|
|
304
|
+
},
|
|
305
|
+
multiMonthYear: {
|
|
306
|
+
type: 'multiMonth',
|
|
307
|
+
duration: { years: 1 },
|
|
308
|
+
fixedWeekCount: true,
|
|
309
|
+
showNonCurrentDates: false, // TODO: looks bad when single-col layout
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
export { index as default };
|
package/global.js
ADDED
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
FullCalendar Multi-Month Plugin v7.0.0-beta.5
|
|
3
|
+
Docs & License: https://fullcalendar.io/docs/multimonth-grid
|
|
4
|
+
(c) 2025 Adam Shaw
|
|
5
|
+
*/
|
|
6
|
+
FullCalendar.MultiMonth = (function (exports, core, internal$1, internal, classNames, preact) {
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
|
|
10
|
+
|
|
11
|
+
var classNames__default = /*#__PURE__*/_interopDefault(classNames);
|
|
12
|
+
|
|
13
|
+
class SingleMonth extends internal.DateComponent {
|
|
14
|
+
constructor() {
|
|
15
|
+
super(...arguments);
|
|
16
|
+
// memo
|
|
17
|
+
this.buildDayTableModel = internal.memoize(internal$1.buildDayTableModel);
|
|
18
|
+
this.createDayHeaderFormatter = internal.memoize(internal$1.createDayHeaderFormatter);
|
|
19
|
+
this.buildDateRowConfig = internal.memoize(internal$1.buildDateRowConfig);
|
|
20
|
+
// ref
|
|
21
|
+
this.gridElRef = preact.createRef();
|
|
22
|
+
this.titleElRef = preact.createRef();
|
|
23
|
+
this.tableHeaderElRef = preact.createRef();
|
|
24
|
+
// internal
|
|
25
|
+
this.slicer = new internal$1.DayTableSlicer();
|
|
26
|
+
this.titleId = internal.getUniqueDomId();
|
|
27
|
+
this.handleEl = (el) => {
|
|
28
|
+
var _a;
|
|
29
|
+
const { options } = this.context;
|
|
30
|
+
if (el) {
|
|
31
|
+
this.rootEl = el;
|
|
32
|
+
(_a = options.singleMonthDidMount) === null || _a === void 0 ? void 0 : _a.call(options, Object.assign({ el: this.rootEl }, this.renderProps));
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
render() {
|
|
37
|
+
const { props, state, context } = this;
|
|
38
|
+
const { dateProfile, forPrint } = props;
|
|
39
|
+
const { options, dateEnv } = context;
|
|
40
|
+
const dayTableModel = this.buildDayTableModel(dateProfile, context.dateProfileGenerator, dateEnv);
|
|
41
|
+
const slicedProps = this.slicer.sliceProps(props, dateProfile, options.nextDayThreshold, context, dayTableModel);
|
|
42
|
+
const dayHeaderFormat = this.createDayHeaderFormatter(options.dayHeaderFormat, false, // datesRepDistinctDays
|
|
43
|
+
dayTableModel.colCount);
|
|
44
|
+
const rowConfig = this.buildDateRowConfig(dayTableModel.headerDates, false, // datesRepDistinctDays
|
|
45
|
+
dateProfile, props.todayRange, dayHeaderFormat, context);
|
|
46
|
+
const isTitleAndHeaderSticky = !forPrint && props.colCount === 1;
|
|
47
|
+
const isAspectRatio = !forPrint || props.hasLateralSiblings;
|
|
48
|
+
const invAspectRatio = 1 / options.aspectRatio;
|
|
49
|
+
const cellColCnt = dayTableModel.cellRows[0].length;
|
|
50
|
+
const colWidth = state.gridWidth != null ? state.gridWidth / cellColCnt : undefined;
|
|
51
|
+
const cellIsMicro = colWidth != null && colWidth <= internal$1.dayMicroWidth;
|
|
52
|
+
const cellIsNarrow = cellIsMicro || (colWidth != null && colWidth <= options.dayNarrowWidth);
|
|
53
|
+
const rowHeightGuess = state.gridWidth != null
|
|
54
|
+
? invAspectRatio * state.gridWidth / 6
|
|
55
|
+
: undefined;
|
|
56
|
+
const headerStickyBottom = isTitleAndHeaderSticky
|
|
57
|
+
? rowHeightGuess
|
|
58
|
+
: undefined;
|
|
59
|
+
const titleStickyBottom = isTitleAndHeaderSticky && rowHeightGuess != null && state.tableHeaderHeight != null
|
|
60
|
+
? rowHeightGuess + state.tableHeaderHeight + 1
|
|
61
|
+
: undefined;
|
|
62
|
+
const hasNavLink = options.navLinks && props.colCount > 1;
|
|
63
|
+
const headerRenderProps = {
|
|
64
|
+
colCount: props.colCount,
|
|
65
|
+
isSticky: isTitleAndHeaderSticky,
|
|
66
|
+
isNarrow: cellIsNarrow,
|
|
67
|
+
hasNavLink,
|
|
68
|
+
};
|
|
69
|
+
const monthStartDate = props.dateProfile.currentRange.start;
|
|
70
|
+
const navLinkAttrs = hasNavLink
|
|
71
|
+
? internal.buildNavLinkAttrs(context, monthStartDate, 'month', props.isoDateStr)
|
|
72
|
+
: {};
|
|
73
|
+
return (preact.createElement("div", { role: 'listitem', style: { width: props.width } },
|
|
74
|
+
preact.createElement("div", { ref: this.gridElRef, role: 'grid', "aria-labelledby": this.titleId, "data-date": props.isoDateStr, className: core.joinClassNames(internal.generateClassName(options.singleMonthClass, {
|
|
75
|
+
colCount: props.colCount || 0,
|
|
76
|
+
}), props.borderlessX && classNames__default["default"].borderlessX, props.borderlessTop && classNames__default["default"].borderlessTop, props.borderlessBottom && classNames__default["default"].borderlessBottom, props.colCount === 1 && classNames__default["default"].noMargin, classNames__default["default"].flexCol, props.hasLateralSiblings && classNames__default["default"].breakInsideAvoid) },
|
|
77
|
+
preact.createElement("div", { id: this.titleId, ref: this.titleElRef, className: core.joinClassNames(internal.generateClassName(options.singleMonthHeaderClass, headerRenderProps), isTitleAndHeaderSticky && classNames__default["default"].stickyT, classNames__default["default"].flexCol), style: {
|
|
78
|
+
// HACK to keep zIndex above table-header,
|
|
79
|
+
// because in Chrome, something about position:sticky on this title div
|
|
80
|
+
// causes its bottom border to no be considered part of its mass,
|
|
81
|
+
// and would get overlapped and hidden by the table-header div
|
|
82
|
+
zIndex: isTitleAndHeaderSticky ? 3 : undefined,
|
|
83
|
+
marginBottom: titleStickyBottom,
|
|
84
|
+
} },
|
|
85
|
+
preact.createElement("div", Object.assign({}, navLinkAttrs, { className: core.joinClassNames(internal.generateClassName(options.singleMonthHeaderInnerClass, headerRenderProps), navLinkAttrs.className) }), dateEnv.format(monthStartDate, props.titleFormat))),
|
|
86
|
+
preact.createElement("div", { className: internal.joinArrayishClassNames(options.tableClass, props.borderlessX && classNames__default["default"].borderlessX, isTitleAndHeaderSticky && classNames__default["default"].borderlessTop, props.borderlessBottom && classNames__default["default"].borderlessBottom, classNames__default["default"].flexCol), style: {
|
|
87
|
+
marginTop: titleStickyBottom != null ? -titleStickyBottom : undefined,
|
|
88
|
+
} },
|
|
89
|
+
preact.createElement("div", { ref: this.tableHeaderElRef, className: core.joinClassNames(internal.generateClassName(options.tableHeaderClass, {
|
|
90
|
+
isSticky: isTitleAndHeaderSticky,
|
|
91
|
+
}), props.borderlessX && classNames__default["default"].borderlessX, classNames__default["default"].flexCol, isTitleAndHeaderSticky && classNames__default["default"].sticky), style: {
|
|
92
|
+
zIndex: isTitleAndHeaderSticky ? 2 : undefined,
|
|
93
|
+
top: isTitleAndHeaderSticky ? state.titleHeight : 0,
|
|
94
|
+
marginBottom: headerStickyBottom,
|
|
95
|
+
} },
|
|
96
|
+
preact.createElement(internal$1.DayGridHeaderRow, Object.assign({}, rowConfig, { role: 'row', borderBottom: false, cellIsNarrow: cellIsNarrow, cellIsMicro: cellIsMicro, rowLevel: 0 })),
|
|
97
|
+
preact.createElement("div", { className: internal.generateClassName(options.dayHeaderDividerClass, {
|
|
98
|
+
isSticky: isTitleAndHeaderSticky,
|
|
99
|
+
options: { allDaySlot: Boolean(options.allDaySlot) },
|
|
100
|
+
}) })),
|
|
101
|
+
preact.createElement("div", { className: internal.joinArrayishClassNames(options.tableBodyClass, classNames__default["default"].flexCol, isAspectRatio && classNames__default["default"].rel, props.borderlessX && classNames__default["default"].borderlessX, isTitleAndHeaderSticky && classNames__default["default"].borderlessTop, (isTitleAndHeaderSticky || props.noEdgeEffects) && classNames__default["default"].noEdgeEffects), style: {
|
|
102
|
+
zIndex: isTitleAndHeaderSticky ? 1 : undefined,
|
|
103
|
+
marginTop: headerStickyBottom != null ? -headerStickyBottom : undefined,
|
|
104
|
+
paddingBottom: isAspectRatio ? internal.fracToCssDim(invAspectRatio) : undefined,
|
|
105
|
+
} },
|
|
106
|
+
preact.createElement(internal$1.DayGridRows, { dateProfile: props.dateProfile, todayRange: props.todayRange, cellRows: dayTableModel.cellRows, className: isAspectRatio ? classNames__default["default"].fill : '', forPrint: forPrint && !props.hasLateralSiblings, dayMaxEventRows: (forPrint && props.hasLateralSiblings)
|
|
107
|
+
? 1 // for side-by-side multimonths, limit to one row
|
|
108
|
+
: true // otherwise, always do +more link, never expand rows
|
|
109
|
+
,
|
|
110
|
+
// content
|
|
111
|
+
fgEventSegs: slicedProps.fgEventSegs, bgEventSegs: slicedProps.bgEventSegs, businessHourSegs: slicedProps.businessHourSegs, dateSelectionSegs: slicedProps.dateSelectionSegs, eventDrag: slicedProps.eventDrag, eventResize: slicedProps.eventResize, eventSelection: slicedProps.eventSelection,
|
|
112
|
+
// dimensions
|
|
113
|
+
visibleWidth: state.gridWidth, cellIsNarrow: cellIsNarrow, cellIsMicro: cellIsMicro }))))));
|
|
114
|
+
}
|
|
115
|
+
componentDidMount() {
|
|
116
|
+
this.disconnectGridWidth = internal.watchWidth(this.gridElRef.current, (width) => {
|
|
117
|
+
this.setState({ gridWidth: width });
|
|
118
|
+
});
|
|
119
|
+
this.disconnectTitleHeight = internal.watchHeight(this.titleElRef.current, (height) => {
|
|
120
|
+
this.setState({ titleHeight: height });
|
|
121
|
+
});
|
|
122
|
+
this.disconnectTableHeaderHeight = internal.watchHeight(this.tableHeaderElRef.current, (height) => {
|
|
123
|
+
this.setState({ tableHeaderHeight: height });
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
componentWillUnmount() {
|
|
127
|
+
var _a;
|
|
128
|
+
const { options } = this.context;
|
|
129
|
+
this.disconnectGridWidth();
|
|
130
|
+
this.disconnectTitleHeight();
|
|
131
|
+
this.disconnectTableHeaderHeight();
|
|
132
|
+
(_a = options.singleMonthWillUnmount) === null || _a === void 0 ? void 0 : _a.call(options, Object.assign({ el: this.rootEl }, this.renderProps));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
class MultiMonthView extends internal.DateComponent {
|
|
137
|
+
constructor() {
|
|
138
|
+
super(...arguments);
|
|
139
|
+
// memo
|
|
140
|
+
this.splitDateProfileByMonth = internal.memoize(splitDateProfileByMonth);
|
|
141
|
+
this.buildMonthFormat = internal.memoize(buildMonthFormat);
|
|
142
|
+
// ref
|
|
143
|
+
this.scrollerRef = preact.createRef();
|
|
144
|
+
this.innerElRef = preact.createRef();
|
|
145
|
+
this.scrollDate = null;
|
|
146
|
+
this.handleScrollEnd = (isUser) => {
|
|
147
|
+
if (isUser) {
|
|
148
|
+
this.scrollDate = null;
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
render() {
|
|
153
|
+
const { context, props, state } = this;
|
|
154
|
+
const { options } = context;
|
|
155
|
+
const verticalScrolling = !props.forPrint && !internal.getIsHeightAuto(options);
|
|
156
|
+
const monthDateProfiles = this.splitDateProfileByMonth(context.dateProfileGenerator, props.dateProfile, context.dateEnv, options.fixedWeekCount, options.showNonCurrentDates);
|
|
157
|
+
const monthTitleFormat = this.buildMonthFormat(options.singleMonthTitleFormat, monthDateProfiles);
|
|
158
|
+
const { multiMonthMaxColumns, singleMonthMinWidth } = options;
|
|
159
|
+
const { innerWidth } = state;
|
|
160
|
+
let cols;
|
|
161
|
+
let cssMonthWidth;
|
|
162
|
+
let hasLateralSiblings = false;
|
|
163
|
+
if (innerWidth != null) {
|
|
164
|
+
cols = Math.max(1, Math.min(multiMonthMaxColumns, Math.floor(innerWidth / singleMonthMinWidth)));
|
|
165
|
+
if (props.forPrint) {
|
|
166
|
+
cols = Math.min(cols, 2);
|
|
167
|
+
}
|
|
168
|
+
cssMonthWidth = internal.fracToCssDim(1 / cols);
|
|
169
|
+
hasLateralSiblings = cols > 1;
|
|
170
|
+
}
|
|
171
|
+
return (preact.createElement(internal.NowTimer, { unit: "day" }, (nowDate, todayRange) => (preact.createElement(internal.ViewContainer, { viewSpec: context.viewSpec, className: core.joinClassNames(
|
|
172
|
+
// HACK for Safari. Can't do break-inside:avoid with flexbox items, likely b/c it's not standard:
|
|
173
|
+
// https://stackoverflow.com/a/60256345
|
|
174
|
+
!props.forPrint && classNames__default["default"].flexCol, props.className), borderlessX: props.borderlessX, borderlessTop: props.borderlessTop, borderlessBottom: props.borderlessBottom, noEdgeEffects: props.noEdgeEffects },
|
|
175
|
+
preact.createElement(internal.Scroller, { vertical: verticalScrolling, className: verticalScrolling ? classNames__default["default"].liquid : '', ref: this.scrollerRef },
|
|
176
|
+
preact.createElement("div", { role: 'list', "aria-labelledby": props.labelId, "aria-label": props.labelStr, className: classNames__default["default"].safeTiles, ref: this.innerElRef }, monthDateProfiles.map((monthDateProfile, i) => {
|
|
177
|
+
const monthStr = internal.formatIsoMonthStr(monthDateProfile.currentRange.start);
|
|
178
|
+
return (preact.createElement(SingleMonth, Object.assign({}, props, { key: monthStr, todayRange: todayRange, isoDateStr: monthStr, titleFormat: monthTitleFormat, dateProfile: monthDateProfile, width: cssMonthWidth, colCount: cols,
|
|
179
|
+
// when single-col, kill X border on all items
|
|
180
|
+
borderlessX: cols === 1,
|
|
181
|
+
// when single-col, kill top border on all items
|
|
182
|
+
borderlessTop: cols === 1,
|
|
183
|
+
// when single-col, kill bottom border on last item
|
|
184
|
+
borderlessBottom: cols === 1 && i === monthDateProfiles.length - 1, hasLateralSiblings: hasLateralSiblings })));
|
|
185
|
+
})))))));
|
|
186
|
+
}
|
|
187
|
+
// Lifecycle
|
|
188
|
+
// -----------------------------------------------------------------------------------------------
|
|
189
|
+
componentDidMount() {
|
|
190
|
+
this.resetScroll();
|
|
191
|
+
this.scrollerRef.current.addScrollEndListener(this.handleScrollEnd);
|
|
192
|
+
this.disconnectInnerWidth = internal.watchWidth(this.innerElRef.current, (innerWidth) => {
|
|
193
|
+
internal.afterSize(() => {
|
|
194
|
+
this.setState({ innerWidth });
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
componentDidUpdate(prevProps) {
|
|
199
|
+
if (prevProps.dateProfile !== this.props.dateProfile && this.context.options.scrollTimeReset) {
|
|
200
|
+
this.resetScroll();
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
// NOT optimal to update so often
|
|
204
|
+
// TODO: isolate dependencies of scroll coordinate
|
|
205
|
+
this.applyScroll();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
componentWillUnmount() {
|
|
209
|
+
this.scrollerRef.current.removeScrollEndListener(this.handleScrollEnd);
|
|
210
|
+
this.disconnectInnerWidth();
|
|
211
|
+
}
|
|
212
|
+
// Scrolling
|
|
213
|
+
// -----------------------------------------------------------------------------------------------
|
|
214
|
+
resetScroll() {
|
|
215
|
+
this.scrollDate = this.props.dateProfile.currentDate;
|
|
216
|
+
this.applyScroll();
|
|
217
|
+
}
|
|
218
|
+
applyScroll() {
|
|
219
|
+
if (this.scrollDate != null &&
|
|
220
|
+
this.state.innerWidth != null // render completed?
|
|
221
|
+
) {
|
|
222
|
+
const scroller = this.scrollerRef.current;
|
|
223
|
+
const innerEl = this.innerElRef.current;
|
|
224
|
+
const monthEl = innerEl.querySelector(`[data-date="${internal.formatIsoMonthStr(this.scrollDate)}"]`);
|
|
225
|
+
const scrollTop = Math.ceil(// for fractions, err on the side of scrolling further
|
|
226
|
+
monthEl.parentNode.getBoundingClientRect().top -
|
|
227
|
+
innerEl.getBoundingClientRect().top);
|
|
228
|
+
scroller.scrollTo({ y: scrollTop });
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// date profile
|
|
233
|
+
// -------------------------------------------------------------------------------------------------
|
|
234
|
+
const oneMonthDuration = internal.createDuration(1, 'month');
|
|
235
|
+
function splitDateProfileByMonth(dateProfileGenerator, dateProfile, dateEnv, fixedWeekCount, showNonCurrentDates) {
|
|
236
|
+
const { start, end } = dateProfile.currentRange;
|
|
237
|
+
let monthStart = start;
|
|
238
|
+
const monthDateProfiles = [];
|
|
239
|
+
while (monthStart.valueOf() < end.valueOf()) {
|
|
240
|
+
const monthEnd = dateEnv.add(monthStart, oneMonthDuration);
|
|
241
|
+
const currentRange = {
|
|
242
|
+
// yuck
|
|
243
|
+
start: dateProfileGenerator.skipHiddenDays(monthStart),
|
|
244
|
+
end: dateProfileGenerator.skipHiddenDays(monthEnd, -1, true),
|
|
245
|
+
};
|
|
246
|
+
let renderRange = internal$1.buildDayTableRenderRange({
|
|
247
|
+
currentRange,
|
|
248
|
+
snapToWeek: true,
|
|
249
|
+
fixedWeekCount,
|
|
250
|
+
dateEnv,
|
|
251
|
+
});
|
|
252
|
+
renderRange = {
|
|
253
|
+
// yuck
|
|
254
|
+
start: dateProfileGenerator.skipHiddenDays(renderRange.start),
|
|
255
|
+
end: dateProfileGenerator.skipHiddenDays(renderRange.end, -1, true),
|
|
256
|
+
};
|
|
257
|
+
const activeRange = dateProfile.activeRange ?
|
|
258
|
+
internal.intersectRanges(dateProfile.activeRange, showNonCurrentDates ? renderRange : currentRange) :
|
|
259
|
+
null;
|
|
260
|
+
monthDateProfiles.push({
|
|
261
|
+
currentDate: dateProfile.currentDate,
|
|
262
|
+
isValid: dateProfile.isValid,
|
|
263
|
+
validRange: dateProfile.validRange,
|
|
264
|
+
renderRange,
|
|
265
|
+
activeRange,
|
|
266
|
+
currentRange,
|
|
267
|
+
currentRangeUnit: 'month',
|
|
268
|
+
isRangeAllDay: true,
|
|
269
|
+
dateIncrement: dateProfile.dateIncrement,
|
|
270
|
+
slotMinTime: dateProfile.slotMaxTime,
|
|
271
|
+
slotMaxTime: dateProfile.slotMinTime,
|
|
272
|
+
});
|
|
273
|
+
monthStart = monthEnd;
|
|
274
|
+
}
|
|
275
|
+
return monthDateProfiles;
|
|
276
|
+
}
|
|
277
|
+
// date formatting
|
|
278
|
+
// -------------------------------------------------------------------------------------------------
|
|
279
|
+
const YEAR_MONTH_FORMATTER = internal.createFormatter({ year: 'numeric', month: 'long' });
|
|
280
|
+
const YEAR_FORMATTER = internal.createFormatter({ month: 'long' });
|
|
281
|
+
function buildMonthFormat(formatOverride, monthDateProfiles) {
|
|
282
|
+
return formatOverride ||
|
|
283
|
+
((monthDateProfiles[0].currentRange.start.getUTCFullYear() !==
|
|
284
|
+
monthDateProfiles[monthDateProfiles.length - 1].currentRange.start.getUTCFullYear())
|
|
285
|
+
? YEAR_MONTH_FORMATTER
|
|
286
|
+
: YEAR_FORMATTER);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const OPTION_REFINERS = {
|
|
290
|
+
multiMonthMaxColumns: Number,
|
|
291
|
+
singleMonthMinWidth: Number,
|
|
292
|
+
singleMonthTitleFormat: internal.createFormatter,
|
|
293
|
+
singleMonthDidMount: internal.identity,
|
|
294
|
+
singleMonthWillUnmount: internal.identity,
|
|
295
|
+
singleMonthClass: internal.identity,
|
|
296
|
+
singleMonthHeaderClass: internal.identity,
|
|
297
|
+
singleMonthHeaderInnerClass: internal.identity,
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
var plugin = core.createPlugin({
|
|
301
|
+
name: '@fullcalendar/multimonth',
|
|
302
|
+
initialView: 'multiMonthYear',
|
|
303
|
+
optionRefiners: OPTION_REFINERS,
|
|
304
|
+
views: {
|
|
305
|
+
multiMonth: {
|
|
306
|
+
component: MultiMonthView,
|
|
307
|
+
dateProfileGeneratorClass: internal$1.TableDateProfileGenerator,
|
|
308
|
+
multiMonthMaxColumns: 3,
|
|
309
|
+
singleMonthMinWidth: 350,
|
|
310
|
+
},
|
|
311
|
+
multiMonthYear: {
|
|
312
|
+
type: 'multiMonth',
|
|
313
|
+
duration: { years: 1 },
|
|
314
|
+
fixedWeekCount: true,
|
|
315
|
+
showNonCurrentDates: false, // TODO: looks bad when single-col layout
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
core.globalPlugins.push(plugin);
|
|
321
|
+
|
|
322
|
+
exports["default"] = plugin;
|
|
323
|
+
|
|
324
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
325
|
+
|
|
326
|
+
return exports;
|
|
327
|
+
|
|
328
|
+
})({}, FullCalendar, FullCalendar.DayGrid.Internal, FullCalendar.Internal, FullCalendar.InternalClassNames, FullCalendar.Preact);
|