@forcecalendar/interface 1.0.16 → 1.0.18
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/dist/force-calendar-interface.esm.js +788 -533
- package/dist/force-calendar-interface.esm.js.map +1 -1
- package/dist/force-calendar-interface.umd.js +200 -182
- package/dist/force-calendar-interface.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/components/EventForm.js +15 -10
- package/src/components/ForceCalendar.js +48 -451
- package/src/core/EventBus.js +66 -0
- package/src/core/StateManager.js +44 -3
- package/src/index.js +7 -1
- package/src/renderers/BaseViewRenderer.js +219 -0
- package/src/renderers/DayViewRenderer.js +199 -0
- package/src/renderers/MonthViewRenderer.js +125 -0
- package/src/renderers/WeekViewRenderer.js +171 -0
- package/src/renderers/index.js +11 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MonthViewRenderer - Renders month calendar grid
|
|
3
|
+
*
|
|
4
|
+
* Pure JavaScript renderer for month view, compatible with Salesforce Locker Service.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { BaseViewRenderer } from './BaseViewRenderer.js';
|
|
8
|
+
|
|
9
|
+
export class MonthViewRenderer extends BaseViewRenderer {
|
|
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 = '<div style="padding: 20px; text-align: center; color: #666;">No data available for month view.</div>';
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
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
|
+
<div class="fc-month-view" style="display: flex; flex-direction: column; height: 100%; min-height: 400px; background: #fff; border: 1px solid #e5e7eb;">
|
|
37
|
+
<div class="fc-month-header" style="display: grid; grid-template-columns: repeat(7, 1fr); border-bottom: 1px solid #e5e7eb; background: #f9fafb;">
|
|
38
|
+
${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('')}
|
|
39
|
+
</div>
|
|
40
|
+
<div class="fc-month-body" style="display: flex; flex-direction: column; flex: 1;">
|
|
41
|
+
`;
|
|
42
|
+
|
|
43
|
+
viewData.weeks.forEach(week => {
|
|
44
|
+
html += this._renderWeek(week);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
html += '</div></div>';
|
|
48
|
+
return html;
|
|
49
|
+
}
|
|
50
|
+
|
|
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;
|
|
59
|
+
}
|
|
60
|
+
|
|
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;">';
|
|
63
|
+
|
|
64
|
+
week.days.forEach(day => {
|
|
65
|
+
html += this._renderDay(day);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
html += '</div>';
|
|
69
|
+
return html;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
_renderDay(day) {
|
|
73
|
+
const isOtherMonth = !day.isCurrentMonth;
|
|
74
|
+
const isToday = day.isToday;
|
|
75
|
+
|
|
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
|
+
: '';
|
|
81
|
+
|
|
82
|
+
const events = day.events || [];
|
|
83
|
+
const visibleEvents = events.slice(0, this.maxEventsToShow);
|
|
84
|
+
const moreCount = events.length - this.maxEventsToShow;
|
|
85
|
+
|
|
86
|
+
return `
|
|
87
|
+
<div class="fc-month-day" data-date="${day.date}"
|
|
88
|
+
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
|
+
<div class="fc-day-number" style="font-size: 13px; font-weight: 500; color: ${dayNumColor}; padding: 2px 4px; margin-bottom: 4px; ${todayStyle}">
|
|
90
|
+
${day.dayOfMonth}
|
|
91
|
+
</div>
|
|
92
|
+
<div class="fc-day-events" style="display: flex; flex-direction: column; gap: 2px; flex: 1; overflow: hidden;">
|
|
93
|
+
${visibleEvents.map(evt => this._renderEvent(evt)).join('')}
|
|
94
|
+
${moreCount > 0 ? `<div class="fc-more-events" style="font-size: 10px; color: #6b7280; padding: 2px 4px; font-weight: 500;">+${moreCount} more</div>` : ''}
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
_renderEvent(event) {
|
|
101
|
+
const color = event.backgroundColor || '#2563eb';
|
|
102
|
+
return `
|
|
103
|
+
<div class="fc-event" data-event-id="${this.escapeHTML(event.id)}"
|
|
104
|
+
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
|
+
${this.escapeHTML(event.title)}
|
|
106
|
+
</div>
|
|
107
|
+
`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
_attachEventHandlers() {
|
|
111
|
+
// Day click handlers
|
|
112
|
+
this.container.querySelectorAll('.fc-month-day').forEach(dayEl => {
|
|
113
|
+
this.addListener(dayEl, 'click', (e) => {
|
|
114
|
+
if (e.target.closest('.fc-event')) return;
|
|
115
|
+
const date = new Date(dayEl.dataset.date);
|
|
116
|
+
this.stateManager.selectDate(date);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Common event handlers (event clicks)
|
|
121
|
+
this.attachCommonEventHandlers();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export default MonthViewRenderer;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WeekViewRenderer - Renders week calendar view
|
|
3
|
+
*
|
|
4
|
+
* Pure JavaScript renderer for week view, compatible with Salesforce Locker Service.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { BaseViewRenderer } from './BaseViewRenderer.js';
|
|
8
|
+
|
|
9
|
+
export class WeekViewRenderer 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
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
render() {
|
|
17
|
+
if (!this.container || !this.stateManager) return;
|
|
18
|
+
|
|
19
|
+
const viewData = this.stateManager.getViewData();
|
|
20
|
+
if (!viewData || !viewData.days || viewData.days.length === 0) {
|
|
21
|
+
this.container.innerHTML = '<div style="padding: 20px; text-align: center; color: #666;">No data available for week view.</div>';
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
this.cleanup();
|
|
26
|
+
const config = this.stateManager.getState().config;
|
|
27
|
+
const html = this._renderWeekView(viewData, config);
|
|
28
|
+
this.container.innerHTML = html;
|
|
29
|
+
this._attachEventHandlers();
|
|
30
|
+
this._scrollToCurrentTime();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
_renderWeekView(viewData, config) {
|
|
34
|
+
const days = viewData.days;
|
|
35
|
+
const dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
36
|
+
const hours = Array.from({ length: 24 }, (_, i) => i);
|
|
37
|
+
|
|
38
|
+
// Process days to categorize events
|
|
39
|
+
const processedDays = days.map(day => {
|
|
40
|
+
const dayDate = new Date(day.date);
|
|
41
|
+
const events = day.events || [];
|
|
42
|
+
return {
|
|
43
|
+
...day,
|
|
44
|
+
date: dayDate,
|
|
45
|
+
dayName: dayNames[dayDate.getDay()],
|
|
46
|
+
dayOfMonth: dayDate.getDate(),
|
|
47
|
+
isToday: this.isToday(dayDate),
|
|
48
|
+
timedEvents: events.filter(e => !e.allDay),
|
|
49
|
+
allDayEvents: events.filter(e => e.allDay)
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return `
|
|
54
|
+
<div class="fc-week-view" style="display: flex; flex-direction: column; height: 100%; background: #fff; overflow: hidden;">
|
|
55
|
+
${this._renderHeader(processedDays)}
|
|
56
|
+
${this._renderAllDayRow(processedDays)}
|
|
57
|
+
${this._renderTimeGrid(processedDays, hours)}
|
|
58
|
+
</div>
|
|
59
|
+
`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
_renderHeader(days) {
|
|
63
|
+
return `
|
|
64
|
+
<div class="fc-week-header" style="display: grid; grid-template-columns: 60px repeat(7, 1fr); border-bottom: 1px solid #e5e7eb; background: #f9fafb; flex-shrink: 0;">
|
|
65
|
+
<div style="border-right: 1px solid #e5e7eb;"></div>
|
|
66
|
+
${days.map(day => `
|
|
67
|
+
<div style="padding: 12px 8px; text-align: center; border-right: 1px solid #e5e7eb;">
|
|
68
|
+
<div style="font-size: 10px; font-weight: 700; color: #6b7280; text-transform: uppercase; letter-spacing: 0.1em;">
|
|
69
|
+
${day.dayName}
|
|
70
|
+
</div>
|
|
71
|
+
<div style="font-size: 16px; font-weight: 500; margin-top: 4px; ${day.isToday ? 'background: #dc2626; color: white; border-radius: 50%; width: 28px; height: 28px; display: inline-flex; align-items: center; justify-content: center;' : 'color: #111827;'}">
|
|
72
|
+
${day.dayOfMonth}
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
`).join('')}
|
|
76
|
+
</div>
|
|
77
|
+
`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
_renderAllDayRow(days) {
|
|
81
|
+
return `
|
|
82
|
+
<div class="fc-all-day-row" style="display: grid; grid-template-columns: 60px repeat(7, 1fr); border-bottom: 1px solid #e5e7eb; background: #fafafa; min-height: 32px; flex-shrink: 0;">
|
|
83
|
+
<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;">
|
|
84
|
+
All day
|
|
85
|
+
</div>
|
|
86
|
+
${days.map(day => `
|
|
87
|
+
<div class="fc-all-day-cell" data-date="${day.date.toISOString()}" style="border-right: 1px solid #e5e7eb; padding: 4px; display: flex; flex-direction: column; gap: 2px;">
|
|
88
|
+
${day.allDayEvents.map(evt => `
|
|
89
|
+
<div class="fc-event fc-all-day-event" data-event-id="${this.escapeHTML(evt.id)}"
|
|
90
|
+
style="background-color: ${evt.backgroundColor || '#2563eb'}; font-size: 10px; padding: 2px 4px; border-radius: 2px; color: white; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
|
|
91
|
+
${this.escapeHTML(evt.title)}
|
|
92
|
+
</div>
|
|
93
|
+
`).join('')}
|
|
94
|
+
</div>
|
|
95
|
+
`).join('')}
|
|
96
|
+
</div>
|
|
97
|
+
`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
_renderTimeGrid(days, hours) {
|
|
101
|
+
return `
|
|
102
|
+
<div id="week-scroll-container" class="fc-time-grid-container" style="flex: 1; overflow-y: auto; overflow-x: hidden; position: relative;">
|
|
103
|
+
<div class="fc-time-grid" style="display: grid; grid-template-columns: 60px repeat(7, 1fr); position: relative; height: ${this.totalHeight}px;">
|
|
104
|
+
${this._renderTimeGutter(hours)}
|
|
105
|
+
${days.map(day => this._renderDayColumn(day, hours)).join('')}
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
_renderTimeGutter(hours) {
|
|
112
|
+
return `
|
|
113
|
+
<div class="fc-time-gutter" style="border-right: 1px solid #e5e7eb; background: #fafafa;">
|
|
114
|
+
${hours.map(h => `
|
|
115
|
+
<div style="height: ${this.hourHeight}px; font-size: 10px; color: #6b7280; text-align: right; padding-right: 8px; font-weight: 500;">
|
|
116
|
+
${h === 0 ? '' : this.formatHour(h)}
|
|
117
|
+
</div>
|
|
118
|
+
`).join('')}
|
|
119
|
+
</div>
|
|
120
|
+
`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
_renderDayColumn(day, hours) {
|
|
124
|
+
return `
|
|
125
|
+
<div class="fc-week-day-column" data-date="${day.date.toISOString()}" style="border-right: 1px solid #e5e7eb; position: relative; cursor: pointer;">
|
|
126
|
+
<!-- Hour grid lines -->
|
|
127
|
+
${hours.map(() => `<div style="height: ${this.hourHeight}px; border-bottom: 1px solid #f3f4f6;"></div>`).join('')}
|
|
128
|
+
|
|
129
|
+
<!-- Now indicator for today -->
|
|
130
|
+
${day.isToday ? this.renderNowIndicator() : ''}
|
|
131
|
+
|
|
132
|
+
<!-- Timed events -->
|
|
133
|
+
${day.timedEvents.map(evt => this.renderTimedEvent(evt, { compact: true })).join('')}
|
|
134
|
+
</div>
|
|
135
|
+
`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
_attachEventHandlers() {
|
|
139
|
+
// Day column click handlers
|
|
140
|
+
this.container.querySelectorAll('.fc-week-day-column').forEach(dayEl => {
|
|
141
|
+
this.addListener(dayEl, 'click', (e) => {
|
|
142
|
+
if (e.target.closest('.fc-event')) return;
|
|
143
|
+
|
|
144
|
+
const date = new Date(dayEl.dataset.date);
|
|
145
|
+
const rect = dayEl.getBoundingClientRect();
|
|
146
|
+
const scrollContainer = this.container.querySelector('#week-scroll-container');
|
|
147
|
+
const y = e.clientY - rect.top + (scrollContainer ? scrollContainer.scrollTop : 0);
|
|
148
|
+
|
|
149
|
+
// Calculate time from click position
|
|
150
|
+
date.setHours(Math.floor(y / this.hourHeight), Math.floor((y % this.hourHeight) / (this.hourHeight / 60)), 0, 0);
|
|
151
|
+
this.stateManager.selectDate(date);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Common event handlers (event clicks)
|
|
156
|
+
this.attachCommonEventHandlers();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
_scrollToCurrentTime() {
|
|
160
|
+
if (this._scrolled) return;
|
|
161
|
+
|
|
162
|
+
const scrollContainer = this.container.querySelector('#week-scroll-container');
|
|
163
|
+
if (scrollContainer) {
|
|
164
|
+
// Scroll to 8 AM, minus some offset for visibility
|
|
165
|
+
scrollContainer.scrollTop = 8 * this.hourHeight - 50;
|
|
166
|
+
this._scrolled = true;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export default WeekViewRenderer;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* View Renderers
|
|
3
|
+
*
|
|
4
|
+
* Pure JavaScript renderers for calendar views.
|
|
5
|
+
* Compatible with Salesforce Locker Service (no custom elements).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export { BaseViewRenderer } from './BaseViewRenderer.js';
|
|
9
|
+
export { MonthViewRenderer } from './MonthViewRenderer.js';
|
|
10
|
+
export { WeekViewRenderer } from './WeekViewRenderer.js';
|
|
11
|
+
export { DayViewRenderer } from './DayViewRenderer.js';
|