@fullcalendar/multimonth 6.1.15 → 7.0.0-beta.0

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,150 +10,133 @@ 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.buildDayTableModel = internal_cjs.memoize(internal_cjs$1.buildDayTableModel);
14
13
  this.slicer = new internal_cjs$1.DayTableSlicer();
15
- this.state = {
16
- labelId: internal_cjs.getUniqueDomId(),
17
- };
14
+ // memo
15
+ this.buildDayTableModel = internal_cjs.memoize(internal_cjs$1.buildDayTableModel);
16
+ this.createDayHeaderFormatter = internal_cjs.memoize(internal_cjs$1.createDayHeaderFormatter);
18
17
  }
19
18
  render() {
20
- const { props, state, context } = this;
19
+ const { props, context } = this;
21
20
  const { dateProfile, forPrint } = props;
22
21
  const { options } = context;
23
22
  const dayTableModel = this.buildDayTableModel(dateProfile, context.dateProfileGenerator);
24
23
  const slicedProps = this.slicer.sliceProps(props, dateProfile, options.nextDayThreshold, context, dayTableModel);
25
24
  // ensure single-month has aspect ratio
26
- const tableHeight = props.tableWidth != null ? props.tableWidth / options.aspectRatio : null;
27
- const rowCnt = dayTableModel.cells.length;
25
+ const tableHeight = typeof props.width === 'number'
26
+ ? props.width / options.aspectRatio
27
+ : null;
28
+ const rowCnt = dayTableModel.cellRows.length;
28
29
  const rowHeight = tableHeight != null ? tableHeight / rowCnt : null;
29
- return (preact_cjs.createElement("div", { ref: props.elRef, "data-date": props.isoDateStr, className: "fc-multimonth-month", style: { width: props.width }, role: "grid", "aria-labelledby": state.labelId },
30
+ const dayHeaderFormat = this.createDayHeaderFormatter(context.options.dayHeaderFormat, false, // datesRepDistinctDays
31
+ 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 } },
30
34
  preact_cjs.createElement("div", { className: "fc-multimonth-header", style: { marginBottom: rowHeight }, role: "presentation" },
31
- preact_cjs.createElement("div", { className: "fc-multimonth-title", id: state.labelId }, context.dateEnv.format(props.dateProfile.currentRange.start, props.titleFormat)),
32
- preact_cjs.createElement("table", { className: [
33
- 'fc-multimonth-header-table',
34
- context.theme.getClass('table'),
35
- ].join(' '), role: "presentation" },
36
- preact_cjs.createElement("thead", { role: "rowgroup" },
37
- preact_cjs.createElement(internal_cjs.DayHeader, { dateProfile: props.dateProfile, dates: dayTableModel.headerDates, datesRepDistinctDays: false })))),
38
- preact_cjs.createElement("div", { className: [
39
- 'fc-multimonth-daygrid',
40
- 'fc-daygrid',
41
- 'fc-daygrid-body',
42
- !forPrint && 'fc-daygrid-body-balanced',
43
- forPrint && 'fc-daygrid-body-unbalanced',
44
- forPrint && 'fc-daygrid-body-natural',
45
- ].join(' '), style: { marginTop: -rowHeight } },
46
- preact_cjs.createElement("table", { className: [
47
- 'fc-multimonth-daygrid-table',
48
- context.theme.getClass('table'),
49
- ].join(' '), style: { height: forPrint ? '' : tableHeight }, role: "presentation" },
50
- preact_cjs.createElement("tbody", { role: "rowgroup" },
51
- preact_cjs.createElement(internal_cjs$1.TableRows, Object.assign({}, slicedProps, { dateProfile: dateProfile, cells: dayTableModel.cells, eventSelection: props.eventSelection, dayMaxEvents: !forPrint, dayMaxEventRows: !forPrint, showWeekNumbers: options.weekNumbers, clientWidth: props.clientWidth, clientHeight: props.clientHeight, forPrint: forPrint })))))));
35
+ 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,
40
+ } },
41
+ preact_cjs.createElement(internal_cjs$1.DayGridRows // .fc-grow
42
+ , { dateProfile: props.dateProfile, todayRange: props.todayRange, cellRows: dayTableModel.cellRows, forPrint: props.forPrint,
43
+ // content
44
+ fgEventSegs: slicedProps.fgEventSegs, bgEventSegs: slicedProps.bgEventSegs, businessHourSegs: slicedProps.businessHourSegs, dateSelectionSegs: slicedProps.dateSelectionSegs, eventDrag: slicedProps.eventDrag, eventResize: slicedProps.eventResize, eventSelection: slicedProps.eventSelection }))));
52
45
  }
53
46
  }
54
47
 
55
48
  class MultiMonthView extends internal_cjs.DateComponent {
56
49
  constructor() {
57
50
  super(...arguments);
51
+ // memo
58
52
  this.splitDateProfileByMonth = internal_cjs.memoize(splitDateProfileByMonth);
59
53
  this.buildMonthFormat = internal_cjs.memoize(buildMonthFormat);
60
- this.scrollElRef = preact_cjs.createRef();
61
- this.firstMonthElRef = preact_cjs.createRef();
62
- this.needsScrollReset = false;
63
- this.handleSizing = (isForced) => {
64
- if (isForced) {
65
- this.updateSize();
54
+ // ref
55
+ this.rootElRef = preact_cjs.createRef(); // also the scroll container
56
+ this.innerElRef = preact_cjs.createRef();
57
+ this.handleClientWidth = (clientWidth) => {
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(internal_cjs.compareNumbers);
71
+ xPadding = clientWidth - xSpan;
72
+ }
66
73
  }
74
+ this.setState({ clientWidth, xGap, xPadding });
67
75
  };
76
+ this.timeScrollResponder = new internal_cjs.ScrollResponder((_time) => {
77
+ // HACK to scroll to day
78
+ if (this.state.clientWidth != null) {
79
+ const { currentDate } = this.props.dateProfile;
80
+ const rootEl = this.rootElRef.current;
81
+ const innerEl = this.innerElRef.current;
82
+ const monthEl = innerEl.querySelector(`[data-date="${internal_cjs.formatIsoMonthStr(currentDate)}"]`);
83
+ rootEl.scrollTop = Math.ceil(// for fractions, err on the side of scrolling further
84
+ monthEl.getBoundingClientRect().top -
85
+ innerEl.getBoundingClientRect().top);
86
+ return true;
87
+ }
88
+ return false;
89
+ });
68
90
  }
69
91
  render() {
70
92
  const { context, props, state } = this;
71
93
  const { options } = context;
72
- const { clientWidth, clientHeight } = state;
73
- const monthHPadding = state.monthHPadding || 0;
74
- const colCount = Math.min(clientWidth != null ?
75
- Math.floor(clientWidth / (options.multiMonthMinWidth + monthHPadding)) :
76
- 1, options.multiMonthMaxColumns) || 1;
77
- const monthWidthPct = (100 / colCount) + '%';
78
- const monthTableWidth = clientWidth == null ? null :
79
- (clientWidth / colCount) - monthHPadding;
80
- const isLegitSingleCol = clientWidth != null && colCount === 1;
81
- const monthDateProfiles = this.splitDateProfileByMonth(context.dateProfileGenerator, props.dateProfile, context.dateEnv, isLegitSingleCol ? false : options.fixedWeekCount, options.showNonCurrentDates);
94
+ const colCount = state.clientWidth == null
95
+ ? 2
96
+ : Math.min(options.multiMonthMaxColumns, Math.floor((state.clientWidth - state.xPadding + state.xGap) /
97
+ (options.multiMonthMinWidth + state.xGap)));
98
+ const monthWidth = state.clientWidth == null
99
+ ? '34%' // will expand. now small enough to be 1/3. for allowing gap
100
+ : Math.floor(// exact values can cause expansion to other rows
101
+ (state.clientWidth - state.xPadding - (state.xGap * (colCount - 1))) /
102
+ colCount);
103
+ const monthDateProfiles = this.splitDateProfileByMonth(context.dateProfileGenerator, props.dateProfile, context.dateEnv, options.fixedWeekCount, options.showNonCurrentDates);
82
104
  const monthTitleFormat = this.buildMonthFormat(options.multiMonthTitleFormat, monthDateProfiles);
83
105
  const rootClassNames = [
84
- 'fc-multimonth',
85
- isLegitSingleCol ?
106
+ 'fc-multimonth-view',
107
+ (colCount === 1) ?
86
108
  'fc-multimonth-singlecol' :
87
109
  'fc-multimonth-multicol',
88
- (monthTableWidth != null && monthTableWidth < 400) ?
89
- 'fc-multimonth-compact' :
90
- '',
91
- props.isHeightAuto ?
110
+ internal_cjs.getIsHeightAuto(options) ?
92
111
  '' :
93
- 'fc-scroller', // for AutoScroller
112
+ 'fc-multimonth-scroll',
113
+ 'fc-border', // BAD to mix this with size-listening?
94
114
  ];
95
- return (preact_cjs.createElement(internal_cjs.ViewContainer, { elRef: this.scrollElRef, elClasses: rootClassNames, viewSpec: context.viewSpec }, monthDateProfiles.map((monthDateProfile, i) => {
96
- const monthStr = internal_cjs.formatIsoMonthStr(monthDateProfile.currentRange.start);
97
- return (preact_cjs.createElement(SingleMonth, Object.assign({}, props, { key: monthStr, isoDateStr: monthStr, elRef: i === 0 ? this.firstMonthElRef : undefined, titleFormat: monthTitleFormat, dateProfile: monthDateProfile, width: monthWidthPct, tableWidth: monthTableWidth, clientWidth: clientWidth, clientHeight: clientHeight })));
98
- })));
115
+ return (preact_cjs.createElement(internal_cjs.NowTimer, { unit: "day" }, (nowDate, todayRange) => (preact_cjs.createElement(internal_cjs.ViewContainer, { elRef: this.rootElRef, elClasses: rootClassNames, viewSpec: context.viewSpec },
116
+ preact_cjs.createElement("div", { ref: this.innerElRef, className: 'fc-multimonth-inner' }, monthDateProfiles.map((monthDateProfile, i) => {
117
+ const monthStr = internal_cjs.formatIsoMonthStr(monthDateProfile.currentRange.start);
118
+ return (preact_cjs.createElement(SingleMonth, Object.assign({}, props, { key: monthStr, todayRange: todayRange, isoDateStr: monthStr, titleFormat: monthTitleFormat, dateProfile: monthDateProfile, width: monthWidth })));
119
+ }))))));
99
120
  }
100
121
  componentDidMount() {
101
- this.updateSize();
102
- this.context.addResizeHandler(this.handleSizing);
103
- this.requestScrollReset();
122
+ const { context } = this;
123
+ const { options } = context;
124
+ this.unwatchWidth = internal_cjs.watchWidth(this.rootElRef.current, this.handleClientWidth);
125
+ context.emitter.on('_timeScrollRequest', this.timeScrollResponder.handleScroll);
126
+ this.timeScrollResponder.handleScroll(options.scrollTime);
104
127
  }
105
128
  componentDidUpdate(prevProps) {
106
- if (!internal_cjs.isPropsEqual(prevProps, this.props)) { // an external change?
107
- this.handleSizing(false);
108
- }
109
- if (prevProps.dateProfile !== this.props.dateProfile) {
110
- this.requestScrollReset();
129
+ const { options } = this.context;
130
+ if (prevProps.dateProfile !== this.props.dateProfile && options.scrollTimeReset) {
131
+ this.timeScrollResponder.handleScroll(options.scrollTime);
111
132
  }
112
133
  else {
113
- this.flushScrollReset();
134
+ this.timeScrollResponder.drain();
114
135
  }
115
136
  }
116
137
  componentWillUnmount() {
117
- this.context.removeResizeHandler(this.handleSizing);
118
- }
119
- updateSize() {
120
- const scrollEl = this.scrollElRef.current;
121
- const firstMonthEl = this.firstMonthElRef.current;
122
- if (scrollEl) {
123
- this.setState({
124
- clientWidth: scrollEl.clientWidth,
125
- clientHeight: scrollEl.clientHeight,
126
- });
127
- }
128
- if (firstMonthEl && scrollEl) {
129
- if (this.state.monthHPadding == null) { // always remember initial non-zero value
130
- this.setState({
131
- monthHPadding: scrollEl.clientWidth - // go within padding
132
- firstMonthEl.firstChild.offsetWidth,
133
- });
134
- }
135
- }
136
- }
137
- requestScrollReset() {
138
- this.needsScrollReset = true;
139
- this.flushScrollReset();
140
- }
141
- flushScrollReset() {
142
- if (this.needsScrollReset &&
143
- this.state.monthHPadding != null // indicates sizing already happened
144
- ) {
145
- const { currentDate } = this.props.dateProfile;
146
- const scrollEl = this.scrollElRef.current;
147
- const monthEl = scrollEl.querySelector(`[data-date="${internal_cjs.formatIsoMonthStr(currentDate)}"]`);
148
- scrollEl.scrollTop = monthEl.getBoundingClientRect().top -
149
- this.firstMonthElRef.current.getBoundingClientRect().top;
150
- this.needsScrollReset = false;
151
- }
152
- }
153
- // workaround for when queued setState render (w/ clientWidth) gets cancelled because
154
- // subsequent update and shouldComponentUpdate says not to render :(
155
- shouldComponentUpdate() {
156
- return true;
138
+ this.unwatchWidth();
139
+ this.context.emitter.off('_timeScrollRequest', this.timeScrollResponder.handleScroll);
157
140
  }
158
141
  }
159
142
  // date profile
@@ -219,7 +202,7 @@ const OPTION_REFINERS = {
219
202
  multiMonthMinWidth: Number,
220
203
  };
221
204
 
222
- var css_248z = ".fc .fc-multimonth{border:1px solid var(--fc-border-color);display:flex;flex-wrap:wrap;overflow-x:hidden;overflow-y:auto}.fc .fc-multimonth-title{font-size:1.2em;font-weight:700;padding:1em 0;text-align:center}.fc .fc-multimonth-daygrid{background:var(--fc-page-bg-color)}.fc .fc-multimonth-daygrid-table,.fc .fc-multimonth-header-table{table-layout:fixed;width:100%}.fc .fc-multimonth-daygrid-table{border-top-style:hidden!important}.fc .fc-multimonth-singlecol .fc-multimonth{position:relative}.fc .fc-multimonth-singlecol .fc-multimonth-header{background:var(--fc-page-bg-color);position:relative;top:0;z-index:2}.fc .fc-multimonth-singlecol .fc-multimonth-daygrid{position:relative;z-index:1}.fc .fc-multimonth-singlecol .fc-multimonth-daygrid-table,.fc .fc-multimonth-singlecol .fc-multimonth-header-table{border-left-style:hidden;border-right-style:hidden}.fc .fc-multimonth-singlecol .fc-multimonth-month:last-child .fc-multimonth-daygrid-table{border-bottom-style:hidden}.fc .fc-multimonth-multicol{line-height:1}.fc .fc-multimonth-multicol .fc-multimonth-month{padding:0 1.2em 1.2em}.fc .fc-multimonth-multicol .fc-daygrid-more-link{border:1px solid var(--fc-event-border-color);display:block;float:none;padding:1px}.fc .fc-multimonth-compact{line-height:1}.fc .fc-multimonth-compact .fc-multimonth-daygrid-table,.fc .fc-multimonth-compact .fc-multimonth-header-table{font-size:.9em}.fc-media-screen .fc-multimonth-singlecol .fc-multimonth-header{position:sticky}.fc-media-print .fc-multimonth{overflow:visible}";
205
+ var css_248z = ".fc-multimonth-scroll{overflow-x:hidden;overflow-y:scroll}.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}";
223
206
  internal_cjs.injectStyles(css_248z);
224
207
 
225
208
  var index = index_cjs.createPlugin({
package/index.global.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- FullCalendar Multi-Month Plugin v6.1.15
2
+ FullCalendar Multi-Month Plugin v7.0.0-beta.0
3
3
  Docs & License: https://fullcalendar.io/docs/multimonth-grid
4
4
  (c) 2024 Adam Shaw
5
5
  */
@@ -9,150 +9,133 @@ 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.buildDayTableModel = internal.memoize(internal$1.buildDayTableModel);
13
12
  this.slicer = new internal$1.DayTableSlicer();
14
- this.state = {
15
- labelId: internal.getUniqueDomId(),
16
- };
13
+ // memo
14
+ this.buildDayTableModel = internal.memoize(internal$1.buildDayTableModel);
15
+ this.createDayHeaderFormatter = internal.memoize(internal$1.createDayHeaderFormatter);
17
16
  }
18
17
  render() {
19
- const { props, state, context } = this;
18
+ const { props, context } = this;
20
19
  const { dateProfile, forPrint } = props;
21
20
  const { options } = context;
22
21
  const dayTableModel = this.buildDayTableModel(dateProfile, context.dateProfileGenerator);
23
22
  const slicedProps = this.slicer.sliceProps(props, dateProfile, options.nextDayThreshold, context, dayTableModel);
24
23
  // ensure single-month has aspect ratio
25
- const tableHeight = props.tableWidth != null ? props.tableWidth / options.aspectRatio : null;
26
- const rowCnt = dayTableModel.cells.length;
24
+ const tableHeight = typeof props.width === 'number'
25
+ ? props.width / options.aspectRatio
26
+ : null;
27
+ const rowCnt = dayTableModel.cellRows.length;
27
28
  const rowHeight = tableHeight != null ? tableHeight / rowCnt : null;
28
- return (preact.createElement("div", { ref: props.elRef, "data-date": props.isoDateStr, className: "fc-multimonth-month", style: { width: props.width }, role: "grid", "aria-labelledby": state.labelId },
29
+ const dayHeaderFormat = this.createDayHeaderFormatter(context.options.dayHeaderFormat, false, // datesRepDistinctDays
30
+ 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 } },
29
33
  preact.createElement("div", { className: "fc-multimonth-header", style: { marginBottom: rowHeight }, role: "presentation" },
30
- preact.createElement("div", { className: "fc-multimonth-title", id: state.labelId }, context.dateEnv.format(props.dateProfile.currentRange.start, props.titleFormat)),
31
- preact.createElement("table", { className: [
32
- 'fc-multimonth-header-table',
33
- context.theme.getClass('table'),
34
- ].join(' '), role: "presentation" },
35
- preact.createElement("thead", { role: "rowgroup" },
36
- preact.createElement(internal.DayHeader, { dateProfile: props.dateProfile, dates: dayTableModel.headerDates, datesRepDistinctDays: false })))),
37
- preact.createElement("div", { className: [
38
- 'fc-multimonth-daygrid',
39
- 'fc-daygrid',
40
- 'fc-daygrid-body',
41
- !forPrint && 'fc-daygrid-body-balanced',
42
- forPrint && 'fc-daygrid-body-unbalanced',
43
- forPrint && 'fc-daygrid-body-natural',
44
- ].join(' '), style: { marginTop: -rowHeight } },
45
- preact.createElement("table", { className: [
46
- 'fc-multimonth-daygrid-table',
47
- context.theme.getClass('table'),
48
- ].join(' '), style: { height: forPrint ? '' : tableHeight }, role: "presentation" },
49
- preact.createElement("tbody", { role: "rowgroup" },
50
- preact.createElement(internal$1.TableRows, Object.assign({}, slicedProps, { dateProfile: dateProfile, cells: dayTableModel.cells, eventSelection: props.eventSelection, dayMaxEvents: !forPrint, dayMaxEventRows: !forPrint, showWeekNumbers: options.weekNumbers, clientWidth: props.clientWidth, clientHeight: props.clientHeight, forPrint: forPrint })))))));
34
+ 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,
39
+ } },
40
+ preact.createElement(internal$1.DayGridRows // .fc-grow
41
+ , { dateProfile: props.dateProfile, todayRange: props.todayRange, cellRows: dayTableModel.cellRows, forPrint: props.forPrint,
42
+ // content
43
+ fgEventSegs: slicedProps.fgEventSegs, bgEventSegs: slicedProps.bgEventSegs, businessHourSegs: slicedProps.businessHourSegs, dateSelectionSegs: slicedProps.dateSelectionSegs, eventDrag: slicedProps.eventDrag, eventResize: slicedProps.eventResize, eventSelection: slicedProps.eventSelection }))));
51
44
  }
52
45
  }
53
46
 
54
47
  class MultiMonthView extends internal.DateComponent {
55
48
  constructor() {
56
49
  super(...arguments);
50
+ // memo
57
51
  this.splitDateProfileByMonth = internal.memoize(splitDateProfileByMonth);
58
52
  this.buildMonthFormat = internal.memoize(buildMonthFormat);
59
- this.scrollElRef = preact.createRef();
60
- this.firstMonthElRef = preact.createRef();
61
- this.needsScrollReset = false;
62
- this.handleSizing = (isForced) => {
63
- if (isForced) {
64
- this.updateSize();
53
+ // ref
54
+ this.rootElRef = preact.createRef(); // also the scroll container
55
+ this.innerElRef = preact.createRef();
56
+ this.handleClientWidth = (clientWidth) => {
57
+ let { xGap, xPadding } = this.state;
58
+ // for first time, assume 2 columns and query gap/padding
59
+ if (xGap == null) {
60
+ const innerEl = this.innerElRef.current;
61
+ const children = innerEl.childNodes;
62
+ if (children.length > 1) {
63
+ const box0 = children[0].getBoundingClientRect();
64
+ const box1 = children[1].getBoundingClientRect();
65
+ let xSpan;
66
+ [xGap, xSpan] = [
67
+ Math.abs(box0.left - box1.right),
68
+ Math.abs(box0.right - box1.left),
69
+ ].sort(internal.compareNumbers);
70
+ xPadding = clientWidth - xSpan;
71
+ }
65
72
  }
73
+ this.setState({ clientWidth, xGap, xPadding });
66
74
  };
75
+ this.timeScrollResponder = new internal.ScrollResponder((_time) => {
76
+ // HACK to scroll to day
77
+ if (this.state.clientWidth != null) {
78
+ const { currentDate } = this.props.dateProfile;
79
+ const rootEl = this.rootElRef.current;
80
+ const innerEl = this.innerElRef.current;
81
+ const monthEl = innerEl.querySelector(`[data-date="${internal.formatIsoMonthStr(currentDate)}"]`);
82
+ rootEl.scrollTop = Math.ceil(// for fractions, err on the side of scrolling further
83
+ monthEl.getBoundingClientRect().top -
84
+ innerEl.getBoundingClientRect().top);
85
+ return true;
86
+ }
87
+ return false;
88
+ });
67
89
  }
68
90
  render() {
69
91
  const { context, props, state } = this;
70
92
  const { options } = context;
71
- const { clientWidth, clientHeight } = state;
72
- const monthHPadding = state.monthHPadding || 0;
73
- const colCount = Math.min(clientWidth != null ?
74
- Math.floor(clientWidth / (options.multiMonthMinWidth + monthHPadding)) :
75
- 1, options.multiMonthMaxColumns) || 1;
76
- const monthWidthPct = (100 / colCount) + '%';
77
- const monthTableWidth = clientWidth == null ? null :
78
- (clientWidth / colCount) - monthHPadding;
79
- const isLegitSingleCol = clientWidth != null && colCount === 1;
80
- const monthDateProfiles = this.splitDateProfileByMonth(context.dateProfileGenerator, props.dateProfile, context.dateEnv, isLegitSingleCol ? false : options.fixedWeekCount, options.showNonCurrentDates);
93
+ const colCount = state.clientWidth == null
94
+ ? 2
95
+ : Math.min(options.multiMonthMaxColumns, Math.floor((state.clientWidth - state.xPadding + state.xGap) /
96
+ (options.multiMonthMinWidth + state.xGap)));
97
+ const monthWidth = state.clientWidth == null
98
+ ? '34%' // will expand. now small enough to be 1/3. for allowing gap
99
+ : Math.floor(// exact values can cause expansion to other rows
100
+ (state.clientWidth - state.xPadding - (state.xGap * (colCount - 1))) /
101
+ colCount);
102
+ const monthDateProfiles = this.splitDateProfileByMonth(context.dateProfileGenerator, props.dateProfile, context.dateEnv, options.fixedWeekCount, options.showNonCurrentDates);
81
103
  const monthTitleFormat = this.buildMonthFormat(options.multiMonthTitleFormat, monthDateProfiles);
82
104
  const rootClassNames = [
83
- 'fc-multimonth',
84
- isLegitSingleCol ?
105
+ 'fc-multimonth-view',
106
+ (colCount === 1) ?
85
107
  'fc-multimonth-singlecol' :
86
108
  'fc-multimonth-multicol',
87
- (monthTableWidth != null && monthTableWidth < 400) ?
88
- 'fc-multimonth-compact' :
89
- '',
90
- props.isHeightAuto ?
109
+ internal.getIsHeightAuto(options) ?
91
110
  '' :
92
- 'fc-scroller', // for AutoScroller
111
+ 'fc-multimonth-scroll',
112
+ 'fc-border', // BAD to mix this with size-listening?
93
113
  ];
94
- return (preact.createElement(internal.ViewContainer, { elRef: this.scrollElRef, elClasses: rootClassNames, viewSpec: context.viewSpec }, monthDateProfiles.map((monthDateProfile, i) => {
95
- const monthStr = internal.formatIsoMonthStr(monthDateProfile.currentRange.start);
96
- return (preact.createElement(SingleMonth, Object.assign({}, props, { key: monthStr, isoDateStr: monthStr, elRef: i === 0 ? this.firstMonthElRef : undefined, titleFormat: monthTitleFormat, dateProfile: monthDateProfile, width: monthWidthPct, tableWidth: monthTableWidth, clientWidth: clientWidth, clientHeight: clientHeight })));
97
- })));
114
+ return (preact.createElement(internal.NowTimer, { unit: "day" }, (nowDate, todayRange) => (preact.createElement(internal.ViewContainer, { elRef: this.rootElRef, elClasses: rootClassNames, viewSpec: context.viewSpec },
115
+ preact.createElement("div", { ref: this.innerElRef, className: 'fc-multimonth-inner' }, monthDateProfiles.map((monthDateProfile, i) => {
116
+ const monthStr = internal.formatIsoMonthStr(monthDateProfile.currentRange.start);
117
+ return (preact.createElement(SingleMonth, Object.assign({}, props, { key: monthStr, todayRange: todayRange, isoDateStr: monthStr, titleFormat: monthTitleFormat, dateProfile: monthDateProfile, width: monthWidth })));
118
+ }))))));
98
119
  }
99
120
  componentDidMount() {
100
- this.updateSize();
101
- this.context.addResizeHandler(this.handleSizing);
102
- this.requestScrollReset();
121
+ const { context } = this;
122
+ const { options } = context;
123
+ this.unwatchWidth = internal.watchWidth(this.rootElRef.current, this.handleClientWidth);
124
+ context.emitter.on('_timeScrollRequest', this.timeScrollResponder.handleScroll);
125
+ this.timeScrollResponder.handleScroll(options.scrollTime);
103
126
  }
104
127
  componentDidUpdate(prevProps) {
105
- if (!internal.isPropsEqual(prevProps, this.props)) { // an external change?
106
- this.handleSizing(false);
107
- }
108
- if (prevProps.dateProfile !== this.props.dateProfile) {
109
- this.requestScrollReset();
128
+ const { options } = this.context;
129
+ if (prevProps.dateProfile !== this.props.dateProfile && options.scrollTimeReset) {
130
+ this.timeScrollResponder.handleScroll(options.scrollTime);
110
131
  }
111
132
  else {
112
- this.flushScrollReset();
133
+ this.timeScrollResponder.drain();
113
134
  }
114
135
  }
115
136
  componentWillUnmount() {
116
- this.context.removeResizeHandler(this.handleSizing);
117
- }
118
- updateSize() {
119
- const scrollEl = this.scrollElRef.current;
120
- const firstMonthEl = this.firstMonthElRef.current;
121
- if (scrollEl) {
122
- this.setState({
123
- clientWidth: scrollEl.clientWidth,
124
- clientHeight: scrollEl.clientHeight,
125
- });
126
- }
127
- if (firstMonthEl && scrollEl) {
128
- if (this.state.monthHPadding == null) { // always remember initial non-zero value
129
- this.setState({
130
- monthHPadding: scrollEl.clientWidth - // go within padding
131
- firstMonthEl.firstChild.offsetWidth,
132
- });
133
- }
134
- }
135
- }
136
- requestScrollReset() {
137
- this.needsScrollReset = true;
138
- this.flushScrollReset();
139
- }
140
- flushScrollReset() {
141
- if (this.needsScrollReset &&
142
- this.state.monthHPadding != null // indicates sizing already happened
143
- ) {
144
- const { currentDate } = this.props.dateProfile;
145
- const scrollEl = this.scrollElRef.current;
146
- const monthEl = scrollEl.querySelector(`[data-date="${internal.formatIsoMonthStr(currentDate)}"]`);
147
- scrollEl.scrollTop = monthEl.getBoundingClientRect().top -
148
- this.firstMonthElRef.current.getBoundingClientRect().top;
149
- this.needsScrollReset = false;
150
- }
151
- }
152
- // workaround for when queued setState render (w/ clientWidth) gets cancelled because
153
- // subsequent update and shouldComponentUpdate says not to render :(
154
- shouldComponentUpdate() {
155
- return true;
137
+ this.unwatchWidth();
138
+ this.context.emitter.off('_timeScrollRequest', this.timeScrollResponder.handleScroll);
156
139
  }
157
140
  }
158
141
  // date profile
@@ -218,7 +201,7 @@ FullCalendar.MultiMonth = (function (exports, core, internal$1, internal, preact
218
201
  multiMonthMinWidth: Number,
219
202
  };
220
203
 
221
- var css_248z = ".fc .fc-multimonth{border:1px solid var(--fc-border-color);display:flex;flex-wrap:wrap;overflow-x:hidden;overflow-y:auto}.fc .fc-multimonth-title{font-size:1.2em;font-weight:700;padding:1em 0;text-align:center}.fc .fc-multimonth-daygrid{background:var(--fc-page-bg-color)}.fc .fc-multimonth-daygrid-table,.fc .fc-multimonth-header-table{table-layout:fixed;width:100%}.fc .fc-multimonth-daygrid-table{border-top-style:hidden!important}.fc .fc-multimonth-singlecol .fc-multimonth{position:relative}.fc .fc-multimonth-singlecol .fc-multimonth-header{background:var(--fc-page-bg-color);position:relative;top:0;z-index:2}.fc .fc-multimonth-singlecol .fc-multimonth-daygrid{position:relative;z-index:1}.fc .fc-multimonth-singlecol .fc-multimonth-daygrid-table,.fc .fc-multimonth-singlecol .fc-multimonth-header-table{border-left-style:hidden;border-right-style:hidden}.fc .fc-multimonth-singlecol .fc-multimonth-month:last-child .fc-multimonth-daygrid-table{border-bottom-style:hidden}.fc .fc-multimonth-multicol{line-height:1}.fc .fc-multimonth-multicol .fc-multimonth-month{padding:0 1.2em 1.2em}.fc .fc-multimonth-multicol .fc-daygrid-more-link{border:1px solid var(--fc-event-border-color);display:block;float:none;padding:1px}.fc .fc-multimonth-compact{line-height:1}.fc .fc-multimonth-compact .fc-multimonth-daygrid-table,.fc .fc-multimonth-compact .fc-multimonth-header-table{font-size:.9em}.fc-media-screen .fc-multimonth-singlecol .fc-multimonth-header{position:sticky}.fc-media-print .fc-multimonth{overflow:visible}";
204
+ var css_248z = ".fc-multimonth-scroll{overflow-x:hidden;overflow-y:scroll}.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}";
222
205
  internal.injectStyles(css_248z);
223
206
 
224
207
  var plugin = core.createPlugin({
@@ -1,6 +1,6 @@
1
1
  /*!
2
- FullCalendar Multi-Month Plugin v6.1.15
2
+ FullCalendar Multi-Month Plugin v7.0.0-beta.0
3
3
  Docs & License: https://fullcalendar.io/docs/multimonth-grid
4
4
  (c) 2024 Adam Shaw
5
5
  */
6
- FullCalendar.MultiMonth=function(t,e,l,i,n){"use strict";class o extends i.DateComponent{constructor(){super(...arguments),this.buildDayTableModel=i.memoize(l.buildDayTableModel),this.slicer=new l.DayTableSlicer,this.state={labelId:i.getUniqueDomId()}}render(){const{props:t,state:e,context:o}=this,{dateProfile:a,forPrint:r}=t,{options:s}=o,c=this.buildDayTableModel(a,o.dateProfileGenerator),d=this.slicer.sliceProps(t,a,s.nextDayThreshold,o,c),m=null!=t.tableWidth?t.tableWidth/s.aspectRatio:null,h=c.cells.length,u=null!=m?m/h:null;return n.createElement("div",{ref:t.elRef,"data-date":t.isoDateStr,className:"fc-multimonth-month",style:{width:t.width},role:"grid","aria-labelledby":e.labelId},n.createElement("div",{className:"fc-multimonth-header",style:{marginBottom:u},role:"presentation"},n.createElement("div",{className:"fc-multimonth-title",id:e.labelId},o.dateEnv.format(t.dateProfile.currentRange.start,t.titleFormat)),n.createElement("table",{className:["fc-multimonth-header-table",o.theme.getClass("table")].join(" "),role:"presentation"},n.createElement("thead",{role:"rowgroup"},n.createElement(i.DayHeader,{dateProfile:t.dateProfile,dates:c.headerDates,datesRepDistinctDays:!1})))),n.createElement("div",{className:["fc-multimonth-daygrid","fc-daygrid","fc-daygrid-body",!r&&"fc-daygrid-body-balanced",r&&"fc-daygrid-body-unbalanced",r&&"fc-daygrid-body-natural"].join(" "),style:{marginTop:-u}},n.createElement("table",{className:["fc-multimonth-daygrid-table",o.theme.getClass("table")].join(" "),style:{height:r?"":m},role:"presentation"},n.createElement("tbody",{role:"rowgroup"},n.createElement(l.TableRows,Object.assign({},d,{dateProfile:a,cells:c.cells,eventSelection:t.eventSelection,dayMaxEvents:!r,dayMaxEventRows:!r,showWeekNumbers:s.weekNumbers,clientWidth:t.clientWidth,clientHeight:t.clientHeight,forPrint:r}))))))}}class a extends i.DateComponent{constructor(){super(...arguments),this.splitDateProfileByMonth=i.memoize(s),this.buildMonthFormat=i.memoize(m),this.scrollElRef=n.createRef(),this.firstMonthElRef=n.createRef(),this.needsScrollReset=!1,this.handleSizing=t=>{t&&this.updateSize()}}render(){const{context:t,props:e,state:l}=this,{options:a}=t,{clientWidth:r,clientHeight:s}=l,c=l.monthHPadding||0,d=Math.min(null!=r?Math.floor(r/(a.multiMonthMinWidth+c)):1,a.multiMonthMaxColumns)||1,m=100/d+"%",h=null==r?null:r/d-c,u=null!=r&&1===d,f=this.splitDateProfileByMonth(t.dateProfileGenerator,e.dateProfile,t.dateEnv,!u&&a.fixedWeekCount,a.showNonCurrentDates),g=this.buildMonthFormat(a.multiMonthTitleFormat,f),p=["fc-multimonth",u?"fc-multimonth-singlecol":"fc-multimonth-multicol",null!=h&&h<400?"fc-multimonth-compact":"",e.isHeightAuto?"":"fc-scroller"];return n.createElement(i.ViewContainer,{elRef:this.scrollElRef,elClasses:p,viewSpec:t.viewSpec},f.map((t,l)=>{const a=i.formatIsoMonthStr(t.currentRange.start);return n.createElement(o,Object.assign({},e,{key:a,isoDateStr:a,elRef:0===l?this.firstMonthElRef:void 0,titleFormat:g,dateProfile:t,width:m,tableWidth:h,clientWidth:r,clientHeight:s}))}))}componentDidMount(){this.updateSize(),this.context.addResizeHandler(this.handleSizing),this.requestScrollReset()}componentDidUpdate(t){i.isPropsEqual(t,this.props)||this.handleSizing(!1),t.dateProfile!==this.props.dateProfile?this.requestScrollReset():this.flushScrollReset()}componentWillUnmount(){this.context.removeResizeHandler(this.handleSizing)}updateSize(){const t=this.scrollElRef.current,e=this.firstMonthElRef.current;t&&this.setState({clientWidth:t.clientWidth,clientHeight:t.clientHeight}),e&&t&&null==this.state.monthHPadding&&this.setState({monthHPadding:t.clientWidth-e.firstChild.offsetWidth})}requestScrollReset(){this.needsScrollReset=!0,this.flushScrollReset()}flushScrollReset(){if(this.needsScrollReset&&null!=this.state.monthHPadding){const{currentDate:t}=this.props.dateProfile,e=this.scrollElRef.current,l=e.querySelector(`[data-date="${i.formatIsoMonthStr(t)}"]`);e.scrollTop=l.getBoundingClientRect().top-this.firstMonthElRef.current.getBoundingClientRect().top,this.needsScrollReset=!1}}shouldComponentUpdate(){return!0}}const r=i.createDuration(1,"month");function s(t,e,n,o,a){const{start:s,end:c}=e.currentRange;let d=s;const m=[];for(;d.valueOf()<c.valueOf();){const s=n.add(d,r),c={start:t.skipHiddenDays(d),end:t.skipHiddenDays(s,-1,!0)};let h=l.buildDayTableRenderRange({currentRange:c,snapToWeek:!0,fixedWeekCount:o,dateEnv:n});h={start:t.skipHiddenDays(h.start),end:t.skipHiddenDays(h.end,-1,!0)};const u=e.activeRange?i.intersectRanges(e.activeRange,a?h:c):null;m.push({currentDate:e.currentDate,isValid:e.isValid,validRange:e.validRange,renderRange:h,activeRange:u,currentRange:c,currentRangeUnit:"month",isRangeAllDay:!0,dateIncrement:e.dateIncrement,slotMinTime:e.slotMaxTime,slotMaxTime:e.slotMinTime}),d=s}return m}const c=i.createFormatter({year:"numeric",month:"long"}),d=i.createFormatter({month:"long"});function m(t,e){return t||(e[0].currentRange.start.getUTCFullYear()!==e[e.length-1].currentRange.start.getUTCFullYear()?c:d)}const h={multiMonthTitleFormat:i.createFormatter,multiMonthMaxColumns:Number,multiMonthMinWidth:Number};i.injectStyles(".fc .fc-multimonth{border:1px solid var(--fc-border-color);display:flex;flex-wrap:wrap;overflow-x:hidden;overflow-y:auto}.fc .fc-multimonth-title{font-size:1.2em;font-weight:700;padding:1em 0;text-align:center}.fc .fc-multimonth-daygrid{background:var(--fc-page-bg-color)}.fc .fc-multimonth-daygrid-table,.fc .fc-multimonth-header-table{table-layout:fixed;width:100%}.fc .fc-multimonth-daygrid-table{border-top-style:hidden!important}.fc .fc-multimonth-singlecol .fc-multimonth{position:relative}.fc .fc-multimonth-singlecol .fc-multimonth-header{background:var(--fc-page-bg-color);position:relative;top:0;z-index:2}.fc .fc-multimonth-singlecol .fc-multimonth-daygrid{position:relative;z-index:1}.fc .fc-multimonth-singlecol .fc-multimonth-daygrid-table,.fc .fc-multimonth-singlecol .fc-multimonth-header-table{border-left-style:hidden;border-right-style:hidden}.fc .fc-multimonth-singlecol .fc-multimonth-month:last-child .fc-multimonth-daygrid-table{border-bottom-style:hidden}.fc .fc-multimonth-multicol{line-height:1}.fc .fc-multimonth-multicol .fc-multimonth-month{padding:0 1.2em 1.2em}.fc .fc-multimonth-multicol .fc-daygrid-more-link{border:1px solid var(--fc-event-border-color);display:block;float:none;padding:1px}.fc .fc-multimonth-compact{line-height:1}.fc .fc-multimonth-compact .fc-multimonth-daygrid-table,.fc .fc-multimonth-compact .fc-multimonth-header-table{font-size:.9em}.fc-media-screen .fc-multimonth-singlecol .fc-multimonth-header{position:sticky}.fc-media-print .fc-multimonth{overflow:visible}");var u=e.createPlugin({name:"@fullcalendar/multimonth",initialView:"multiMonthYear",optionRefiners:h,views:{multiMonth:{component:a,dateProfileGeneratorClass:l.TableDateProfileGenerator,multiMonthMinWidth:350,multiMonthMaxColumns:3},multiMonthYear:{type:"multiMonth",duration:{years:1},fixedWeekCount:!0,showNonCurrentDates:!1}}});return e.globalPlugins.push(u),t.default=u,Object.defineProperty(t,"__esModule",{value:!0}),t}({},FullCalendar,FullCalendar.DayGrid.Internal,FullCalendar.Internal,FullCalendar.Preact);
6
+ FullCalendar.MultiMonth=function(e,t,n,o,l){"use strict";class i extends o.DateComponent{constructor(){super(...arguments),this.slicer=new n.DayTableSlicer,this.buildDayTableModel=o.memoize(n.buildDayTableModel),this.createDayHeaderFormatter=o.memoize(n.createDayHeaderFormatter)}render(){const{props:e,context:t}=this,{dateProfile:o,forPrint:i}=e,{options:r}=t,a=this.buildDayTableModel(o,t.dateProfileGenerator),s=this.slicer.sliceProps(e,o,r.nextDayThreshold,t,a),c="number"==typeof e.width?e.width/r.aspectRatio:null,m=a.cellRows.length,d=null!=c?c/m:null,h=this.createDayHeaderFormatter(t.options.dayHeaderFormat,!1,a.colCnt);return l.createElement("div",{"data-date":e.isoDateStr,role:"grid",className:"fc-multimonth-month fc-grow",style:{width:e.width}},l.createElement("div",{className:"fc-multimonth-header",style:{marginBottom:d},role:"presentation"},l.createElement("div",{className:"fc-multimonth-title"},t.dateEnv.format(e.dateProfile.currentRange.start,e.titleFormat)),l.createElement("div",{className:"fc-multimonth-header-row fc-flex-row"},a.headerDates.map(e=>l.createElement(n.DayOfWeekHeaderCell,{key:e.getUTCDay(),dow:e.getUTCDay(),dayHeaderFormat:h,colWidth:void 0})))),l.createElement("div",{className:"fc-multimonth-body fc-flex-column",style:{marginTop:-d,height:i?"":c}},l.createElement(n.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 r extends o.DateComponent{constructor(){super(...arguments),this.splitDateProfileByMonth=o.memoize(s),this.buildMonthFormat=o.memoize(d),this.rootElRef=l.createRef(),this.innerElRef=l.createRef(),this.handleClientWidth=e=>{let{xGap:t,xPadding:n}=this.state;if(null==t){const l=this.innerElRef.current.childNodes;if(l.length>1){const i=l[0].getBoundingClientRect(),r=l[1].getBoundingClientRect();let a;[t,a]=[Math.abs(i.left-r.right),Math.abs(i.right-r.left)].sort(o.compareNumbers),n=e-a}}this.setState({clientWidth:e,xGap:t,xPadding:n})},this.timeScrollResponder=new o.ScrollResponder(e=>{if(null!=this.state.clientWidth){const{currentDate:e}=this.props.dateProfile,t=this.rootElRef.current,n=this.innerElRef.current,l=n.querySelector(`[data-date="${o.formatIsoMonthStr(e)}"]`);return t.scrollTop=Math.ceil(l.getBoundingClientRect().top-n.getBoundingClientRect().top),!0}return!1})}render(){const{context:e,props:t,state:n}=this,{options:r}=e,a=null==n.clientWidth?2:Math.min(r.multiMonthMaxColumns,Math.floor((n.clientWidth-n.xPadding+n.xGap)/(r.multiMonthMinWidth+n.xGap))),s=null==n.clientWidth?"34%":Math.floor((n.clientWidth-n.xPadding-n.xGap*(a-1))/a),c=this.splitDateProfileByMonth(e.dateProfileGenerator,t.dateProfile,e.dateEnv,r.fixedWeekCount,r.showNonCurrentDates),m=this.buildMonthFormat(r.multiMonthTitleFormat,c),d=["fc-multimonth-view",1===a?"fc-multimonth-singlecol":"fc-multimonth-multicol",o.getIsHeightAuto(r)?"":"fc-multimonth-scroll","fc-border"];return l.createElement(o.NowTimer,{unit:"day"},(n,r)=>l.createElement(o.ViewContainer,{elRef:this.rootElRef,elClasses:d,viewSpec:e.viewSpec},l.createElement("div",{ref:this.innerElRef,className:"fc-multimonth-inner"},c.map((e,n)=>{const a=o.formatIsoMonthStr(e.currentRange.start);return l.createElement(i,Object.assign({},t,{key:a,todayRange:r,isoDateStr:a,titleFormat:m,dateProfile:e,width:s}))}))))}componentDidMount(){const{context:e}=this,{options:t}=e;this.unwatchWidth=o.watchWidth(this.rootElRef.current,this.handleClientWidth),e.emitter.on("_timeScrollRequest",this.timeScrollResponder.handleScroll),this.timeScrollResponder.handleScroll(t.scrollTime)}componentDidUpdate(e){const{options:t}=this.context;e.dateProfile!==this.props.dateProfile&&t.scrollTimeReset?this.timeScrollResponder.handleScroll(t.scrollTime):this.timeScrollResponder.drain()}componentWillUnmount(){this.unwatchWidth(),this.context.emitter.off("_timeScrollRequest",this.timeScrollResponder.handleScroll)}}const a=o.createDuration(1,"month");function s(e,t,l,i,r){const{start:s,end:c}=t.currentRange;let m=s;const d=[];for(;m.valueOf()<c.valueOf();){const s=l.add(m,a),c={start:e.skipHiddenDays(m),end:e.skipHiddenDays(s,-1,!0)};let h=n.buildDayTableRenderRange({currentRange:c,snapToWeek:!0,fixedWeekCount:i,dateEnv:l});h={start:e.skipHiddenDays(h.start),end:e.skipHiddenDays(h.end,-1,!0)};const u=t.activeRange?o.intersectRanges(t.activeRange,r?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=o.createFormatter({year:"numeric",month:"long"}),m=o.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:o.createFormatter,multiMonthMaxColumns:Number,multiMonthMinWidth:Number};o.injectStyles(".fc-multimonth-scroll{overflow-x:hidden;overflow-y:scroll}.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:r,dateProfileGeneratorClass:n.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,155 +1,138 @@
1
1
  import { createPlugin } from '@fullcalendar/core/index.js';
2
- import { buildDayTableModel, DayTableSlicer, TableRows, buildDayTableRenderRange, TableDateProfileGenerator } from '@fullcalendar/daygrid/internal.js';
3
- import { DateComponent, memoize, getUniqueDomId, DayHeader, ViewContainer, formatIsoMonthStr, isPropsEqual, createDuration, intersectRanges, createFormatter, injectStyles } from '@fullcalendar/core/internal.js';
2
+ import { DayTableSlicer, buildDayTableModel, createDayHeaderFormatter, DayOfWeekHeaderCell, DayGridRows, buildDayTableRenderRange, TableDateProfileGenerator } from '@fullcalendar/daygrid/internal.js';
3
+ import { DateComponent, memoize, compareNumbers, ScrollResponder, formatIsoMonthStr, getIsHeightAuto, NowTimer, ViewContainer, watchWidth, 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.buildDayTableModel = memoize(buildDayTableModel);
10
9
  this.slicer = new DayTableSlicer();
11
- this.state = {
12
- labelId: getUniqueDomId(),
13
- };
10
+ // memo
11
+ this.buildDayTableModel = memoize(buildDayTableModel);
12
+ this.createDayHeaderFormatter = memoize(createDayHeaderFormatter);
14
13
  }
15
14
  render() {
16
- const { props, state, context } = this;
15
+ const { props, context } = this;
17
16
  const { dateProfile, forPrint } = props;
18
17
  const { options } = context;
19
18
  const dayTableModel = this.buildDayTableModel(dateProfile, context.dateProfileGenerator);
20
19
  const slicedProps = this.slicer.sliceProps(props, dateProfile, options.nextDayThreshold, context, dayTableModel);
21
20
  // ensure single-month has aspect ratio
22
- const tableHeight = props.tableWidth != null ? props.tableWidth / options.aspectRatio : null;
23
- const rowCnt = dayTableModel.cells.length;
21
+ const tableHeight = typeof props.width === 'number'
22
+ ? props.width / options.aspectRatio
23
+ : null;
24
+ const rowCnt = dayTableModel.cellRows.length;
24
25
  const rowHeight = tableHeight != null ? tableHeight / rowCnt : null;
25
- return (createElement("div", { ref: props.elRef, "data-date": props.isoDateStr, className: "fc-multimonth-month", style: { width: props.width }, role: "grid", "aria-labelledby": state.labelId },
26
+ const dayHeaderFormat = this.createDayHeaderFormatter(context.options.dayHeaderFormat, false, // datesRepDistinctDays
27
+ 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 } },
26
30
  createElement("div", { className: "fc-multimonth-header", style: { marginBottom: rowHeight }, role: "presentation" },
27
- createElement("div", { className: "fc-multimonth-title", id: state.labelId }, context.dateEnv.format(props.dateProfile.currentRange.start, props.titleFormat)),
28
- createElement("table", { className: [
29
- 'fc-multimonth-header-table',
30
- context.theme.getClass('table'),
31
- ].join(' '), role: "presentation" },
32
- createElement("thead", { role: "rowgroup" },
33
- createElement(DayHeader, { dateProfile: props.dateProfile, dates: dayTableModel.headerDates, datesRepDistinctDays: false })))),
34
- createElement("div", { className: [
35
- 'fc-multimonth-daygrid',
36
- 'fc-daygrid',
37
- 'fc-daygrid-body',
38
- !forPrint && 'fc-daygrid-body-balanced',
39
- forPrint && 'fc-daygrid-body-unbalanced',
40
- forPrint && 'fc-daygrid-body-natural',
41
- ].join(' '), style: { marginTop: -rowHeight } },
42
- createElement("table", { className: [
43
- 'fc-multimonth-daygrid-table',
44
- context.theme.getClass('table'),
45
- ].join(' '), style: { height: forPrint ? '' : tableHeight }, role: "presentation" },
46
- createElement("tbody", { role: "rowgroup" },
47
- createElement(TableRows, Object.assign({}, slicedProps, { dateProfile: dateProfile, cells: dayTableModel.cells, eventSelection: props.eventSelection, dayMaxEvents: !forPrint, dayMaxEventRows: !forPrint, showWeekNumbers: options.weekNumbers, clientWidth: props.clientWidth, clientHeight: props.clientHeight, forPrint: forPrint })))))));
31
+ 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,
36
+ } },
37
+ createElement(DayGridRows // .fc-grow
38
+ , { dateProfile: props.dateProfile, todayRange: props.todayRange, cellRows: dayTableModel.cellRows, forPrint: props.forPrint,
39
+ // content
40
+ fgEventSegs: slicedProps.fgEventSegs, bgEventSegs: slicedProps.bgEventSegs, businessHourSegs: slicedProps.businessHourSegs, dateSelectionSegs: slicedProps.dateSelectionSegs, eventDrag: slicedProps.eventDrag, eventResize: slicedProps.eventResize, eventSelection: slicedProps.eventSelection }))));
48
41
  }
49
42
  }
50
43
 
51
44
  class MultiMonthView extends DateComponent {
52
45
  constructor() {
53
46
  super(...arguments);
47
+ // memo
54
48
  this.splitDateProfileByMonth = memoize(splitDateProfileByMonth);
55
49
  this.buildMonthFormat = memoize(buildMonthFormat);
56
- this.scrollElRef = createRef();
57
- this.firstMonthElRef = createRef();
58
- this.needsScrollReset = false;
59
- this.handleSizing = (isForced) => {
60
- if (isForced) {
61
- this.updateSize();
50
+ // ref
51
+ this.rootElRef = createRef(); // also the scroll container
52
+ this.innerElRef = createRef();
53
+ this.handleClientWidth = (clientWidth) => {
54
+ let { xGap, xPadding } = this.state;
55
+ // for first time, assume 2 columns and query gap/padding
56
+ if (xGap == null) {
57
+ const innerEl = this.innerElRef.current;
58
+ const children = innerEl.childNodes;
59
+ if (children.length > 1) {
60
+ const box0 = children[0].getBoundingClientRect();
61
+ const box1 = children[1].getBoundingClientRect();
62
+ let xSpan;
63
+ [xGap, xSpan] = [
64
+ Math.abs(box0.left - box1.right),
65
+ Math.abs(box0.right - box1.left),
66
+ ].sort(compareNumbers);
67
+ xPadding = clientWidth - xSpan;
68
+ }
62
69
  }
70
+ this.setState({ clientWidth, xGap, xPadding });
63
71
  };
72
+ this.timeScrollResponder = new ScrollResponder((_time) => {
73
+ // HACK to scroll to day
74
+ if (this.state.clientWidth != null) {
75
+ const { currentDate } = this.props.dateProfile;
76
+ const rootEl = this.rootElRef.current;
77
+ const innerEl = this.innerElRef.current;
78
+ const monthEl = innerEl.querySelector(`[data-date="${formatIsoMonthStr(currentDate)}"]`);
79
+ rootEl.scrollTop = Math.ceil(// for fractions, err on the side of scrolling further
80
+ monthEl.getBoundingClientRect().top -
81
+ innerEl.getBoundingClientRect().top);
82
+ return true;
83
+ }
84
+ return false;
85
+ });
64
86
  }
65
87
  render() {
66
88
  const { context, props, state } = this;
67
89
  const { options } = context;
68
- const { clientWidth, clientHeight } = state;
69
- const monthHPadding = state.monthHPadding || 0;
70
- const colCount = Math.min(clientWidth != null ?
71
- Math.floor(clientWidth / (options.multiMonthMinWidth + monthHPadding)) :
72
- 1, options.multiMonthMaxColumns) || 1;
73
- const monthWidthPct = (100 / colCount) + '%';
74
- const monthTableWidth = clientWidth == null ? null :
75
- (clientWidth / colCount) - monthHPadding;
76
- const isLegitSingleCol = clientWidth != null && colCount === 1;
77
- const monthDateProfiles = this.splitDateProfileByMonth(context.dateProfileGenerator, props.dateProfile, context.dateEnv, isLegitSingleCol ? false : options.fixedWeekCount, options.showNonCurrentDates);
90
+ const colCount = state.clientWidth == null
91
+ ? 2
92
+ : Math.min(options.multiMonthMaxColumns, Math.floor((state.clientWidth - state.xPadding + state.xGap) /
93
+ (options.multiMonthMinWidth + state.xGap)));
94
+ const monthWidth = state.clientWidth == null
95
+ ? '34%' // will expand. now small enough to be 1/3. for allowing gap
96
+ : Math.floor(// exact values can cause expansion to other rows
97
+ (state.clientWidth - state.xPadding - (state.xGap * (colCount - 1))) /
98
+ colCount);
99
+ const monthDateProfiles = this.splitDateProfileByMonth(context.dateProfileGenerator, props.dateProfile, context.dateEnv, options.fixedWeekCount, options.showNonCurrentDates);
78
100
  const monthTitleFormat = this.buildMonthFormat(options.multiMonthTitleFormat, monthDateProfiles);
79
101
  const rootClassNames = [
80
- 'fc-multimonth',
81
- isLegitSingleCol ?
102
+ 'fc-multimonth-view',
103
+ (colCount === 1) ?
82
104
  'fc-multimonth-singlecol' :
83
105
  'fc-multimonth-multicol',
84
- (monthTableWidth != null && monthTableWidth < 400) ?
85
- 'fc-multimonth-compact' :
86
- '',
87
- props.isHeightAuto ?
106
+ getIsHeightAuto(options) ?
88
107
  '' :
89
- 'fc-scroller', // for AutoScroller
108
+ 'fc-multimonth-scroll',
109
+ 'fc-border', // BAD to mix this with size-listening?
90
110
  ];
91
- return (createElement(ViewContainer, { elRef: this.scrollElRef, elClasses: rootClassNames, viewSpec: context.viewSpec }, monthDateProfiles.map((monthDateProfile, i) => {
92
- const monthStr = formatIsoMonthStr(monthDateProfile.currentRange.start);
93
- return (createElement(SingleMonth, Object.assign({}, props, { key: monthStr, isoDateStr: monthStr, elRef: i === 0 ? this.firstMonthElRef : undefined, titleFormat: monthTitleFormat, dateProfile: monthDateProfile, width: monthWidthPct, tableWidth: monthTableWidth, clientWidth: clientWidth, clientHeight: clientHeight })));
94
- })));
111
+ return (createElement(NowTimer, { unit: "day" }, (nowDate, todayRange) => (createElement(ViewContainer, { elRef: this.rootElRef, elClasses: rootClassNames, viewSpec: context.viewSpec },
112
+ createElement("div", { ref: this.innerElRef, className: 'fc-multimonth-inner' }, monthDateProfiles.map((monthDateProfile, i) => {
113
+ const monthStr = formatIsoMonthStr(monthDateProfile.currentRange.start);
114
+ return (createElement(SingleMonth, Object.assign({}, props, { key: monthStr, todayRange: todayRange, isoDateStr: monthStr, titleFormat: monthTitleFormat, dateProfile: monthDateProfile, width: monthWidth })));
115
+ }))))));
95
116
  }
96
117
  componentDidMount() {
97
- this.updateSize();
98
- this.context.addResizeHandler(this.handleSizing);
99
- this.requestScrollReset();
118
+ const { context } = this;
119
+ const { options } = context;
120
+ this.unwatchWidth = watchWidth(this.rootElRef.current, this.handleClientWidth);
121
+ context.emitter.on('_timeScrollRequest', this.timeScrollResponder.handleScroll);
122
+ this.timeScrollResponder.handleScroll(options.scrollTime);
100
123
  }
101
124
  componentDidUpdate(prevProps) {
102
- if (!isPropsEqual(prevProps, this.props)) { // an external change?
103
- this.handleSizing(false);
104
- }
105
- if (prevProps.dateProfile !== this.props.dateProfile) {
106
- this.requestScrollReset();
125
+ const { options } = this.context;
126
+ if (prevProps.dateProfile !== this.props.dateProfile && options.scrollTimeReset) {
127
+ this.timeScrollResponder.handleScroll(options.scrollTime);
107
128
  }
108
129
  else {
109
- this.flushScrollReset();
130
+ this.timeScrollResponder.drain();
110
131
  }
111
132
  }
112
133
  componentWillUnmount() {
113
- this.context.removeResizeHandler(this.handleSizing);
114
- }
115
- updateSize() {
116
- const scrollEl = this.scrollElRef.current;
117
- const firstMonthEl = this.firstMonthElRef.current;
118
- if (scrollEl) {
119
- this.setState({
120
- clientWidth: scrollEl.clientWidth,
121
- clientHeight: scrollEl.clientHeight,
122
- });
123
- }
124
- if (firstMonthEl && scrollEl) {
125
- if (this.state.monthHPadding == null) { // always remember initial non-zero value
126
- this.setState({
127
- monthHPadding: scrollEl.clientWidth - // go within padding
128
- firstMonthEl.firstChild.offsetWidth,
129
- });
130
- }
131
- }
132
- }
133
- requestScrollReset() {
134
- this.needsScrollReset = true;
135
- this.flushScrollReset();
136
- }
137
- flushScrollReset() {
138
- if (this.needsScrollReset &&
139
- this.state.monthHPadding != null // indicates sizing already happened
140
- ) {
141
- const { currentDate } = this.props.dateProfile;
142
- const scrollEl = this.scrollElRef.current;
143
- const monthEl = scrollEl.querySelector(`[data-date="${formatIsoMonthStr(currentDate)}"]`);
144
- scrollEl.scrollTop = monthEl.getBoundingClientRect().top -
145
- this.firstMonthElRef.current.getBoundingClientRect().top;
146
- this.needsScrollReset = false;
147
- }
148
- }
149
- // workaround for when queued setState render (w/ clientWidth) gets cancelled because
150
- // subsequent update and shouldComponentUpdate says not to render :(
151
- shouldComponentUpdate() {
152
- return true;
134
+ this.unwatchWidth();
135
+ this.context.emitter.off('_timeScrollRequest', this.timeScrollResponder.handleScroll);
153
136
  }
154
137
  }
155
138
  // date profile
@@ -215,7 +198,7 @@ const OPTION_REFINERS = {
215
198
  multiMonthMinWidth: Number,
216
199
  };
217
200
 
218
- var css_248z = ".fc .fc-multimonth{border:1px solid var(--fc-border-color);display:flex;flex-wrap:wrap;overflow-x:hidden;overflow-y:auto}.fc .fc-multimonth-title{font-size:1.2em;font-weight:700;padding:1em 0;text-align:center}.fc .fc-multimonth-daygrid{background:var(--fc-page-bg-color)}.fc .fc-multimonth-daygrid-table,.fc .fc-multimonth-header-table{table-layout:fixed;width:100%}.fc .fc-multimonth-daygrid-table{border-top-style:hidden!important}.fc .fc-multimonth-singlecol .fc-multimonth{position:relative}.fc .fc-multimonth-singlecol .fc-multimonth-header{background:var(--fc-page-bg-color);position:relative;top:0;z-index:2}.fc .fc-multimonth-singlecol .fc-multimonth-daygrid{position:relative;z-index:1}.fc .fc-multimonth-singlecol .fc-multimonth-daygrid-table,.fc .fc-multimonth-singlecol .fc-multimonth-header-table{border-left-style:hidden;border-right-style:hidden}.fc .fc-multimonth-singlecol .fc-multimonth-month:last-child .fc-multimonth-daygrid-table{border-bottom-style:hidden}.fc .fc-multimonth-multicol{line-height:1}.fc .fc-multimonth-multicol .fc-multimonth-month{padding:0 1.2em 1.2em}.fc .fc-multimonth-multicol .fc-daygrid-more-link{border:1px solid var(--fc-event-border-color);display:block;float:none;padding:1px}.fc .fc-multimonth-compact{line-height:1}.fc .fc-multimonth-compact .fc-multimonth-daygrid-table,.fc .fc-multimonth-compact .fc-multimonth-header-table{font-size:.9em}.fc-media-screen .fc-multimonth-singlecol .fc-multimonth-header{position:sticky}.fc-media-print .fc-multimonth{overflow:visible}";
201
+ var css_248z = ".fc-multimonth-scroll{overflow-x:hidden;overflow-y:scroll}.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}";
219
202
  injectStyles(css_248z);
220
203
 
221
204
  var index = createPlugin({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fullcalendar/multimonth",
3
- "version": "6.1.15",
3
+ "version": "7.0.0-beta.0",
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": "~6.1.15"
15
+ "@fullcalendar/daygrid": "7.0.0-beta.0"
16
16
  },
17
17
  "peerDependencies": {
18
- "@fullcalendar/core": "~6.1.15"
18
+ "@fullcalendar/core": "7.0.0-beta.0"
19
19
  },
20
20
  "type": "module",
21
21
  "bugs": "https://fullcalendar.io/reporting-bugs",