@fullcalendar/multimonth 7.0.0-beta.1 → 7.0.0-beta.3

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/index.cjs CHANGED
@@ -10,10 +10,12 @@ var preact_cjs = require('@fullcalendar/core/preact.cjs');
10
10
  class SingleMonth extends internal_cjs.DateComponent {
11
11
  constructor() {
12
12
  super(...arguments);
13
- this.slicer = new internal_cjs$1.DayTableSlicer();
14
13
  // memo
15
14
  this.buildDayTableModel = internal_cjs.memoize(internal_cjs$1.buildDayTableModel);
16
15
  this.createDayHeaderFormatter = internal_cjs.memoize(internal_cjs$1.createDayHeaderFormatter);
16
+ this.buildDateRowConfig = internal_cjs.memoize(internal_cjs$1.buildDateRowConfig);
17
+ // internal
18
+ this.slicer = new internal_cjs$1.DayTableSlicer();
17
19
  }
18
20
  render() {
19
21
  const { props, context } = this;
@@ -21,27 +23,31 @@ class SingleMonth extends internal_cjs.DateComponent {
21
23
  const { options } = context;
22
24
  const dayTableModel = this.buildDayTableModel(dateProfile, context.dateProfileGenerator);
23
25
  const slicedProps = this.slicer.sliceProps(props, dateProfile, options.nextDayThreshold, context, dayTableModel);
24
- // ensure single-month has aspect ratio
25
- const tableHeight = typeof props.width === 'number'
26
- ? props.width / options.aspectRatio
27
- : null;
28
- const rowCnt = dayTableModel.cellRows.length;
29
- const rowHeight = tableHeight != null ? tableHeight / rowCnt : null;
30
- const dayHeaderFormat = this.createDayHeaderFormatter(context.options.dayHeaderFormat, false, // datesRepDistinctDays
26
+ const dayHeaderFormat = this.createDayHeaderFormatter(options.dayHeaderFormat, false, // datesRepDistinctDays
31
27
  dayTableModel.colCnt);
32
- // TODO: tell children if we know dimensions are unstable?
33
- return (preact_cjs.createElement("div", { "data-date": props.isoDateStr, role: "grid", className: "fc-multimonth-month fc-grow", style: { width: props.width } },
34
- preact_cjs.createElement("div", { className: "fc-multimonth-header", style: { marginBottom: rowHeight }, role: "presentation" },
28
+ const rowConfig = this.buildDateRowConfig(dayTableModel.headerDates, false, // datesRepDistinctDays
29
+ dateProfile, props.todayRange, dayHeaderFormat, context);
30
+ const invAspectRatio = 1 / options.aspectRatio;
31
+ const invRowAspectRatio = invAspectRatio / dayTableModel.rowCnt;
32
+ const isHeaderSticky = !forPrint;
33
+ const isAspectRatio = !forPrint || props.hasLateralSiblings;
34
+ return (preact_cjs.createElement("div", { "data-date": props.isoDateStr, className: internal_cjs.joinClassNames('fc-multimonth-month', props.hasLateralSiblings && 'fc-break-inside-avoid'),
35
+ // override fc-liquid's basis. fc-grow isn't sufficient because doesn't set min-width:0
36
+ style: { width: props.width } },
37
+ preact_cjs.createElement("div", { className: "fc-multimonth-header", style: {
38
+ marginBottom: isHeaderSticky ? internal_cjs.fracToCssDim(invRowAspectRatio) : undefined,
39
+ } },
35
40
  preact_cjs.createElement("div", { className: "fc-multimonth-title" }, context.dateEnv.format(props.dateProfile.currentRange.start, props.titleFormat)),
36
- preact_cjs.createElement("div", { className: 'fc-multimonth-header-row fc-flex-row' }, dayTableModel.headerDates.map((headerDate) => (preact_cjs.createElement(internal_cjs$1.DayOfWeekHeaderCell, { key: headerDate.getUTCDay(), dow: headerDate.getUTCDay(), dayHeaderFormat: dayHeaderFormat, colWidth: undefined }))))),
37
- preact_cjs.createElement("div", { className: 'fc-multimonth-body fc-flex-column', style: {
38
- marginTop: -rowHeight,
39
- height: forPrint ? '' : tableHeight,
41
+ preact_cjs.createElement(internal_cjs$1.DayGridHeaderRow, Object.assign({}, rowConfig, { className: 'fc-multimonth-header-row' }))),
42
+ preact_cjs.createElement("div", { className: internal_cjs.joinClassNames('fc-multimonth-body', isAspectRatio && 'fc-rel'), style: {
43
+ marginTop: isHeaderSticky ? internal_cjs.fracToCssDim(-invRowAspectRatio) : undefined,
44
+ paddingBottom: isAspectRatio ? internal_cjs.fracToCssDim(invAspectRatio) : undefined,
40
45
  } },
41
- preact_cjs.createElement(internal_cjs$1.DayGridRows // .fc-grow
42
- , { dateProfile: props.dateProfile, todayRange: props.todayRange, cellRows: dayTableModel.cellRows, forPrint: props.forPrint,
46
+ preact_cjs.createElement(internal_cjs$1.DayGridRows, { dateProfile: props.dateProfile, todayRange: props.todayRange, cellRows: dayTableModel.cellRows, className: isAspectRatio ? 'fc-fill' : '', forPrint: forPrint && !props.hasLateralSiblings, dayMaxEvents: forPrint ? undefined : options.dayMaxEvents, dayMaxEventRows: (forPrint && props.hasLateralSiblings) ? 1 : options.dayMaxEventRows,
43
47
  // content
44
- fgEventSegs: slicedProps.fgEventSegs, bgEventSegs: slicedProps.bgEventSegs, businessHourSegs: slicedProps.businessHourSegs, dateSelectionSegs: slicedProps.dateSelectionSegs, eventDrag: slicedProps.eventDrag, eventResize: slicedProps.eventResize, eventSelection: slicedProps.eventSelection }))));
48
+ fgEventSegs: slicedProps.fgEventSegs, bgEventSegs: slicedProps.bgEventSegs, businessHourSegs: slicedProps.businessHourSegs, dateSelectionSegs: slicedProps.dateSelectionSegs, eventDrag: slicedProps.eventDrag, eventResize: slicedProps.eventResize, eventSelection: slicedProps.eventSelection,
49
+ // dimensions
50
+ visibleWidth: props.visibleWidth }))));
45
51
  }
46
52
  }
47
53
 
@@ -53,32 +59,12 @@ class MultiMonthView extends internal_cjs.DateComponent {
53
59
  this.buildMonthFormat = internal_cjs.memoize(buildMonthFormat);
54
60
  // ref
55
61
  this.scrollerRef = preact_cjs.createRef();
56
- this.innerElRef = preact_cjs.createRef();
57
- // internal
62
+ this.innerElRef = preact_cjs.createRef(); // .fc-multimonth-inner
58
63
  this.scrollDate = null;
59
- // Sizing
60
- // -----------------------------------------------------------------------------------------------
61
- this.handleWidth = (width) => {
62
- let { xGap, xPadding } = this.state;
63
- // for first time, assume 2 columns and query gap/padding
64
- if (xGap == null) {
65
- const innerEl = this.innerElRef.current;
66
- const children = innerEl.childNodes;
67
- if (children.length > 1) {
68
- const box0 = children[0].getBoundingClientRect();
69
- const box1 = children[1].getBoundingClientRect();
70
- let xSpan;
71
- [xGap, xSpan] = [
72
- Math.abs(box0.left - box1.right),
73
- Math.abs(box0.right - box1.left),
74
- ].sort(internal_cjs.compareNumbers);
75
- xPadding = width - xSpan;
76
- }
77
- }
78
- this.setState({ width, xGap, xPadding });
79
- };
80
64
  this.updateScroll = () => {
81
- if (this.scrollDate != null && this.state.width != null) {
65
+ if (this.scrollDate != null &&
66
+ this.state.innerWidth != null // render completed?
67
+ ) {
82
68
  const scroller = this.scrollerRef.current;
83
69
  const innerEl = this.innerElRef.current;
84
70
  const monthEl = innerEl.querySelector(`[data-date="${internal_cjs.formatIsoMonthStr(this.scrollDate)}"]`);
@@ -96,32 +82,33 @@ class MultiMonthView extends internal_cjs.DateComponent {
96
82
  const { context, props, state } = this;
97
83
  const { options } = context;
98
84
  const verticalScrolling = !props.forPrint && !internal_cjs.getIsHeightAuto(options);
99
- const colCount = state.width == null
100
- ? 2
101
- : Math.min(options.multiMonthMaxColumns, Math.floor((state.width - state.xPadding + state.xGap) /
102
- (options.multiMonthMinWidth + state.xGap)));
103
- const monthWidth = state.width == null
104
- ? '34%' // will expand. now small enough to be 1/3. for allowing gap
105
- : Math.floor(// exact values can cause expansion to other rows
106
- (state.width - state.xPadding - (state.xGap * (colCount - 1))) /
107
- colCount);
108
85
  const monthDateProfiles = this.splitDateProfileByMonth(context.dateProfileGenerator, props.dateProfile, context.dateEnv, options.fixedWeekCount, options.showNonCurrentDates);
109
86
  const monthTitleFormat = this.buildMonthFormat(options.multiMonthTitleFormat, monthDateProfiles);
110
- const rootClassNames = [
111
- 'fc-multimonth-view',
112
- (colCount === 1) ?
87
+ const { multiMonthMinWidth, multiMonthMaxColumns } = options;
88
+ const { innerWidth } = state;
89
+ let cols;
90
+ let computedMonthWidth;
91
+ let cssMonthWidth;
92
+ let hasLateralSiblings = false;
93
+ if (innerWidth != null) {
94
+ cols = Math.max(1, Math.min(multiMonthMaxColumns, Math.floor(innerWidth / multiMonthMinWidth)));
95
+ if (props.forPrint) {
96
+ cols = Math.min(cols, 2);
97
+ }
98
+ computedMonthWidth = innerWidth / cols;
99
+ cssMonthWidth = internal_cjs.fracToCssDim(1 / cols);
100
+ hasLateralSiblings = cols > 1;
101
+ }
102
+ return (preact_cjs.createElement(internal_cjs.NowTimer, { unit: "day" }, (nowDate, todayRange) => (preact_cjs.createElement(internal_cjs.ViewContainer, { className: internal_cjs.joinClassNames('fc-multimonth fc-border', (cols === 1) ?
113
103
  'fc-multimonth-singlecol' :
114
- 'fc-multimonth-multicol',
115
- 'fc-flex-column',
116
- 'fc-border', // BAD to mix this with size-listening?
117
- ];
118
- return (preact_cjs.createElement(internal_cjs.NowTimer, { unit: "day" }, (nowDate, todayRange) => (preact_cjs.createElement(internal_cjs.ViewContainer, { elClasses: rootClassNames, viewSpec: context.viewSpec },
119
- preact_cjs.createElement(internal_cjs.Scroller, { vertical: verticalScrolling, elClassNames: [
120
- verticalScrolling ? 'fc-liquid' : '',
121
- ], ref: this.scrollerRef, widthRef: this.handleWidth },
122
- preact_cjs.createElement("div", { ref: this.innerElRef, className: 'fc-multimonth-inner' }, monthDateProfiles.map((monthDateProfile, i) => {
104
+ 'fc-multimonth-multicol',
105
+ // HACK for Safari. Can't do break-inside:avoid with flexbox items, likely b/c it's not standard:
106
+ // https://stackoverflow.com/a/60256345
107
+ !props.forPrint && 'fc-flex-col'), viewSpec: context.viewSpec },
108
+ preact_cjs.createElement(internal_cjs.Scroller, { vertical: verticalScrolling, className: verticalScrolling ? 'fc-liquid' : '', ref: this.scrollerRef },
109
+ preact_cjs.createElement("div", { ref: this.innerElRef, className: 'fc-multimonth-inner' }, monthDateProfiles.map((monthDateProfile) => {
123
110
  const monthStr = internal_cjs.formatIsoMonthStr(monthDateProfile.currentRange.start);
124
- return (preact_cjs.createElement(SingleMonth, Object.assign({}, props, { key: monthStr, todayRange: todayRange, isoDateStr: monthStr, titleFormat: monthTitleFormat, dateProfile: monthDateProfile, width: monthWidth })));
111
+ return (preact_cjs.createElement(SingleMonth, Object.assign({}, props, { key: monthStr, todayRange: todayRange, isoDateStr: monthStr, titleFormat: monthTitleFormat, dateProfile: monthDateProfile, width: cssMonthWidth, visibleWidth: computedMonthWidth, hasLateralSiblings: hasLateralSiblings })));
125
112
  })))))));
126
113
  }
127
114
  // Lifecycle
@@ -129,6 +116,11 @@ class MultiMonthView extends internal_cjs.DateComponent {
129
116
  componentDidMount() {
130
117
  this.resetScroll();
131
118
  this.scrollerRef.current.addScrollEndListener(this.clearScroll);
119
+ this.disconnectInnerWidth = internal_cjs.watchWidth(this.innerElRef.current, (innerWidth) => {
120
+ internal_cjs.afterSize(() => {
121
+ this.setState({ innerWidth });
122
+ });
123
+ });
132
124
  }
133
125
  componentDidUpdate(prevProps) {
134
126
  if (prevProps.dateProfile !== this.props.dateProfile && this.context.options.scrollTimeReset) {
@@ -142,6 +134,7 @@ class MultiMonthView extends internal_cjs.DateComponent {
142
134
  }
143
135
  componentWillUnmount() {
144
136
  this.scrollerRef.current.removeScrollEndListener(this.clearScroll);
137
+ this.disconnectInnerWidth();
145
138
  }
146
139
  // Scrolling
147
140
  // -----------------------------------------------------------------------------------------------
@@ -213,7 +206,7 @@ const OPTION_REFINERS = {
213
206
  multiMonthMinWidth: Number,
214
207
  };
215
208
 
216
- var css_248z = ".fc-multimonth-inner{display:flex;flex-wrap:wrap}.fc-multimonth-multicol .fc-multimonth-month{margin:0 1.2em 1.2em}.fc-multimonth-multicol .fc-multimonth-title{padding:1em 0}.fc-multimonth-singlecol .fc-multimonth-title{padding:.5em 0}.fc-multimonth-title{font-size:1.2em;font-weight:700;text-align:center}.fc-multimonth-header-row{border-top:1px solid var(--fc-border-color)}.fc-multimonth-header-row,.fc-multimonth-month{border-bottom:1px solid var(--fc-border-color)}.fc-multimonth-singlecol .fc-multimonth-month:last-child{border-bottom:0}.fc-multimonth-multicol .fc-multimonth-body,.fc-multimonth-multicol .fc-multimonth-header-row{border-left:1px solid var(--fc-border-color);border-right:1px solid var(--fc-border-color);font-size:.9em;line-height:1}.fc-media-screen .fc-multimonth-singlecol .fc-multimonth-header{background:var(--fc-page-bg-color);position:sticky;top:0;z-index:2}.fc-media-screen .fc-multimonth-singlecol .fc-multimonth-body{position:relative;z-index:1}";
209
+ var css_248z = ".fc-media-screen .fc-multimonth-inner{display:flex;flex-direction:row;flex-wrap:wrap}.fc-media-print.fc-direction-ltr .fc-multimonth-inner>*{float:left}.fc-media-print.fc-direction-rtl .fc-multimonth-inner>*{float:right}.fc-media-print .fc-multimonth-inner:after{clear:both;content:\"\";display:block}.fc-multimonth-multicol .fc-multimonth-month{padding:1.2em}.fc-multimonth-singlecol .fc-multimonth-title{padding:.5em 0}.fc-multimonth-multicol .fc-multimonth-title{padding-bottom:1em}.fc-multimonth-title{font-size:1.2em;font-weight:700;text-align:center}.fc-multimonth-header-row{border-top:1px solid var(--fc-border-color)}.fc-multimonth-body:not(.fc-multimonth-singlecol .fc-multimonth-month:last-child .fc-multimonth-body),.fc-multimonth-header-row{border-bottom:1px solid var(--fc-border-color)}.fc-multimonth-multicol .fc-multimonth-body,.fc-multimonth-multicol .fc-multimonth-header-row{border-left:1px solid var(--fc-border-color);border-right:1px solid var(--fc-border-color);font-size:.9em;line-height:1}.fc-media-screen .fc-multimonth-singlecol .fc-multimonth-header{background:var(--fc-page-bg-color);position:sticky;top:0;z-index:2}.fc-media-screen .fc-multimonth-singlecol .fc-multimonth-body{position:relative;z-index:1}";
217
210
  internal_cjs.injectStyles(css_248z);
218
211
 
219
212
  var index = index_cjs.createPlugin({
@@ -231,7 +224,7 @@ var index = index_cjs.createPlugin({
231
224
  type: 'multiMonth',
232
225
  duration: { years: 1 },
233
226
  fixedWeekCount: true,
234
- showNonCurrentDates: false,
227
+ showNonCurrentDates: false, // TODO: looks bad when single-col layout
235
228
  },
236
229
  },
237
230
  });
package/index.global.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- FullCalendar Multi-Month Plugin v7.0.0-beta.1
2
+ FullCalendar Multi-Month Plugin v7.0.0-beta.3
3
3
  Docs & License: https://fullcalendar.io/docs/multimonth-grid
4
4
  (c) 2024 Adam Shaw
5
5
  */
@@ -9,10 +9,12 @@ FullCalendar.MultiMonth = (function (exports, core, internal$1, internal, preact
9
9
  class SingleMonth extends internal.DateComponent {
10
10
  constructor() {
11
11
  super(...arguments);
12
- this.slicer = new internal$1.DayTableSlicer();
13
12
  // memo
14
13
  this.buildDayTableModel = internal.memoize(internal$1.buildDayTableModel);
15
14
  this.createDayHeaderFormatter = internal.memoize(internal$1.createDayHeaderFormatter);
15
+ this.buildDateRowConfig = internal.memoize(internal$1.buildDateRowConfig);
16
+ // internal
17
+ this.slicer = new internal$1.DayTableSlicer();
16
18
  }
17
19
  render() {
18
20
  const { props, context } = this;
@@ -20,27 +22,31 @@ FullCalendar.MultiMonth = (function (exports, core, internal$1, internal, preact
20
22
  const { options } = context;
21
23
  const dayTableModel = this.buildDayTableModel(dateProfile, context.dateProfileGenerator);
22
24
  const slicedProps = this.slicer.sliceProps(props, dateProfile, options.nextDayThreshold, context, dayTableModel);
23
- // ensure single-month has aspect ratio
24
- const tableHeight = typeof props.width === 'number'
25
- ? props.width / options.aspectRatio
26
- : null;
27
- const rowCnt = dayTableModel.cellRows.length;
28
- const rowHeight = tableHeight != null ? tableHeight / rowCnt : null;
29
- const dayHeaderFormat = this.createDayHeaderFormatter(context.options.dayHeaderFormat, false, // datesRepDistinctDays
25
+ const dayHeaderFormat = this.createDayHeaderFormatter(options.dayHeaderFormat, false, // datesRepDistinctDays
30
26
  dayTableModel.colCnt);
31
- // TODO: tell children if we know dimensions are unstable?
32
- return (preact.createElement("div", { "data-date": props.isoDateStr, role: "grid", className: "fc-multimonth-month fc-grow", style: { width: props.width } },
33
- preact.createElement("div", { className: "fc-multimonth-header", style: { marginBottom: rowHeight }, role: "presentation" },
27
+ const rowConfig = this.buildDateRowConfig(dayTableModel.headerDates, false, // datesRepDistinctDays
28
+ dateProfile, props.todayRange, dayHeaderFormat, context);
29
+ const invAspectRatio = 1 / options.aspectRatio;
30
+ const invRowAspectRatio = invAspectRatio / dayTableModel.rowCnt;
31
+ const isHeaderSticky = !forPrint;
32
+ const isAspectRatio = !forPrint || props.hasLateralSiblings;
33
+ return (preact.createElement("div", { "data-date": props.isoDateStr, className: internal.joinClassNames('fc-multimonth-month', props.hasLateralSiblings && 'fc-break-inside-avoid'),
34
+ // override fc-liquid's basis. fc-grow isn't sufficient because doesn't set min-width:0
35
+ style: { width: props.width } },
36
+ preact.createElement("div", { className: "fc-multimonth-header", style: {
37
+ marginBottom: isHeaderSticky ? internal.fracToCssDim(invRowAspectRatio) : undefined,
38
+ } },
34
39
  preact.createElement("div", { className: "fc-multimonth-title" }, context.dateEnv.format(props.dateProfile.currentRange.start, props.titleFormat)),
35
- preact.createElement("div", { className: 'fc-multimonth-header-row fc-flex-row' }, dayTableModel.headerDates.map((headerDate) => (preact.createElement(internal$1.DayOfWeekHeaderCell, { key: headerDate.getUTCDay(), dow: headerDate.getUTCDay(), dayHeaderFormat: dayHeaderFormat, colWidth: undefined }))))),
36
- preact.createElement("div", { className: 'fc-multimonth-body fc-flex-column', style: {
37
- marginTop: -rowHeight,
38
- height: forPrint ? '' : tableHeight,
40
+ preact.createElement(internal$1.DayGridHeaderRow, Object.assign({}, rowConfig, { className: 'fc-multimonth-header-row' }))),
41
+ preact.createElement("div", { className: internal.joinClassNames('fc-multimonth-body', isAspectRatio && 'fc-rel'), style: {
42
+ marginTop: isHeaderSticky ? internal.fracToCssDim(-invRowAspectRatio) : undefined,
43
+ paddingBottom: isAspectRatio ? internal.fracToCssDim(invAspectRatio) : undefined,
39
44
  } },
40
- preact.createElement(internal$1.DayGridRows // .fc-grow
41
- , { dateProfile: props.dateProfile, todayRange: props.todayRange, cellRows: dayTableModel.cellRows, forPrint: props.forPrint,
45
+ preact.createElement(internal$1.DayGridRows, { dateProfile: props.dateProfile, todayRange: props.todayRange, cellRows: dayTableModel.cellRows, className: isAspectRatio ? 'fc-fill' : '', forPrint: forPrint && !props.hasLateralSiblings, dayMaxEvents: forPrint ? undefined : options.dayMaxEvents, dayMaxEventRows: (forPrint && props.hasLateralSiblings) ? 1 : options.dayMaxEventRows,
42
46
  // content
43
- fgEventSegs: slicedProps.fgEventSegs, bgEventSegs: slicedProps.bgEventSegs, businessHourSegs: slicedProps.businessHourSegs, dateSelectionSegs: slicedProps.dateSelectionSegs, eventDrag: slicedProps.eventDrag, eventResize: slicedProps.eventResize, eventSelection: slicedProps.eventSelection }))));
47
+ fgEventSegs: slicedProps.fgEventSegs, bgEventSegs: slicedProps.bgEventSegs, businessHourSegs: slicedProps.businessHourSegs, dateSelectionSegs: slicedProps.dateSelectionSegs, eventDrag: slicedProps.eventDrag, eventResize: slicedProps.eventResize, eventSelection: slicedProps.eventSelection,
48
+ // dimensions
49
+ visibleWidth: props.visibleWidth }))));
44
50
  }
45
51
  }
46
52
 
@@ -52,32 +58,12 @@ FullCalendar.MultiMonth = (function (exports, core, internal$1, internal, preact
52
58
  this.buildMonthFormat = internal.memoize(buildMonthFormat);
53
59
  // ref
54
60
  this.scrollerRef = preact.createRef();
55
- this.innerElRef = preact.createRef();
56
- // internal
61
+ this.innerElRef = preact.createRef(); // .fc-multimonth-inner
57
62
  this.scrollDate = null;
58
- // Sizing
59
- // -----------------------------------------------------------------------------------------------
60
- this.handleWidth = (width) => {
61
- let { xGap, xPadding } = this.state;
62
- // for first time, assume 2 columns and query gap/padding
63
- if (xGap == null) {
64
- const innerEl = this.innerElRef.current;
65
- const children = innerEl.childNodes;
66
- if (children.length > 1) {
67
- const box0 = children[0].getBoundingClientRect();
68
- const box1 = children[1].getBoundingClientRect();
69
- let xSpan;
70
- [xGap, xSpan] = [
71
- Math.abs(box0.left - box1.right),
72
- Math.abs(box0.right - box1.left),
73
- ].sort(internal.compareNumbers);
74
- xPadding = width - xSpan;
75
- }
76
- }
77
- this.setState({ width, xGap, xPadding });
78
- };
79
63
  this.updateScroll = () => {
80
- if (this.scrollDate != null && this.state.width != null) {
64
+ if (this.scrollDate != null &&
65
+ this.state.innerWidth != null // render completed?
66
+ ) {
81
67
  const scroller = this.scrollerRef.current;
82
68
  const innerEl = this.innerElRef.current;
83
69
  const monthEl = innerEl.querySelector(`[data-date="${internal.formatIsoMonthStr(this.scrollDate)}"]`);
@@ -95,32 +81,33 @@ FullCalendar.MultiMonth = (function (exports, core, internal$1, internal, preact
95
81
  const { context, props, state } = this;
96
82
  const { options } = context;
97
83
  const verticalScrolling = !props.forPrint && !internal.getIsHeightAuto(options);
98
- const colCount = state.width == null
99
- ? 2
100
- : Math.min(options.multiMonthMaxColumns, Math.floor((state.width - state.xPadding + state.xGap) /
101
- (options.multiMonthMinWidth + state.xGap)));
102
- const monthWidth = state.width == null
103
- ? '34%' // will expand. now small enough to be 1/3. for allowing gap
104
- : Math.floor(// exact values can cause expansion to other rows
105
- (state.width - state.xPadding - (state.xGap * (colCount - 1))) /
106
- colCount);
107
84
  const monthDateProfiles = this.splitDateProfileByMonth(context.dateProfileGenerator, props.dateProfile, context.dateEnv, options.fixedWeekCount, options.showNonCurrentDates);
108
85
  const monthTitleFormat = this.buildMonthFormat(options.multiMonthTitleFormat, monthDateProfiles);
109
- const rootClassNames = [
110
- 'fc-multimonth-view',
111
- (colCount === 1) ?
86
+ const { multiMonthMinWidth, multiMonthMaxColumns } = options;
87
+ const { innerWidth } = state;
88
+ let cols;
89
+ let computedMonthWidth;
90
+ let cssMonthWidth;
91
+ let hasLateralSiblings = false;
92
+ if (innerWidth != null) {
93
+ cols = Math.max(1, Math.min(multiMonthMaxColumns, Math.floor(innerWidth / multiMonthMinWidth)));
94
+ if (props.forPrint) {
95
+ cols = Math.min(cols, 2);
96
+ }
97
+ computedMonthWidth = innerWidth / cols;
98
+ cssMonthWidth = internal.fracToCssDim(1 / cols);
99
+ hasLateralSiblings = cols > 1;
100
+ }
101
+ return (preact.createElement(internal.NowTimer, { unit: "day" }, (nowDate, todayRange) => (preact.createElement(internal.ViewContainer, { className: internal.joinClassNames('fc-multimonth fc-border', (cols === 1) ?
112
102
  'fc-multimonth-singlecol' :
113
- 'fc-multimonth-multicol',
114
- 'fc-flex-column',
115
- 'fc-border', // BAD to mix this with size-listening?
116
- ];
117
- return (preact.createElement(internal.NowTimer, { unit: "day" }, (nowDate, todayRange) => (preact.createElement(internal.ViewContainer, { elClasses: rootClassNames, viewSpec: context.viewSpec },
118
- preact.createElement(internal.Scroller, { vertical: verticalScrolling, elClassNames: [
119
- verticalScrolling ? 'fc-liquid' : '',
120
- ], ref: this.scrollerRef, widthRef: this.handleWidth },
121
- preact.createElement("div", { ref: this.innerElRef, className: 'fc-multimonth-inner' }, monthDateProfiles.map((monthDateProfile, i) => {
103
+ 'fc-multimonth-multicol',
104
+ // HACK for Safari. Can't do break-inside:avoid with flexbox items, likely b/c it's not standard:
105
+ // https://stackoverflow.com/a/60256345
106
+ !props.forPrint && 'fc-flex-col'), viewSpec: context.viewSpec },
107
+ preact.createElement(internal.Scroller, { vertical: verticalScrolling, className: verticalScrolling ? 'fc-liquid' : '', ref: this.scrollerRef },
108
+ preact.createElement("div", { ref: this.innerElRef, className: 'fc-multimonth-inner' }, monthDateProfiles.map((monthDateProfile) => {
122
109
  const monthStr = internal.formatIsoMonthStr(monthDateProfile.currentRange.start);
123
- return (preact.createElement(SingleMonth, Object.assign({}, props, { key: monthStr, todayRange: todayRange, isoDateStr: monthStr, titleFormat: monthTitleFormat, dateProfile: monthDateProfile, width: monthWidth })));
110
+ return (preact.createElement(SingleMonth, Object.assign({}, props, { key: monthStr, todayRange: todayRange, isoDateStr: monthStr, titleFormat: monthTitleFormat, dateProfile: monthDateProfile, width: cssMonthWidth, visibleWidth: computedMonthWidth, hasLateralSiblings: hasLateralSiblings })));
124
111
  })))))));
125
112
  }
126
113
  // Lifecycle
@@ -128,6 +115,11 @@ FullCalendar.MultiMonth = (function (exports, core, internal$1, internal, preact
128
115
  componentDidMount() {
129
116
  this.resetScroll();
130
117
  this.scrollerRef.current.addScrollEndListener(this.clearScroll);
118
+ this.disconnectInnerWidth = internal.watchWidth(this.innerElRef.current, (innerWidth) => {
119
+ internal.afterSize(() => {
120
+ this.setState({ innerWidth });
121
+ });
122
+ });
131
123
  }
132
124
  componentDidUpdate(prevProps) {
133
125
  if (prevProps.dateProfile !== this.props.dateProfile && this.context.options.scrollTimeReset) {
@@ -141,6 +133,7 @@ FullCalendar.MultiMonth = (function (exports, core, internal$1, internal, preact
141
133
  }
142
134
  componentWillUnmount() {
143
135
  this.scrollerRef.current.removeScrollEndListener(this.clearScroll);
136
+ this.disconnectInnerWidth();
144
137
  }
145
138
  // Scrolling
146
139
  // -----------------------------------------------------------------------------------------------
@@ -212,7 +205,7 @@ FullCalendar.MultiMonth = (function (exports, core, internal$1, internal, preact
212
205
  multiMonthMinWidth: Number,
213
206
  };
214
207
 
215
- var css_248z = ".fc-multimonth-inner{display:flex;flex-wrap:wrap}.fc-multimonth-multicol .fc-multimonth-month{margin:0 1.2em 1.2em}.fc-multimonth-multicol .fc-multimonth-title{padding:1em 0}.fc-multimonth-singlecol .fc-multimonth-title{padding:.5em 0}.fc-multimonth-title{font-size:1.2em;font-weight:700;text-align:center}.fc-multimonth-header-row{border-top:1px solid var(--fc-border-color)}.fc-multimonth-header-row,.fc-multimonth-month{border-bottom:1px solid var(--fc-border-color)}.fc-multimonth-singlecol .fc-multimonth-month:last-child{border-bottom:0}.fc-multimonth-multicol .fc-multimonth-body,.fc-multimonth-multicol .fc-multimonth-header-row{border-left:1px solid var(--fc-border-color);border-right:1px solid var(--fc-border-color);font-size:.9em;line-height:1}.fc-media-screen .fc-multimonth-singlecol .fc-multimonth-header{background:var(--fc-page-bg-color);position:sticky;top:0;z-index:2}.fc-media-screen .fc-multimonth-singlecol .fc-multimonth-body{position:relative;z-index:1}";
208
+ var css_248z = ".fc-media-screen .fc-multimonth-inner{display:flex;flex-direction:row;flex-wrap:wrap}.fc-media-print.fc-direction-ltr .fc-multimonth-inner>*{float:left}.fc-media-print.fc-direction-rtl .fc-multimonth-inner>*{float:right}.fc-media-print .fc-multimonth-inner:after{clear:both;content:\"\";display:block}.fc-multimonth-multicol .fc-multimonth-month{padding:1.2em}.fc-multimonth-singlecol .fc-multimonth-title{padding:.5em 0}.fc-multimonth-multicol .fc-multimonth-title{padding-bottom:1em}.fc-multimonth-title{font-size:1.2em;font-weight:700;text-align:center}.fc-multimonth-header-row{border-top:1px solid var(--fc-border-color)}.fc-multimonth-body:not(.fc-multimonth-singlecol .fc-multimonth-month:last-child .fc-multimonth-body),.fc-multimonth-header-row{border-bottom:1px solid var(--fc-border-color)}.fc-multimonth-multicol .fc-multimonth-body,.fc-multimonth-multicol .fc-multimonth-header-row{border-left:1px solid var(--fc-border-color);border-right:1px solid var(--fc-border-color);font-size:.9em;line-height:1}.fc-media-screen .fc-multimonth-singlecol .fc-multimonth-header{background:var(--fc-page-bg-color);position:sticky;top:0;z-index:2}.fc-media-screen .fc-multimonth-singlecol .fc-multimonth-body{position:relative;z-index:1}";
216
209
  internal.injectStyles(css_248z);
217
210
 
218
211
  var plugin = core.createPlugin({
@@ -230,7 +223,7 @@ FullCalendar.MultiMonth = (function (exports, core, internal$1, internal, preact
230
223
  type: 'multiMonth',
231
224
  duration: { years: 1 },
232
225
  fixedWeekCount: true,
233
- showNonCurrentDates: false,
226
+ showNonCurrentDates: false, // TODO: looks bad when single-col layout
234
227
  },
235
228
  },
236
229
  });
@@ -1,6 +1,6 @@
1
1
  /*!
2
- FullCalendar Multi-Month Plugin v7.0.0-beta.1
2
+ FullCalendar Multi-Month Plugin v7.0.0-beta.3
3
3
  Docs & License: https://fullcalendar.io/docs/multimonth-grid
4
4
  (c) 2024 Adam Shaw
5
5
  */
6
- FullCalendar.MultiMonth=function(e,t,l,n,r){"use strict";class o extends n.DateComponent{constructor(){super(...arguments),this.slicer=new l.DayTableSlicer,this.buildDayTableModel=n.memoize(l.buildDayTableModel),this.createDayHeaderFormatter=n.memoize(l.createDayHeaderFormatter)}render(){const{props:e,context:t}=this,{dateProfile:n,forPrint:o}=e,{options:i}=t,a=this.buildDayTableModel(n,t.dateProfileGenerator),s=this.slicer.sliceProps(e,n,i.nextDayThreshold,t,a),c="number"==typeof e.width?e.width/i.aspectRatio:null,m=a.cellRows.length,d=null!=c?c/m:null,h=this.createDayHeaderFormatter(t.options.dayHeaderFormat,!1,a.colCnt);return r.createElement("div",{"data-date":e.isoDateStr,role:"grid",className:"fc-multimonth-month fc-grow",style:{width:e.width}},r.createElement("div",{className:"fc-multimonth-header",style:{marginBottom:d},role:"presentation"},r.createElement("div",{className:"fc-multimonth-title"},t.dateEnv.format(e.dateProfile.currentRange.start,e.titleFormat)),r.createElement("div",{className:"fc-multimonth-header-row fc-flex-row"},a.headerDates.map(e=>r.createElement(l.DayOfWeekHeaderCell,{key:e.getUTCDay(),dow:e.getUTCDay(),dayHeaderFormat:h,colWidth:void 0})))),r.createElement("div",{className:"fc-multimonth-body fc-flex-column",style:{marginTop:-d,height:o?"":c}},r.createElement(l.DayGridRows,{dateProfile:e.dateProfile,todayRange:e.todayRange,cellRows:a.cellRows,forPrint:e.forPrint,fgEventSegs:s.fgEventSegs,bgEventSegs:s.bgEventSegs,businessHourSegs:s.businessHourSegs,dateSelectionSegs:s.dateSelectionSegs,eventDrag:s.eventDrag,eventResize:s.eventResize,eventSelection:s.eventSelection})))}}class i extends n.DateComponent{constructor(){super(...arguments),this.splitDateProfileByMonth=n.memoize(s),this.buildMonthFormat=n.memoize(d),this.scrollerRef=r.createRef(),this.innerElRef=r.createRef(),this.scrollDate=null,this.handleWidth=e=>{let{xGap:t,xPadding:l}=this.state;if(null==t){const r=this.innerElRef.current.childNodes;if(r.length>1){const o=r[0].getBoundingClientRect(),i=r[1].getBoundingClientRect();let a;[t,a]=[Math.abs(o.left-i.right),Math.abs(o.right-i.left)].sort(n.compareNumbers),l=e-a}}this.setState({width:e,xGap:t,xPadding:l})},this.updateScroll=()=>{if(null!=this.scrollDate&&null!=this.state.width){const e=this.scrollerRef.current,t=this.innerElRef.current,l=t.querySelector(`[data-date="${n.formatIsoMonthStr(this.scrollDate)}"]`),r=Math.ceil(l.getBoundingClientRect().top-t.getBoundingClientRect().top);e.scrollTo({y:r})}},this.clearScroll=()=>{this.scrollDate=null}}render(){const{context:e,props:t,state:l}=this,{options:i}=e,a=!t.forPrint&&!n.getIsHeightAuto(i),s=null==l.width?2:Math.min(i.multiMonthMaxColumns,Math.floor((l.width-l.xPadding+l.xGap)/(i.multiMonthMinWidth+l.xGap))),c=null==l.width?"34%":Math.floor((l.width-l.xPadding-l.xGap*(s-1))/s),m=this.splitDateProfileByMonth(e.dateProfileGenerator,t.dateProfile,e.dateEnv,i.fixedWeekCount,i.showNonCurrentDates),d=this.buildMonthFormat(i.multiMonthTitleFormat,m),h=["fc-multimonth-view",1===s?"fc-multimonth-singlecol":"fc-multimonth-multicol","fc-flex-column","fc-border"];return r.createElement(n.NowTimer,{unit:"day"},(l,i)=>r.createElement(n.ViewContainer,{elClasses:h,viewSpec:e.viewSpec},r.createElement(n.Scroller,{vertical:a,elClassNames:[a?"fc-liquid":""],ref:this.scrollerRef,widthRef:this.handleWidth},r.createElement("div",{ref:this.innerElRef,className:"fc-multimonth-inner"},m.map((e,l)=>{const a=n.formatIsoMonthStr(e.currentRange.start);return r.createElement(o,Object.assign({},t,{key:a,todayRange:i,isoDateStr:a,titleFormat:d,dateProfile:e,width:c}))})))))}componentDidMount(){this.resetScroll(),this.scrollerRef.current.addScrollEndListener(this.clearScroll)}componentDidUpdate(e){e.dateProfile!==this.props.dateProfile&&this.context.options.scrollTimeReset?this.resetScroll():this.updateScroll()}componentWillUnmount(){this.scrollerRef.current.removeScrollEndListener(this.clearScroll)}resetScroll(){this.scrollDate=this.props.dateProfile.currentDate,this.updateScroll()}}const a=n.createDuration(1,"month");function s(e,t,r,o,i){const{start:s,end:c}=t.currentRange;let m=s;const d=[];for(;m.valueOf()<c.valueOf();){const s=r.add(m,a),c={start:e.skipHiddenDays(m),end:e.skipHiddenDays(s,-1,!0)};let h=l.buildDayTableRenderRange({currentRange:c,snapToWeek:!0,fixedWeekCount:o,dateEnv:r});h={start:e.skipHiddenDays(h.start),end:e.skipHiddenDays(h.end,-1,!0)};const u=t.activeRange?n.intersectRanges(t.activeRange,i?h:c):null;d.push({currentDate:t.currentDate,isValid:t.isValid,validRange:t.validRange,renderRange:h,activeRange:u,currentRange:c,currentRangeUnit:"month",isRangeAllDay:!0,dateIncrement:t.dateIncrement,slotMinTime:t.slotMaxTime,slotMaxTime:t.slotMinTime}),m=s}return d}const c=n.createFormatter({year:"numeric",month:"long"}),m=n.createFormatter({month:"long"});function d(e,t){return e||(t[0].currentRange.start.getUTCFullYear()!==t[t.length-1].currentRange.start.getUTCFullYear()?c:m)}const h={multiMonthTitleFormat:n.createFormatter,multiMonthMaxColumns:Number,multiMonthMinWidth:Number};n.injectStyles(".fc-multimonth-inner{display:flex;flex-wrap:wrap}.fc-multimonth-multicol .fc-multimonth-month{margin:0 1.2em 1.2em}.fc-multimonth-multicol .fc-multimonth-title{padding:1em 0}.fc-multimonth-singlecol .fc-multimonth-title{padding:.5em 0}.fc-multimonth-title{font-size:1.2em;font-weight:700;text-align:center}.fc-multimonth-header-row{border-top:1px solid var(--fc-border-color)}.fc-multimonth-header-row,.fc-multimonth-month{border-bottom:1px solid var(--fc-border-color)}.fc-multimonth-singlecol .fc-multimonth-month:last-child{border-bottom:0}.fc-multimonth-multicol .fc-multimonth-body,.fc-multimonth-multicol .fc-multimonth-header-row{border-left:1px solid var(--fc-border-color);border-right:1px solid var(--fc-border-color);font-size:.9em;line-height:1}.fc-media-screen .fc-multimonth-singlecol .fc-multimonth-header{background:var(--fc-page-bg-color);position:sticky;top:0;z-index:2}.fc-media-screen .fc-multimonth-singlecol .fc-multimonth-body{position:relative;z-index:1}");var u=t.createPlugin({name:"@fullcalendar/multimonth",initialView:"multiMonthYear",optionRefiners:h,views:{multiMonth:{component:i,dateProfileGeneratorClass:l.TableDateProfileGenerator,multiMonthMinWidth:350,multiMonthMaxColumns:3},multiMonthYear:{type:"multiMonth",duration:{years:1},fixedWeekCount:!0,showNonCurrentDates:!1}}});return t.globalPlugins.push(u),e.default=u,Object.defineProperty(e,"__esModule",{value:!0}),e}({},FullCalendar,FullCalendar.DayGrid.Internal,FullCalendar.Internal,FullCalendar.Preact);
6
+ FullCalendar.MultiMonth=function(e,t,i,n,r){"use strict";class o extends n.DateComponent{constructor(){super(...arguments),this.buildDayTableModel=n.memoize(i.buildDayTableModel),this.createDayHeaderFormatter=n.memoize(i.createDayHeaderFormatter),this.buildDateRowConfig=n.memoize(i.buildDateRowConfig),this.slicer=new i.DayTableSlicer}render(){const{props:e,context:t}=this,{dateProfile:o,forPrint:l}=e,{options:a}=t,s=this.buildDayTableModel(o,t.dateProfileGenerator),c=this.slicer.sliceProps(e,o,a.nextDayThreshold,t,s),m=this.createDayHeaderFormatter(a.dayHeaderFormat,!1,s.colCnt),d=this.buildDateRowConfig(s.headerDates,!1,o,e.todayRange,m,t),h=1/a.aspectRatio,u=h/s.rowCnt,f=!l,g=!l||e.hasLateralSiblings;return r.createElement("div",{"data-date":e.isoDateStr,className:n.joinClassNames("fc-multimonth-month",e.hasLateralSiblings&&"fc-break-inside-avoid"),style:{width:e.width}},r.createElement("div",{className:"fc-multimonth-header",style:{marginBottom:f?n.fracToCssDim(u):void 0}},r.createElement("div",{className:"fc-multimonth-title"},t.dateEnv.format(e.dateProfile.currentRange.start,e.titleFormat)),r.createElement(i.DayGridHeaderRow,Object.assign({},d,{className:"fc-multimonth-header-row"}))),r.createElement("div",{className:n.joinClassNames("fc-multimonth-body",g&&"fc-rel"),style:{marginTop:f?n.fracToCssDim(-u):void 0,paddingBottom:g?n.fracToCssDim(h):void 0}},r.createElement(i.DayGridRows,{dateProfile:e.dateProfile,todayRange:e.todayRange,cellRows:s.cellRows,className:g?"fc-fill":"",forPrint:l&&!e.hasLateralSiblings,dayMaxEvents:l?void 0:a.dayMaxEvents,dayMaxEventRows:l&&e.hasLateralSiblings?1:a.dayMaxEventRows,fgEventSegs:c.fgEventSegs,bgEventSegs:c.bgEventSegs,businessHourSegs:c.businessHourSegs,dateSelectionSegs:c.dateSelectionSegs,eventDrag:c.eventDrag,eventResize:c.eventResize,eventSelection:c.eventSelection,visibleWidth:e.visibleWidth})))}}class l extends n.DateComponent{constructor(){super(...arguments),this.splitDateProfileByMonth=n.memoize(s),this.buildMonthFormat=n.memoize(d),this.scrollerRef=r.createRef(),this.innerElRef=r.createRef(),this.scrollDate=null,this.updateScroll=()=>{if(null!=this.scrollDate&&null!=this.state.innerWidth){const e=this.scrollerRef.current,t=this.innerElRef.current,i=t.querySelector(`[data-date="${n.formatIsoMonthStr(this.scrollDate)}"]`),r=Math.ceil(i.getBoundingClientRect().top-t.getBoundingClientRect().top);e.scrollTo({y:r})}},this.clearScroll=()=>{this.scrollDate=null}}render(){const{context:e,props:t,state:i}=this,{options:l}=e,a=!t.forPrint&&!n.getIsHeightAuto(l),s=this.splitDateProfileByMonth(e.dateProfileGenerator,t.dateProfile,e.dateEnv,l.fixedWeekCount,l.showNonCurrentDates),c=this.buildMonthFormat(l.multiMonthTitleFormat,s),{multiMonthMinWidth:m,multiMonthMaxColumns:d}=l,{innerWidth:h}=i;let u,f,g,p=!1;return null!=h&&(u=Math.max(1,Math.min(d,Math.floor(h/m))),t.forPrint&&(u=Math.min(u,2)),f=h/u,g=n.fracToCssDim(1/u),p=u>1),r.createElement(n.NowTimer,{unit:"day"},(i,l)=>r.createElement(n.ViewContainer,{className:n.joinClassNames("fc-multimonth fc-border",1===u?"fc-multimonth-singlecol":"fc-multimonth-multicol",!t.forPrint&&"fc-flex-col"),viewSpec:e.viewSpec},r.createElement(n.Scroller,{vertical:a,className:a?"fc-liquid":"",ref:this.scrollerRef},r.createElement("div",{ref:this.innerElRef,className:"fc-multimonth-inner"},s.map(e=>{const i=n.formatIsoMonthStr(e.currentRange.start);return r.createElement(o,Object.assign({},t,{key:i,todayRange:l,isoDateStr:i,titleFormat:c,dateProfile:e,width:g,visibleWidth:f,hasLateralSiblings:p}))})))))}componentDidMount(){this.resetScroll(),this.scrollerRef.current.addScrollEndListener(this.clearScroll),this.disconnectInnerWidth=n.watchWidth(this.innerElRef.current,e=>{n.afterSize(()=>{this.setState({innerWidth:e})})})}componentDidUpdate(e){e.dateProfile!==this.props.dateProfile&&this.context.options.scrollTimeReset?this.resetScroll():this.updateScroll()}componentWillUnmount(){this.scrollerRef.current.removeScrollEndListener(this.clearScroll),this.disconnectInnerWidth()}resetScroll(){this.scrollDate=this.props.dateProfile.currentDate,this.updateScroll()}}const a=n.createDuration(1,"month");function s(e,t,r,o,l){const{start:s,end:c}=t.currentRange;let m=s;const d=[];for(;m.valueOf()<c.valueOf();){const s=r.add(m,a),c={start:e.skipHiddenDays(m),end:e.skipHiddenDays(s,-1,!0)};let h=i.buildDayTableRenderRange({currentRange:c,snapToWeek:!0,fixedWeekCount:o,dateEnv:r});h={start:e.skipHiddenDays(h.start),end:e.skipHiddenDays(h.end,-1,!0)};const u=t.activeRange?n.intersectRanges(t.activeRange,l?h:c):null;d.push({currentDate:t.currentDate,isValid:t.isValid,validRange:t.validRange,renderRange:h,activeRange:u,currentRange:c,currentRangeUnit:"month",isRangeAllDay:!0,dateIncrement:t.dateIncrement,slotMinTime:t.slotMaxTime,slotMaxTime:t.slotMinTime}),m=s}return d}const c=n.createFormatter({year:"numeric",month:"long"}),m=n.createFormatter({month:"long"});function d(e,t){return e||(t[0].currentRange.start.getUTCFullYear()!==t[t.length-1].currentRange.start.getUTCFullYear()?c:m)}const h={multiMonthTitleFormat:n.createFormatter,multiMonthMaxColumns:Number,multiMonthMinWidth:Number};n.injectStyles('.fc-media-screen .fc-multimonth-inner{display:flex;flex-direction:row;flex-wrap:wrap}.fc-media-print.fc-direction-ltr .fc-multimonth-inner>*{float:left}.fc-media-print.fc-direction-rtl .fc-multimonth-inner>*{float:right}.fc-media-print .fc-multimonth-inner:after{clear:both;content:"";display:block}.fc-multimonth-multicol .fc-multimonth-month{padding:1.2em}.fc-multimonth-singlecol .fc-multimonth-title{padding:.5em 0}.fc-multimonth-multicol .fc-multimonth-title{padding-bottom:1em}.fc-multimonth-title{font-size:1.2em;font-weight:700;text-align:center}.fc-multimonth-header-row{border-top:1px solid var(--fc-border-color)}.fc-multimonth-body:not(.fc-multimonth-singlecol .fc-multimonth-month:last-child .fc-multimonth-body),.fc-multimonth-header-row{border-bottom:1px solid var(--fc-border-color)}.fc-multimonth-multicol .fc-multimonth-body,.fc-multimonth-multicol .fc-multimonth-header-row{border-left:1px solid var(--fc-border-color);border-right:1px solid var(--fc-border-color);font-size:.9em;line-height:1}.fc-media-screen .fc-multimonth-singlecol .fc-multimonth-header{background:var(--fc-page-bg-color);position:sticky;top:0;z-index:2}.fc-media-screen .fc-multimonth-singlecol .fc-multimonth-body{position:relative;z-index:1}');var u=t.createPlugin({name:"@fullcalendar/multimonth",initialView:"multiMonthYear",optionRefiners:h,views:{multiMonth:{component:l,dateProfileGeneratorClass:i.TableDateProfileGenerator,multiMonthMinWidth:350,multiMonthMaxColumns:3},multiMonthYear:{type:"multiMonth",duration:{years:1},fixedWeekCount:!0,showNonCurrentDates:!1}}});return t.globalPlugins.push(u),e.default=u,Object.defineProperty(e,"__esModule",{value:!0}),e}({},FullCalendar,FullCalendar.DayGrid.Internal,FullCalendar.Internal,FullCalendar.Preact);
package/index.js CHANGED
@@ -1,15 +1,17 @@
1
1
  import { createPlugin } from '@fullcalendar/core/index.js';
2
- import { DayTableSlicer, buildDayTableModel, createDayHeaderFormatter, DayOfWeekHeaderCell, DayGridRows, buildDayTableRenderRange, TableDateProfileGenerator } from '@fullcalendar/daygrid/internal.js';
3
- import { DateComponent, memoize, compareNumbers, formatIsoMonthStr, getIsHeightAuto, NowTimer, ViewContainer, Scroller, createDuration, intersectRanges, createFormatter, injectStyles } from '@fullcalendar/core/internal.js';
2
+ import { buildDayTableModel, createDayHeaderFormatter, buildDateRowConfig, DayTableSlicer, DayGridHeaderRow, DayGridRows, buildDayTableRenderRange, TableDateProfileGenerator } from '@fullcalendar/daygrid/internal.js';
3
+ import { DateComponent, memoize, joinClassNames, fracToCssDim, formatIsoMonthStr, getIsHeightAuto, NowTimer, ViewContainer, Scroller, watchWidth, afterSize, createDuration, intersectRanges, createFormatter, injectStyles } from '@fullcalendar/core/internal.js';
4
4
  import { createElement, createRef } from '@fullcalendar/core/preact.js';
5
5
 
6
6
  class SingleMonth extends DateComponent {
7
7
  constructor() {
8
8
  super(...arguments);
9
- this.slicer = new DayTableSlicer();
10
9
  // memo
11
10
  this.buildDayTableModel = memoize(buildDayTableModel);
12
11
  this.createDayHeaderFormatter = memoize(createDayHeaderFormatter);
12
+ this.buildDateRowConfig = memoize(buildDateRowConfig);
13
+ // internal
14
+ this.slicer = new DayTableSlicer();
13
15
  }
14
16
  render() {
15
17
  const { props, context } = this;
@@ -17,27 +19,31 @@ class SingleMonth extends DateComponent {
17
19
  const { options } = context;
18
20
  const dayTableModel = this.buildDayTableModel(dateProfile, context.dateProfileGenerator);
19
21
  const slicedProps = this.slicer.sliceProps(props, dateProfile, options.nextDayThreshold, context, dayTableModel);
20
- // ensure single-month has aspect ratio
21
- const tableHeight = typeof props.width === 'number'
22
- ? props.width / options.aspectRatio
23
- : null;
24
- const rowCnt = dayTableModel.cellRows.length;
25
- const rowHeight = tableHeight != null ? tableHeight / rowCnt : null;
26
- const dayHeaderFormat = this.createDayHeaderFormatter(context.options.dayHeaderFormat, false, // datesRepDistinctDays
22
+ const dayHeaderFormat = this.createDayHeaderFormatter(options.dayHeaderFormat, false, // datesRepDistinctDays
27
23
  dayTableModel.colCnt);
28
- // TODO: tell children if we know dimensions are unstable?
29
- return (createElement("div", { "data-date": props.isoDateStr, role: "grid", className: "fc-multimonth-month fc-grow", style: { width: props.width } },
30
- createElement("div", { className: "fc-multimonth-header", style: { marginBottom: rowHeight }, role: "presentation" },
24
+ const rowConfig = this.buildDateRowConfig(dayTableModel.headerDates, false, // datesRepDistinctDays
25
+ dateProfile, props.todayRange, dayHeaderFormat, context);
26
+ const invAspectRatio = 1 / options.aspectRatio;
27
+ const invRowAspectRatio = invAspectRatio / dayTableModel.rowCnt;
28
+ const isHeaderSticky = !forPrint;
29
+ const isAspectRatio = !forPrint || props.hasLateralSiblings;
30
+ return (createElement("div", { "data-date": props.isoDateStr, className: joinClassNames('fc-multimonth-month', props.hasLateralSiblings && 'fc-break-inside-avoid'),
31
+ // override fc-liquid's basis. fc-grow isn't sufficient because doesn't set min-width:0
32
+ style: { width: props.width } },
33
+ createElement("div", { className: "fc-multimonth-header", style: {
34
+ marginBottom: isHeaderSticky ? fracToCssDim(invRowAspectRatio) : undefined,
35
+ } },
31
36
  createElement("div", { className: "fc-multimonth-title" }, context.dateEnv.format(props.dateProfile.currentRange.start, props.titleFormat)),
32
- createElement("div", { className: 'fc-multimonth-header-row fc-flex-row' }, dayTableModel.headerDates.map((headerDate) => (createElement(DayOfWeekHeaderCell, { key: headerDate.getUTCDay(), dow: headerDate.getUTCDay(), dayHeaderFormat: dayHeaderFormat, colWidth: undefined }))))),
33
- createElement("div", { className: 'fc-multimonth-body fc-flex-column', style: {
34
- marginTop: -rowHeight,
35
- height: forPrint ? '' : tableHeight,
37
+ createElement(DayGridHeaderRow, Object.assign({}, rowConfig, { className: 'fc-multimonth-header-row' }))),
38
+ createElement("div", { className: joinClassNames('fc-multimonth-body', isAspectRatio && 'fc-rel'), style: {
39
+ marginTop: isHeaderSticky ? fracToCssDim(-invRowAspectRatio) : undefined,
40
+ paddingBottom: isAspectRatio ? fracToCssDim(invAspectRatio) : undefined,
36
41
  } },
37
- createElement(DayGridRows // .fc-grow
38
- , { dateProfile: props.dateProfile, todayRange: props.todayRange, cellRows: dayTableModel.cellRows, forPrint: props.forPrint,
42
+ createElement(DayGridRows, { dateProfile: props.dateProfile, todayRange: props.todayRange, cellRows: dayTableModel.cellRows, className: isAspectRatio ? 'fc-fill' : '', forPrint: forPrint && !props.hasLateralSiblings, dayMaxEvents: forPrint ? undefined : options.dayMaxEvents, dayMaxEventRows: (forPrint && props.hasLateralSiblings) ? 1 : options.dayMaxEventRows,
39
43
  // content
40
- fgEventSegs: slicedProps.fgEventSegs, bgEventSegs: slicedProps.bgEventSegs, businessHourSegs: slicedProps.businessHourSegs, dateSelectionSegs: slicedProps.dateSelectionSegs, eventDrag: slicedProps.eventDrag, eventResize: slicedProps.eventResize, eventSelection: slicedProps.eventSelection }))));
44
+ fgEventSegs: slicedProps.fgEventSegs, bgEventSegs: slicedProps.bgEventSegs, businessHourSegs: slicedProps.businessHourSegs, dateSelectionSegs: slicedProps.dateSelectionSegs, eventDrag: slicedProps.eventDrag, eventResize: slicedProps.eventResize, eventSelection: slicedProps.eventSelection,
45
+ // dimensions
46
+ visibleWidth: props.visibleWidth }))));
41
47
  }
42
48
  }
43
49
 
@@ -49,32 +55,12 @@ class MultiMonthView extends DateComponent {
49
55
  this.buildMonthFormat = memoize(buildMonthFormat);
50
56
  // ref
51
57
  this.scrollerRef = createRef();
52
- this.innerElRef = createRef();
53
- // internal
58
+ this.innerElRef = createRef(); // .fc-multimonth-inner
54
59
  this.scrollDate = null;
55
- // Sizing
56
- // -----------------------------------------------------------------------------------------------
57
- this.handleWidth = (width) => {
58
- let { xGap, xPadding } = this.state;
59
- // for first time, assume 2 columns and query gap/padding
60
- if (xGap == null) {
61
- const innerEl = this.innerElRef.current;
62
- const children = innerEl.childNodes;
63
- if (children.length > 1) {
64
- const box0 = children[0].getBoundingClientRect();
65
- const box1 = children[1].getBoundingClientRect();
66
- let xSpan;
67
- [xGap, xSpan] = [
68
- Math.abs(box0.left - box1.right),
69
- Math.abs(box0.right - box1.left),
70
- ].sort(compareNumbers);
71
- xPadding = width - xSpan;
72
- }
73
- }
74
- this.setState({ width, xGap, xPadding });
75
- };
76
60
  this.updateScroll = () => {
77
- if (this.scrollDate != null && this.state.width != null) {
61
+ if (this.scrollDate != null &&
62
+ this.state.innerWidth != null // render completed?
63
+ ) {
78
64
  const scroller = this.scrollerRef.current;
79
65
  const innerEl = this.innerElRef.current;
80
66
  const monthEl = innerEl.querySelector(`[data-date="${formatIsoMonthStr(this.scrollDate)}"]`);
@@ -92,32 +78,33 @@ class MultiMonthView extends DateComponent {
92
78
  const { context, props, state } = this;
93
79
  const { options } = context;
94
80
  const verticalScrolling = !props.forPrint && !getIsHeightAuto(options);
95
- const colCount = state.width == null
96
- ? 2
97
- : Math.min(options.multiMonthMaxColumns, Math.floor((state.width - state.xPadding + state.xGap) /
98
- (options.multiMonthMinWidth + state.xGap)));
99
- const monthWidth = state.width == null
100
- ? '34%' // will expand. now small enough to be 1/3. for allowing gap
101
- : Math.floor(// exact values can cause expansion to other rows
102
- (state.width - state.xPadding - (state.xGap * (colCount - 1))) /
103
- colCount);
104
81
  const monthDateProfiles = this.splitDateProfileByMonth(context.dateProfileGenerator, props.dateProfile, context.dateEnv, options.fixedWeekCount, options.showNonCurrentDates);
105
82
  const monthTitleFormat = this.buildMonthFormat(options.multiMonthTitleFormat, monthDateProfiles);
106
- const rootClassNames = [
107
- 'fc-multimonth-view',
108
- (colCount === 1) ?
83
+ const { multiMonthMinWidth, multiMonthMaxColumns } = options;
84
+ const { innerWidth } = state;
85
+ let cols;
86
+ let computedMonthWidth;
87
+ let cssMonthWidth;
88
+ let hasLateralSiblings = false;
89
+ if (innerWidth != null) {
90
+ cols = Math.max(1, Math.min(multiMonthMaxColumns, Math.floor(innerWidth / multiMonthMinWidth)));
91
+ if (props.forPrint) {
92
+ cols = Math.min(cols, 2);
93
+ }
94
+ computedMonthWidth = innerWidth / cols;
95
+ cssMonthWidth = fracToCssDim(1 / cols);
96
+ hasLateralSiblings = cols > 1;
97
+ }
98
+ return (createElement(NowTimer, { unit: "day" }, (nowDate, todayRange) => (createElement(ViewContainer, { className: joinClassNames('fc-multimonth fc-border', (cols === 1) ?
109
99
  'fc-multimonth-singlecol' :
110
- 'fc-multimonth-multicol',
111
- 'fc-flex-column',
112
- 'fc-border', // BAD to mix this with size-listening?
113
- ];
114
- return (createElement(NowTimer, { unit: "day" }, (nowDate, todayRange) => (createElement(ViewContainer, { elClasses: rootClassNames, viewSpec: context.viewSpec },
115
- createElement(Scroller, { vertical: verticalScrolling, elClassNames: [
116
- verticalScrolling ? 'fc-liquid' : '',
117
- ], ref: this.scrollerRef, widthRef: this.handleWidth },
118
- createElement("div", { ref: this.innerElRef, className: 'fc-multimonth-inner' }, monthDateProfiles.map((monthDateProfile, i) => {
100
+ 'fc-multimonth-multicol',
101
+ // HACK for Safari. Can't do break-inside:avoid with flexbox items, likely b/c it's not standard:
102
+ // https://stackoverflow.com/a/60256345
103
+ !props.forPrint && 'fc-flex-col'), viewSpec: context.viewSpec },
104
+ createElement(Scroller, { vertical: verticalScrolling, className: verticalScrolling ? 'fc-liquid' : '', ref: this.scrollerRef },
105
+ createElement("div", { ref: this.innerElRef, className: 'fc-multimonth-inner' }, monthDateProfiles.map((monthDateProfile) => {
119
106
  const monthStr = formatIsoMonthStr(monthDateProfile.currentRange.start);
120
- return (createElement(SingleMonth, Object.assign({}, props, { key: monthStr, todayRange: todayRange, isoDateStr: monthStr, titleFormat: monthTitleFormat, dateProfile: monthDateProfile, width: monthWidth })));
107
+ return (createElement(SingleMonth, Object.assign({}, props, { key: monthStr, todayRange: todayRange, isoDateStr: monthStr, titleFormat: monthTitleFormat, dateProfile: monthDateProfile, width: cssMonthWidth, visibleWidth: computedMonthWidth, hasLateralSiblings: hasLateralSiblings })));
121
108
  })))))));
122
109
  }
123
110
  // Lifecycle
@@ -125,6 +112,11 @@ class MultiMonthView extends DateComponent {
125
112
  componentDidMount() {
126
113
  this.resetScroll();
127
114
  this.scrollerRef.current.addScrollEndListener(this.clearScroll);
115
+ this.disconnectInnerWidth = watchWidth(this.innerElRef.current, (innerWidth) => {
116
+ afterSize(() => {
117
+ this.setState({ innerWidth });
118
+ });
119
+ });
128
120
  }
129
121
  componentDidUpdate(prevProps) {
130
122
  if (prevProps.dateProfile !== this.props.dateProfile && this.context.options.scrollTimeReset) {
@@ -138,6 +130,7 @@ class MultiMonthView extends DateComponent {
138
130
  }
139
131
  componentWillUnmount() {
140
132
  this.scrollerRef.current.removeScrollEndListener(this.clearScroll);
133
+ this.disconnectInnerWidth();
141
134
  }
142
135
  // Scrolling
143
136
  // -----------------------------------------------------------------------------------------------
@@ -209,7 +202,7 @@ const OPTION_REFINERS = {
209
202
  multiMonthMinWidth: Number,
210
203
  };
211
204
 
212
- var css_248z = ".fc-multimonth-inner{display:flex;flex-wrap:wrap}.fc-multimonth-multicol .fc-multimonth-month{margin:0 1.2em 1.2em}.fc-multimonth-multicol .fc-multimonth-title{padding:1em 0}.fc-multimonth-singlecol .fc-multimonth-title{padding:.5em 0}.fc-multimonth-title{font-size:1.2em;font-weight:700;text-align:center}.fc-multimonth-header-row{border-top:1px solid var(--fc-border-color)}.fc-multimonth-header-row,.fc-multimonth-month{border-bottom:1px solid var(--fc-border-color)}.fc-multimonth-singlecol .fc-multimonth-month:last-child{border-bottom:0}.fc-multimonth-multicol .fc-multimonth-body,.fc-multimonth-multicol .fc-multimonth-header-row{border-left:1px solid var(--fc-border-color);border-right:1px solid var(--fc-border-color);font-size:.9em;line-height:1}.fc-media-screen .fc-multimonth-singlecol .fc-multimonth-header{background:var(--fc-page-bg-color);position:sticky;top:0;z-index:2}.fc-media-screen .fc-multimonth-singlecol .fc-multimonth-body{position:relative;z-index:1}";
205
+ var css_248z = ".fc-media-screen .fc-multimonth-inner{display:flex;flex-direction:row;flex-wrap:wrap}.fc-media-print.fc-direction-ltr .fc-multimonth-inner>*{float:left}.fc-media-print.fc-direction-rtl .fc-multimonth-inner>*{float:right}.fc-media-print .fc-multimonth-inner:after{clear:both;content:\"\";display:block}.fc-multimonth-multicol .fc-multimonth-month{padding:1.2em}.fc-multimonth-singlecol .fc-multimonth-title{padding:.5em 0}.fc-multimonth-multicol .fc-multimonth-title{padding-bottom:1em}.fc-multimonth-title{font-size:1.2em;font-weight:700;text-align:center}.fc-multimonth-header-row{border-top:1px solid var(--fc-border-color)}.fc-multimonth-body:not(.fc-multimonth-singlecol .fc-multimonth-month:last-child .fc-multimonth-body),.fc-multimonth-header-row{border-bottom:1px solid var(--fc-border-color)}.fc-multimonth-multicol .fc-multimonth-body,.fc-multimonth-multicol .fc-multimonth-header-row{border-left:1px solid var(--fc-border-color);border-right:1px solid var(--fc-border-color);font-size:.9em;line-height:1}.fc-media-screen .fc-multimonth-singlecol .fc-multimonth-header{background:var(--fc-page-bg-color);position:sticky;top:0;z-index:2}.fc-media-screen .fc-multimonth-singlecol .fc-multimonth-body{position:relative;z-index:1}";
213
206
  injectStyles(css_248z);
214
207
 
215
208
  var index = createPlugin({
@@ -227,7 +220,7 @@ var index = createPlugin({
227
220
  type: 'multiMonth',
228
221
  duration: { years: 1 },
229
222
  fixedWeekCount: true,
230
- showNonCurrentDates: false,
223
+ showNonCurrentDates: false, // TODO: looks bad when single-col layout
231
224
  },
232
225
  },
233
226
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fullcalendar/multimonth",
3
- "version": "7.0.0-beta.1",
3
+ "version": "7.0.0-beta.3",
4
4
  "title": "FullCalendar Multi-Month Plugin",
5
5
  "description": "Display a sequence or grid of multiple months",
6
6
  "keywords": [
@@ -12,10 +12,10 @@
12
12
  ],
13
13
  "homepage": "https://fullcalendar.io/docs/multimonth-grid",
14
14
  "dependencies": {
15
- "@fullcalendar/daygrid": "7.0.0-beta.1"
15
+ "@fullcalendar/daygrid": "7.0.0-beta.3"
16
16
  },
17
17
  "peerDependencies": {
18
- "@fullcalendar/core": "7.0.0-beta.1"
18
+ "@fullcalendar/core": "7.0.0-beta.3"
19
19
  },
20
20
  "type": "module",
21
21
  "bugs": "https://fullcalendar.io/reporting-bugs",