@forcecalendar/interface 1.0.27 → 1.0.29

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.
@@ -7,92 +7,94 @@
7
7
  import { BaseViewRenderer } from './BaseViewRenderer.js';
8
8
 
9
9
  export class DayViewRenderer extends BaseViewRenderer {
10
- constructor(container, stateManager) {
11
- super(container, stateManager);
12
- this.hourHeight = 60; // pixels per hour
13
- this.totalHeight = 24 * this.hourHeight; // 1440px for 24 hours
10
+ constructor(container, stateManager) {
11
+ super(container, stateManager);
12
+ this.hourHeight = 60; // pixels per hour
13
+ this.totalHeight = 24 * this.hourHeight; // 1440px for 24 hours
14
+ }
15
+
16
+ render() {
17
+ if (!this.container || !this.stateManager) return;
18
+
19
+ const viewData = this.stateManager.getViewData();
20
+ if (!viewData) {
21
+ this.container.innerHTML =
22
+ '<div style="padding: 20px; text-align: center; color: #666;">No data available for day view.</div>';
23
+ return;
14
24
  }
15
25
 
16
- render() {
17
- if (!this.container || !this.stateManager) return;
18
-
19
- const viewData = this.stateManager.getViewData();
20
- if (!viewData) {
21
- this.container.innerHTML = '<div style="padding: 20px; text-align: center; color: #666;">No data available for day view.</div>';
22
- return;
23
- }
24
-
25
- this.cleanup();
26
- const config = this.stateManager.getState().config;
27
- const html = this._renderDayView(viewData, config);
28
- this.container.innerHTML = html;
29
- this._attachEventHandlers();
30
- this._scrollToCurrentTime();
31
- }
26
+ this.cleanup();
27
+ const config = this.stateManager.getState().config;
28
+ const html = this._renderDayView(viewData, config);
29
+ this.container.innerHTML = html;
30
+ this._attachEventHandlers();
31
+ this._scrollToCurrentTime();
32
+ }
32
33
 
33
- _renderDayView(viewData, config) {
34
- const currentDate = this.stateManager?.getState()?.currentDate || new Date();
35
- const dayData = this._extractDayData(viewData, currentDate);
34
+ _renderDayView(viewData, _config) {
35
+ const currentDate = this.stateManager?.getState()?.currentDate || new Date();
36
+ const dayData = this._extractDayData(viewData, currentDate);
36
37
 
37
- if (!dayData) {
38
- return '<div style="padding: 20px; text-align: center; color: #666;">No data available for day view.</div>';
39
- }
38
+ if (!dayData) {
39
+ return '<div style="padding: 20px; text-align: center; color: #666;">No data available for day view.</div>';
40
+ }
40
41
 
41
- const { dayDate, dayName, isToday, allDayEvents, timedEvents } = dayData;
42
- const hours = Array.from({ length: 24 }, (_, i) => i);
42
+ const { dayDate, dayName, isToday, allDayEvents, timedEvents } = dayData;
43
+ const hours = Array.from({ length: 24 }, (_, i) => i);
43
44
 
44
- return `
45
+ return `
45
46
  <div class="fc-day-view" style="display: flex; flex-direction: column; height: 100%; background: #fff; overflow: hidden;">
46
47
  ${this._renderHeader(dayDate, dayName, isToday)}
47
48
  ${this._renderAllDayRow(allDayEvents, dayDate)}
48
49
  ${this._renderTimeGrid(timedEvents, isToday, dayDate, hours)}
49
50
  </div>
50
51
  `;
51
- }
52
-
53
- _extractDayData(viewData, currentDate) {
54
- let dayDate, dayName, isToday, allDayEvents, timedEvents;
55
- const dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
56
-
57
- if (viewData.type === 'day' && viewData.date) {
58
- // Core day view structure
59
- dayDate = new Date(viewData.date);
60
- dayName = viewData.dayName || dayNames[dayDate.getDay()];
61
- isToday = viewData.isToday !== undefined ? viewData.isToday : this.isToday(dayDate);
62
- allDayEvents = viewData.allDayEvents || [];
63
-
64
- // Extract timed events from hours array
65
- if (viewData.hours && Array.isArray(viewData.hours)) {
66
- const eventMap = new Map();
67
- viewData.hours.forEach(hour => {
68
- (hour.events || []).forEach(evt => {
69
- if (!eventMap.has(evt.id)) {
70
- eventMap.set(evt.id, evt);
71
- }
72
- });
73
- });
74
- timedEvents = Array.from(eventMap.values());
75
- } else {
76
- timedEvents = [];
52
+ }
53
+
54
+ _extractDayData(viewData, currentDate) {
55
+ let dayDate, dayName, isToday, allDayEvents, timedEvents;
56
+ const dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
57
+
58
+ if (viewData.type === 'day' && viewData.date) {
59
+ // Core day view structure
60
+ dayDate = new Date(viewData.date);
61
+ dayName = viewData.dayName || dayNames[dayDate.getDay()];
62
+ isToday = viewData.isToday !== undefined ? viewData.isToday : this.isToday(dayDate);
63
+ allDayEvents = viewData.allDayEvents || [];
64
+
65
+ // Extract timed events from hours array
66
+ if (viewData.hours && Array.isArray(viewData.hours)) {
67
+ const eventMap = new Map();
68
+ viewData.hours.forEach(hour => {
69
+ (hour.events || []).forEach(evt => {
70
+ if (!eventMap.has(evt.id)) {
71
+ eventMap.set(evt.id, evt);
77
72
  }
78
- } else if (viewData.days && viewData.days.length > 0) {
79
- // Enriched structure with days array
80
- const dayDataItem = viewData.days.find(d => this.isSameDay(new Date(d.date), currentDate)) || viewData.days[0];
81
- dayDate = new Date(dayDataItem.date);
82
- dayName = dayNames[dayDate.getDay()];
83
- isToday = this.isToday(dayDate);
84
- const events = dayDataItem.events || [];
85
- allDayEvents = events.filter(e => e.allDay);
86
- timedEvents = events.filter(e => !e.allDay);
87
- } else {
88
- return null;
89
- }
90
-
91
- return { dayDate, dayName, isToday, allDayEvents, timedEvents };
73
+ });
74
+ });
75
+ timedEvents = Array.from(eventMap.values());
76
+ } else {
77
+ timedEvents = [];
78
+ }
79
+ } else if (viewData.days && viewData.days.length > 0) {
80
+ // Enriched structure with days array
81
+ const dayDataItem =
82
+ viewData.days.find(d => this.isSameDay(new Date(d.date), currentDate)) || viewData.days[0];
83
+ dayDate = new Date(dayDataItem.date);
84
+ dayName = dayNames[dayDate.getDay()];
85
+ isToday = this.isToday(dayDate);
86
+ const events = dayDataItem.events || [];
87
+ allDayEvents = events.filter(e => e.allDay);
88
+ timedEvents = events.filter(e => !e.allDay);
89
+ } else {
90
+ return null;
92
91
  }
93
92
 
94
- _renderHeader(dayDate, dayName, isToday) {
95
- return `
93
+ return { dayDate, dayName, isToday, allDayEvents, timedEvents };
94
+ }
95
+
96
+ _renderHeader(dayDate, dayName, isToday) {
97
+ return `
96
98
  <div class="fc-day-header" style="display: grid; grid-template-columns: 60px 1fr; border-bottom: 1px solid #e5e7eb; background: #f9fafb; flex-shrink: 0;">
97
99
  <div style="border-right: 1px solid #e5e7eb;"></div>
98
100
  <div style="padding: 16px 24px;">
@@ -105,28 +107,32 @@ export class DayViewRenderer extends BaseViewRenderer {
105
107
  </div>
106
108
  </div>
107
109
  `;
108
- }
110
+ }
109
111
 
110
- _renderAllDayRow(allDayEvents, dayDate) {
111
- return `
112
+ _renderAllDayRow(allDayEvents, dayDate) {
113
+ return `
112
114
  <div class="fc-all-day-row" style="display: grid; grid-template-columns: 60px 1fr; border-bottom: 1px solid #e5e7eb; background: #fafafa; min-height: 36px; flex-shrink: 0;">
113
115
  <div style="font-size: 9px; color: #6b7280; display: flex; align-items: center; justify-content: center; border-right: 1px solid #e5e7eb; text-transform: uppercase; font-weight: 700;">
114
116
  All day
115
117
  </div>
116
118
  <div class="fc-all-day-cell" data-date="${dayDate.toISOString()}" style="padding: 6px 12px; display: flex; flex-wrap: wrap; gap: 4px;">
117
- ${allDayEvents.map(evt => `
119
+ ${allDayEvents
120
+ .map(
121
+ evt => `
118
122
  <div class="fc-event fc-all-day-event" data-event-id="${this.escapeHTML(evt.id)}"
119
123
  style="background-color: ${this.getEventColor(evt)}; font-size: 12px; padding: 4px 8px; border-radius: 4px; color: white; cursor: pointer; font-weight: 500;">
120
124
  ${this.escapeHTML(evt.title)}
121
125
  </div>
122
- `).join('')}
126
+ `
127
+ )
128
+ .join('')}
123
129
  </div>
124
130
  </div>
125
131
  `;
126
- }
132
+ }
127
133
 
128
- _renderTimeGrid(timedEvents, isToday, dayDate, hours) {
129
- return `
134
+ _renderTimeGrid(timedEvents, isToday, dayDate, hours) {
135
+ return `
130
136
  <div id="day-scroll-container" class="fc-time-grid-container" style="flex: 1; overflow-y: auto; overflow-x: hidden; position: relative;">
131
137
  <div class="fc-time-grid" style="display: grid; grid-template-columns: 60px 1fr; position: relative; height: ${this.totalHeight}px;">
132
138
  ${this._renderTimeGutter(hours)}
@@ -134,22 +140,26 @@ export class DayViewRenderer extends BaseViewRenderer {
134
140
  </div>
135
141
  </div>
136
142
  `;
137
- }
143
+ }
138
144
 
139
- _renderTimeGutter(hours) {
140
- return `
145
+ _renderTimeGutter(hours) {
146
+ return `
141
147
  <div class="fc-time-gutter" style="border-right: 1px solid #e5e7eb; background: #fafafa;">
142
- ${hours.map(h => `
148
+ ${hours
149
+ .map(
150
+ h => `
143
151
  <div style="height: ${this.hourHeight}px; font-size: 11px; color: #6b7280; text-align: right; padding-right: 12px; font-weight: 500;">
144
152
  ${h === 0 ? '' : this.formatHour(h)}
145
153
  </div>
146
- `).join('')}
154
+ `
155
+ )
156
+ .join('')}
147
157
  </div>
148
158
  `;
149
- }
159
+ }
150
160
 
151
- _renderDayColumn(timedEvents, isToday, dayDate, hours) {
152
- return `
161
+ _renderDayColumn(timedEvents, isToday, dayDate, hours) {
162
+ return `
153
163
  <div class="fc-day-column" data-date="${dayDate.toISOString()}" style="position: relative; cursor: pointer;">
154
164
  <!-- Hour grid lines -->
155
165
  ${hours.map(() => `<div style="height: ${this.hourHeight}px; border-bottom: 1px solid #f3f4f6;"></div>`).join('')}
@@ -161,38 +171,43 @@ export class DayViewRenderer extends BaseViewRenderer {
161
171
  ${timedEvents.map(evt => this.renderTimedEvent(evt, { compact: false })).join('')}
162
172
  </div>
163
173
  `;
174
+ }
175
+
176
+ _attachEventHandlers() {
177
+ this.addListener(this.container, 'click', e => {
178
+ const dayEl = e.target.closest('.fc-day-column');
179
+ if (!dayEl || !this.container.contains(dayEl)) return;
180
+ if (e.target.closest('.fc-event')) return;
181
+
182
+ const date = new Date(dayEl.dataset.date);
183
+ const rect = dayEl.getBoundingClientRect();
184
+ const scrollContainer = this.container.querySelector('#day-scroll-container');
185
+ const y = e.clientY - rect.top + (scrollContainer ? scrollContainer.scrollTop : 0);
186
+
187
+ // Calculate time from click position
188
+ date.setHours(
189
+ Math.floor(y / this.hourHeight),
190
+ Math.floor((y % this.hourHeight) / (this.hourHeight / 60)),
191
+ 0,
192
+ 0
193
+ );
194
+ this.stateManager.selectDate(date);
195
+ });
196
+
197
+ // Common event handlers (event clicks)
198
+ this.attachCommonEventHandlers();
199
+ }
200
+
201
+ _scrollToCurrentTime() {
202
+ if (this._scrolled) return;
203
+
204
+ const scrollContainer = this.container.querySelector('#day-scroll-container');
205
+ if (scrollContainer) {
206
+ // Scroll to 8 AM, minus some offset for visibility
207
+ scrollContainer.scrollTop = 8 * this.hourHeight - 50;
208
+ this._scrolled = true;
164
209
  }
165
-
166
- _attachEventHandlers() {
167
- this.addListener(this.container, 'click', (e) => {
168
- const dayEl = e.target.closest('.fc-day-column');
169
- if (!dayEl || !this.container.contains(dayEl)) return;
170
- if (e.target.closest('.fc-event')) return;
171
-
172
- const date = new Date(dayEl.dataset.date);
173
- const rect = dayEl.getBoundingClientRect();
174
- const scrollContainer = this.container.querySelector('#day-scroll-container');
175
- const y = e.clientY - rect.top + (scrollContainer ? scrollContainer.scrollTop : 0);
176
-
177
- // Calculate time from click position
178
- date.setHours(Math.floor(y / this.hourHeight), Math.floor((y % this.hourHeight) / (this.hourHeight / 60)), 0, 0);
179
- this.stateManager.selectDate(date);
180
- });
181
-
182
- // Common event handlers (event clicks)
183
- this.attachCommonEventHandlers();
184
- }
185
-
186
- _scrollToCurrentTime() {
187
- if (this._scrolled) return;
188
-
189
- const scrollContainer = this.container.querySelector('#day-scroll-container');
190
- if (scrollContainer) {
191
- // Scroll to 8 AM, minus some offset for visibility
192
- scrollContainer.scrollTop = 8 * this.hourHeight - 50;
193
- this._scrolled = true;
194
- }
195
- }
210
+ }
196
211
  }
197
212
 
198
213
  export default DayViewRenderer;
@@ -7,32 +7,33 @@
7
7
  import { BaseViewRenderer } from './BaseViewRenderer.js';
8
8
 
9
9
  export class MonthViewRenderer extends BaseViewRenderer {
10
- constructor(container, stateManager) {
11
- super(container, stateManager);
12
- this.maxEventsToShow = 3;
10
+ constructor(container, stateManager) {
11
+ super(container, stateManager);
12
+ this.maxEventsToShow = 3;
13
+ }
14
+
15
+ render() {
16
+ if (!this.container || !this.stateManager) return;
17
+
18
+ const viewData = this.stateManager.getViewData();
19
+ if (!viewData || !viewData.weeks) {
20
+ this.container.innerHTML =
21
+ '<div style="padding: 20px; text-align: center; color: #666;">No data available for month view.</div>';
22
+ return;
13
23
  }
14
24
 
15
- render() {
16
- if (!this.container || !this.stateManager) return;
25
+ this.cleanup();
26
+ const config = this.stateManager.getState().config;
27
+ const html = this._renderMonthView(viewData, config);
28
+ this.container.innerHTML = html;
29
+ this._attachEventHandlers();
30
+ }
17
31
 
18
- const viewData = this.stateManager.getViewData();
19
- if (!viewData || !viewData.weeks) {
20
- this.container.innerHTML = '<div style="padding: 20px; text-align: center; color: #666;">No data available for month view.</div>';
21
- return;
22
- }
32
+ _renderMonthView(viewData, config) {
33
+ const weekStartsOn = config.weekStartsOn || 0;
34
+ const dayNames = this._getDayNames(weekStartsOn);
23
35
 
24
- this.cleanup();
25
- const config = this.stateManager.getState().config;
26
- const html = this._renderMonthView(viewData, config);
27
- this.container.innerHTML = html;
28
- this._attachEventHandlers();
29
- }
30
-
31
- _renderMonthView(viewData, config) {
32
- const weekStartsOn = config.weekStartsOn || 0;
33
- const dayNames = this._getDayNames(weekStartsOn);
34
-
35
- let html = `
36
+ let html = `
36
37
  <div class="fc-month-view" style="display: flex; flex-direction: column; height: 100%; min-height: 400px; background: #fff; border: 1px solid #e5e7eb;">
37
38
  <div class="fc-month-header" style="display: grid; grid-template-columns: repeat(7, 1fr); border-bottom: 1px solid #e5e7eb; background: #f9fafb;">
38
39
  ${dayNames.map(d => `<div class="fc-month-header-cell" style="padding: 12px 8px; text-align: center; font-size: 11px; font-weight: 600; color: #6b7280; text-transform: uppercase;">${d}</div>`).join('')}
@@ -40,50 +41,51 @@ export class MonthViewRenderer extends BaseViewRenderer {
40
41
  <div class="fc-month-body" style="display: flex; flex-direction: column; flex: 1;">
41
42
  `;
42
43
 
43
- viewData.weeks.forEach(week => {
44
- html += this._renderWeek(week);
45
- });
44
+ viewData.weeks.forEach(week => {
45
+ html += this._renderWeek(week);
46
+ });
46
47
 
47
- html += '</div></div>';
48
- return html;
49
- }
48
+ html += '</div></div>';
49
+ return html;
50
+ }
50
51
 
51
- _getDayNames(weekStartsOn) {
52
- const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
53
- const dayNames = [];
54
- for (let i = 0; i < 7; i++) {
55
- const dayIndex = (weekStartsOn + i) % 7;
56
- dayNames.push(days[dayIndex]);
57
- }
58
- return dayNames;
52
+ _getDayNames(weekStartsOn) {
53
+ const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
54
+ const dayNames = [];
55
+ for (let i = 0; i < 7; i++) {
56
+ const dayIndex = (weekStartsOn + i) % 7;
57
+ dayNames.push(days[dayIndex]);
59
58
  }
59
+ return dayNames;
60
+ }
60
61
 
61
- _renderWeek(week) {
62
- let html = '<div class="fc-month-week" style="display: grid; grid-template-columns: repeat(7, 1fr); flex: 1; min-height: 80px;">';
62
+ _renderWeek(week) {
63
+ let html =
64
+ '<div class="fc-month-week" style="display: grid; grid-template-columns: repeat(7, 1fr); flex: 1; min-height: 80px;">';
63
65
 
64
- week.days.forEach(day => {
65
- html += this._renderDay(day);
66
- });
66
+ week.days.forEach(day => {
67
+ html += this._renderDay(day);
68
+ });
67
69
 
68
- html += '</div>';
69
- return html;
70
- }
70
+ html += '</div>';
71
+ return html;
72
+ }
71
73
 
72
- _renderDay(day) {
73
- const isOtherMonth = !day.isCurrentMonth;
74
- const isToday = day.isToday;
74
+ _renderDay(day) {
75
+ const isOtherMonth = !day.isCurrentMonth;
76
+ const isToday = day.isToday;
75
77
 
76
- const dayBg = isOtherMonth ? '#f3f4f6' : '#fff';
77
- const dayNumColor = isOtherMonth ? '#9ca3af' : '#111827';
78
- const todayStyle = isToday
79
- ? 'background: #2563eb; color: white; border-radius: 50%; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center;'
80
- : '';
78
+ const dayBg = isOtherMonth ? '#f3f4f6' : '#fff';
79
+ const dayNumColor = isOtherMonth ? '#9ca3af' : '#111827';
80
+ const todayStyle = isToday
81
+ ? 'background: #2563eb; color: white; border-radius: 50%; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center;'
82
+ : '';
81
83
 
82
- const events = day.events || [];
83
- const visibleEvents = events.slice(0, this.maxEventsToShow);
84
- const moreCount = events.length - this.maxEventsToShow;
84
+ const events = day.events || [];
85
+ const visibleEvents = events.slice(0, this.maxEventsToShow);
86
+ const moreCount = events.length - this.maxEventsToShow;
85
87
 
86
- return `
88
+ return `
87
89
  <div class="fc-month-day" data-date="${day.date}"
88
90
  style="background: ${dayBg}; border-right: 1px solid #e5e7eb; border-bottom: 1px solid #e5e7eb; padding: 4px; min-height: 80px; cursor: pointer; display: flex; flex-direction: column;">
89
91
  <div class="fc-day-number" style="font-size: 13px; font-weight: 500; color: ${dayNumColor}; padding: 2px 4px; margin-bottom: 4px; ${todayStyle}">
@@ -95,31 +97,31 @@ export class MonthViewRenderer extends BaseViewRenderer {
95
97
  </div>
96
98
  </div>
97
99
  `;
98
- }
100
+ }
99
101
 
100
- _renderEvent(event) {
101
- const color = this.getEventColor(event);
102
- return `
102
+ _renderEvent(event) {
103
+ const color = this.getEventColor(event);
104
+ return `
103
105
  <div class="fc-event" data-event-id="${this.escapeHTML(event.id)}"
104
106
  style="background-color: ${color}; font-size: 11px; padding: 2px 6px; border-radius: 3px; color: white; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: pointer;">
105
107
  ${this.escapeHTML(event.title)}
106
108
  </div>
107
109
  `;
108
- }
110
+ }
109
111
 
110
- _attachEventHandlers() {
111
- this.addListener(this.container, 'click', (e) => {
112
- const dayEl = e.target.closest('.fc-month-day');
113
- if (!dayEl || !this.container.contains(dayEl)) return;
114
- if (e.target.closest('.fc-event')) return;
112
+ _attachEventHandlers() {
113
+ this.addListener(this.container, 'click', e => {
114
+ const dayEl = e.target.closest('.fc-month-day');
115
+ if (!dayEl || !this.container.contains(dayEl)) return;
116
+ if (e.target.closest('.fc-event')) return;
115
117
 
116
- const date = new Date(dayEl.dataset.date);
117
- this.stateManager.selectDate(date);
118
- });
118
+ const date = new Date(dayEl.dataset.date);
119
+ this.stateManager.selectDate(date);
120
+ });
119
121
 
120
- // Common event handlers (event clicks)
121
- this.attachCommonEventHandlers();
122
- }
122
+ // Common event handlers (event clicks)
123
+ this.attachCommonEventHandlers();
124
+ }
123
125
  }
124
126
 
125
127
  export default MonthViewRenderer;