@fullcalendar/multimonth 6.1.14 → 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,147 +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
- '',
110
+ internal_cjs.getIsHeightAuto(options) ?
111
+ '' :
112
+ 'fc-multimonth-scroll',
113
+ 'fc-border', // BAD to mix this with size-listening?
91
114
  ];
92
- return (preact_cjs.createElement(internal_cjs.ViewContainer, { elRef: this.scrollElRef, elClasses: rootClassNames, viewSpec: context.viewSpec }, monthDateProfiles.map((monthDateProfile, i) => {
93
- const monthStr = internal_cjs.formatIsoMonthStr(monthDateProfile.currentRange.start);
94
- 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 })));
95
- })));
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
+ }))))));
96
120
  }
97
121
  componentDidMount() {
98
- this.updateSize();
99
- this.context.addResizeHandler(this.handleSizing);
100
- 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);
101
127
  }
102
128
  componentDidUpdate(prevProps) {
103
- if (!internal_cjs.isPropsEqual(prevProps, this.props)) { // an external change?
104
- this.handleSizing(false);
105
- }
106
- if (prevProps.dateProfile !== this.props.dateProfile) {
107
- this.requestScrollReset();
129
+ const { options } = this.context;
130
+ if (prevProps.dateProfile !== this.props.dateProfile && options.scrollTimeReset) {
131
+ this.timeScrollResponder.handleScroll(options.scrollTime);
108
132
  }
109
133
  else {
110
- this.flushScrollReset();
134
+ this.timeScrollResponder.drain();
111
135
  }
112
136
  }
113
137
  componentWillUnmount() {
114
- this.context.removeResizeHandler(this.handleSizing);
115
- }
116
- updateSize() {
117
- const scrollEl = this.scrollElRef.current;
118
- const firstMonthEl = this.firstMonthElRef.current;
119
- if (scrollEl) {
120
- this.setState({
121
- clientWidth: scrollEl.clientWidth,
122
- clientHeight: scrollEl.clientHeight,
123
- });
124
- }
125
- if (firstMonthEl && scrollEl) {
126
- if (this.state.monthHPadding == null) { // always remember initial non-zero value
127
- this.setState({
128
- monthHPadding: scrollEl.clientWidth - // go within padding
129
- firstMonthEl.firstChild.offsetWidth,
130
- });
131
- }
132
- }
133
- }
134
- requestScrollReset() {
135
- this.needsScrollReset = true;
136
- this.flushScrollReset();
137
- }
138
- flushScrollReset() {
139
- if (this.needsScrollReset &&
140
- this.state.monthHPadding != null // indicates sizing already happened
141
- ) {
142
- const { currentDate } = this.props.dateProfile;
143
- const scrollEl = this.scrollElRef.current;
144
- const monthEl = scrollEl.querySelector(`[data-date="${internal_cjs.formatIsoMonthStr(currentDate)}"]`);
145
- scrollEl.scrollTop = monthEl.getBoundingClientRect().top -
146
- this.firstMonthElRef.current.getBoundingClientRect().top;
147
- this.needsScrollReset = false;
148
- }
149
- }
150
- // workaround for when queued setState render (w/ clientWidth) gets cancelled because
151
- // subsequent update and shouldComponentUpdate says not to render :(
152
- shouldComponentUpdate() {
153
- return true;
138
+ this.unwatchWidth();
139
+ this.context.emitter.off('_timeScrollRequest', this.timeScrollResponder.handleScroll);
154
140
  }
155
141
  }
156
142
  // date profile
@@ -216,7 +202,7 @@ const OPTION_REFINERS = {
216
202
  multiMonthMinWidth: Number,
217
203
  };
218
204
 
219
- 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}";
220
206
  internal_cjs.injectStyles(css_248z);
221
207
 
222
208
  var index = index_cjs.createPlugin({
package/index.global.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- FullCalendar Multi-Month Plugin v6.1.14
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,147 +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
- '',
109
+ internal.getIsHeightAuto(options) ?
110
+ '' :
111
+ 'fc-multimonth-scroll',
112
+ 'fc-border', // BAD to mix this with size-listening?
90
113
  ];
91
- return (preact.createElement(internal.ViewContainer, { elRef: this.scrollElRef, elClasses: rootClassNames, viewSpec: context.viewSpec }, monthDateProfiles.map((monthDateProfile, i) => {
92
- const monthStr = internal.formatIsoMonthStr(monthDateProfile.currentRange.start);
93
- 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 })));
94
- })));
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
+ }))))));
95
119
  }
96
120
  componentDidMount() {
97
- this.updateSize();
98
- this.context.addResizeHandler(this.handleSizing);
99
- 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);
100
126
  }
101
127
  componentDidUpdate(prevProps) {
102
- if (!internal.isPropsEqual(prevProps, this.props)) { // an external change?
103
- this.handleSizing(false);
104
- }
105
- if (prevProps.dateProfile !== this.props.dateProfile) {
106
- this.requestScrollReset();
128
+ const { options } = this.context;
129
+ if (prevProps.dateProfile !== this.props.dateProfile && options.scrollTimeReset) {
130
+ this.timeScrollResponder.handleScroll(options.scrollTime);
107
131
  }
108
132
  else {
109
- this.flushScrollReset();
133
+ this.timeScrollResponder.drain();
110
134
  }
111
135
  }
112
136
  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="${internal.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;
137
+ this.unwatchWidth();
138
+ this.context.emitter.off('_timeScrollRequest', this.timeScrollResponder.handleScroll);
153
139
  }
154
140
  }
155
141
  // date profile
@@ -215,7 +201,7 @@ FullCalendar.MultiMonth = (function (exports, core, internal$1, internal, preact
215
201
  multiMonthMinWidth: Number,
216
202
  };
217
203
 
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}";
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}";
219
205
  internal.injectStyles(css_248z);
220
206
 
221
207
  var plugin = core.createPlugin({
@@ -1,6 +1,6 @@
1
1
  /*!
2
- FullCalendar Multi-Month Plugin v6.1.14
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":""];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,152 +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
- '',
106
+ getIsHeightAuto(options) ?
107
+ '' :
108
+ 'fc-multimonth-scroll',
109
+ 'fc-border', // BAD to mix this with size-listening?
87
110
  ];
88
- return (createElement(ViewContainer, { elRef: this.scrollElRef, elClasses: rootClassNames, viewSpec: context.viewSpec }, monthDateProfiles.map((monthDateProfile, i) => {
89
- const monthStr = formatIsoMonthStr(monthDateProfile.currentRange.start);
90
- 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 })));
91
- })));
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
+ }))))));
92
116
  }
93
117
  componentDidMount() {
94
- this.updateSize();
95
- this.context.addResizeHandler(this.handleSizing);
96
- 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);
97
123
  }
98
124
  componentDidUpdate(prevProps) {
99
- if (!isPropsEqual(prevProps, this.props)) { // an external change?
100
- this.handleSizing(false);
101
- }
102
- if (prevProps.dateProfile !== this.props.dateProfile) {
103
- this.requestScrollReset();
125
+ const { options } = this.context;
126
+ if (prevProps.dateProfile !== this.props.dateProfile && options.scrollTimeReset) {
127
+ this.timeScrollResponder.handleScroll(options.scrollTime);
104
128
  }
105
129
  else {
106
- this.flushScrollReset();
130
+ this.timeScrollResponder.drain();
107
131
  }
108
132
  }
109
133
  componentWillUnmount() {
110
- this.context.removeResizeHandler(this.handleSizing);
111
- }
112
- updateSize() {
113
- const scrollEl = this.scrollElRef.current;
114
- const firstMonthEl = this.firstMonthElRef.current;
115
- if (scrollEl) {
116
- this.setState({
117
- clientWidth: scrollEl.clientWidth,
118
- clientHeight: scrollEl.clientHeight,
119
- });
120
- }
121
- if (firstMonthEl && scrollEl) {
122
- if (this.state.monthHPadding == null) { // always remember initial non-zero value
123
- this.setState({
124
- monthHPadding: scrollEl.clientWidth - // go within padding
125
- firstMonthEl.firstChild.offsetWidth,
126
- });
127
- }
128
- }
129
- }
130
- requestScrollReset() {
131
- this.needsScrollReset = true;
132
- this.flushScrollReset();
133
- }
134
- flushScrollReset() {
135
- if (this.needsScrollReset &&
136
- this.state.monthHPadding != null // indicates sizing already happened
137
- ) {
138
- const { currentDate } = this.props.dateProfile;
139
- const scrollEl = this.scrollElRef.current;
140
- const monthEl = scrollEl.querySelector(`[data-date="${formatIsoMonthStr(currentDate)}"]`);
141
- scrollEl.scrollTop = monthEl.getBoundingClientRect().top -
142
- this.firstMonthElRef.current.getBoundingClientRect().top;
143
- this.needsScrollReset = false;
144
- }
145
- }
146
- // workaround for when queued setState render (w/ clientWidth) gets cancelled because
147
- // subsequent update and shouldComponentUpdate says not to render :(
148
- shouldComponentUpdate() {
149
- return true;
134
+ this.unwatchWidth();
135
+ this.context.emitter.off('_timeScrollRequest', this.timeScrollResponder.handleScroll);
150
136
  }
151
137
  }
152
138
  // date profile
@@ -212,7 +198,7 @@ const OPTION_REFINERS = {
212
198
  multiMonthMinWidth: Number,
213
199
  };
214
200
 
215
- 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}";
216
202
  injectStyles(css_248z);
217
203
 
218
204
  var index = createPlugin({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fullcalendar/multimonth",
3
- "version": "6.1.14",
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.14"
15
+ "@fullcalendar/daygrid": "7.0.0-beta.0"
16
16
  },
17
17
  "peerDependencies": {
18
- "@fullcalendar/core": "~6.1.14"
18
+ "@fullcalendar/core": "7.0.0-beta.0"
19
19
  },
20
20
  "type": "module",
21
21
  "bugs": "https://fullcalendar.io/reporting-bugs",