@forcecalendar/interface 1.0.18 → 1.0.20
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 +186 -1310
- package/dist/force-calendar-interface.esm.js.map +1 -1
- package/dist/force-calendar-interface.umd.js +35 -750
- package/dist/force-calendar-interface.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/components/ForceCalendar.js +1 -16
- package/src/index.js +0 -5
- package/src/components/views/DayView.js +0 -437
- package/src/components/views/MonthView.js +0 -621
- package/src/components/views/WeekView.js +0 -444
|
@@ -1,621 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MonthView - Month grid view component
|
|
3
|
-
*
|
|
4
|
-
* Displays a traditional month calendar grid with events
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { BaseComponent } from '../../core/BaseComponent.js';
|
|
8
|
-
import { DOMUtils } from '../../utils/DOMUtils.js';
|
|
9
|
-
import { DateUtils } from '../../utils/DateUtils.js';
|
|
10
|
-
import { StyleUtils } from '../../utils/StyleUtils.js';
|
|
11
|
-
|
|
12
|
-
export class MonthView extends BaseComponent {
|
|
13
|
-
constructor() {
|
|
14
|
-
super();
|
|
15
|
-
this._stateManager = null;
|
|
16
|
-
this.viewData = null;
|
|
17
|
-
this.config = {
|
|
18
|
-
maxEventsToShow: 3,
|
|
19
|
-
};
|
|
20
|
-
this._registryCheckInterval = null;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
connectedCallback() {
|
|
24
|
-
super.connectedCallback();
|
|
25
|
-
console.log('[MonthView] connectedCallback - starting registry polling');
|
|
26
|
-
// Poll for registry since attributeChangedCallback doesn't work in Locker Service
|
|
27
|
-
this._startRegistryPolling();
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
disconnectedCallback() {
|
|
31
|
-
super.disconnectedCallback();
|
|
32
|
-
this._stopRegistryPolling();
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
_startRegistryPolling() {
|
|
36
|
-
// Check immediately
|
|
37
|
-
this._checkRegistry();
|
|
38
|
-
|
|
39
|
-
// Then poll every 100ms until we find it (max 5 seconds)
|
|
40
|
-
let attempts = 0;
|
|
41
|
-
this._registryCheckInterval = setInterval(() => {
|
|
42
|
-
attempts++;
|
|
43
|
-
if (this._stateManager || attempts > 50) {
|
|
44
|
-
this._stopRegistryPolling();
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
this._checkRegistry();
|
|
48
|
-
}, 100);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
_stopRegistryPolling() {
|
|
52
|
-
if (this._registryCheckInterval) {
|
|
53
|
-
clearInterval(this._registryCheckInterval);
|
|
54
|
-
this._registryCheckInterval = null;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
_checkRegistry() {
|
|
59
|
-
const registryId = this.getAttribute('data-state-registry');
|
|
60
|
-
console.log('[MonthView] Checking registry for ID:', registryId);
|
|
61
|
-
if (registryId && window.__forceCalendarRegistry && window.__forceCalendarRegistry[registryId]) {
|
|
62
|
-
const manager = window.__forceCalendarRegistry[registryId];
|
|
63
|
-
console.log('[MonthView] Found stateManager in registry');
|
|
64
|
-
this._stopRegistryPolling();
|
|
65
|
-
this.setStateManager(manager);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
set stateManager(manager) {
|
|
70
|
-
console.log('[MonthView] stateManager setter called with:', !!manager);
|
|
71
|
-
this.setStateManager(manager);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Method alternative for Salesforce Locker Service compatibility
|
|
75
|
-
setStateManager(manager) {
|
|
76
|
-
console.log('[MonthView] setStateManager method called with:', !!manager);
|
|
77
|
-
// Prevent re-initialization if same manager
|
|
78
|
-
if (this._stateManager === manager) {
|
|
79
|
-
console.log('[MonthView] stateManager already set, skipping');
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
this._stateManager = manager;
|
|
83
|
-
if (manager) {
|
|
84
|
-
console.log('[MonthView] Subscribing to state changes and loading view data');
|
|
85
|
-
// Subscribe to state changes
|
|
86
|
-
this.unsubscribe = manager.subscribe(this.handleStateUpdate.bind(this));
|
|
87
|
-
this.loadViewData();
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
get stateManager() {
|
|
92
|
-
return this._stateManager;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
handleStateUpdate(newState, oldState) {
|
|
96
|
-
if (newState.currentDate !== oldState.currentDate) {
|
|
97
|
-
this.loadViewData(); // Full reload if the month/year changes
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (newState.events !== oldState.events) {
|
|
102
|
-
this.updateEvents();
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (newState.selectedDate !== oldState.selectedDate) {
|
|
106
|
-
this.updateSelection(newState.selectedDate, oldState.selectedDate);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
updateEvents() {
|
|
111
|
-
this.loadViewData(); // For now, we still do a full reload. A more granular update would be more complex.
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
updateSelection(newDate, oldDate) {
|
|
115
|
-
if (oldDate) {
|
|
116
|
-
const oldDateEl = this.shadowRoot.querySelector(`[data-date^="${oldDate.toISOString().split('T')[0]}"]`);
|
|
117
|
-
if (oldDateEl) {
|
|
118
|
-
oldDateEl.classList.remove('selected');
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
if (newDate) {
|
|
122
|
-
const newDateEl = this.shadowRoot.querySelector(`[data-date^="${newDate.toISOString().split('T')[0]}"]`);
|
|
123
|
-
if (newDateEl) {
|
|
124
|
-
newDateEl.classList.add('selected');
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
loadViewData() {
|
|
130
|
-
console.log('[MonthView] loadViewData called, stateManager:', !!this.stateManager);
|
|
131
|
-
if (!this.stateManager) return;
|
|
132
|
-
|
|
133
|
-
const viewData = this.stateManager.getViewData();
|
|
134
|
-
console.log('[MonthView] viewData from stateManager:', viewData);
|
|
135
|
-
this.viewData = this.processViewData(viewData);
|
|
136
|
-
console.log('[MonthView] processed viewData:', this.viewData);
|
|
137
|
-
this.render();
|
|
138
|
-
console.log('[MonthView] render completed');
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
processViewData(viewData) {
|
|
142
|
-
if (!viewData || !viewData.weeks) return null;
|
|
143
|
-
|
|
144
|
-
const selectedDate = this.stateManager?.getState()?.selectedDate;
|
|
145
|
-
|
|
146
|
-
const weeks = viewData.weeks.map(week => {
|
|
147
|
-
return week.days.map(day => {
|
|
148
|
-
const dayDate = new Date(day.date);
|
|
149
|
-
const isSelected = selectedDate && dayDate.toDateString() === selectedDate.toDateString();
|
|
150
|
-
|
|
151
|
-
const processedEvents = day.events.map(event => ({
|
|
152
|
-
...event,
|
|
153
|
-
textColor: this.getContrastingTextColor(event.backgroundColor)
|
|
154
|
-
}));
|
|
155
|
-
|
|
156
|
-
return {
|
|
157
|
-
...day,
|
|
158
|
-
date: dayDate,
|
|
159
|
-
isOtherMonth: !day.isCurrentMonth,
|
|
160
|
-
isSelected,
|
|
161
|
-
events: processedEvents,
|
|
162
|
-
};
|
|
163
|
-
});
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
return {
|
|
167
|
-
...viewData,
|
|
168
|
-
weeks,
|
|
169
|
-
month: viewData.month,
|
|
170
|
-
year: viewData.year
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
getContrastingTextColor(bgColor) {
|
|
175
|
-
if (!bgColor || typeof bgColor !== 'string') return 'white';
|
|
176
|
-
|
|
177
|
-
// Extract hex color, removing # if present
|
|
178
|
-
const color = (bgColor.charAt(0) === '#') ? bgColor.substring(1) : bgColor;
|
|
179
|
-
|
|
180
|
-
// Validate hex format (3 or 6 characters)
|
|
181
|
-
if (!/^[0-9A-Fa-f]{3}$|^[0-9A-Fa-f]{6}$/.test(color)) {
|
|
182
|
-
return 'white'; // Fallback for invalid format
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Expand 3-char hex to 6-char
|
|
186
|
-
const fullColor = color.length === 3
|
|
187
|
-
? color[0] + color[0] + color[1] + color[1] + color[2] + color[2]
|
|
188
|
-
: color;
|
|
189
|
-
|
|
190
|
-
const r = parseInt(fullColor.substring(0, 2), 16);
|
|
191
|
-
const g = parseInt(fullColor.substring(2, 4), 16);
|
|
192
|
-
const b = parseInt(fullColor.substring(4, 6), 16);
|
|
193
|
-
|
|
194
|
-
// Check for NaN (shouldn't happen with validation, but just in case)
|
|
195
|
-
if (isNaN(r) || isNaN(g) || isNaN(b)) {
|
|
196
|
-
return 'white';
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
const uicolors = [r / 255, g / 255, b / 255];
|
|
200
|
-
const c = uicolors.map((col) => {
|
|
201
|
-
if (col <= 0.03928) {
|
|
202
|
-
return col / 12.92;
|
|
203
|
-
}
|
|
204
|
-
return Math.pow((col + 0.055) / 1.055, 2.4);
|
|
205
|
-
});
|
|
206
|
-
const L = (0.2126 * c[0]) + (0.7152 * c[1]) + (0.0722 * c[2]);
|
|
207
|
-
return (L > 0.179) ? 'black' : 'white';
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
isSelectedDate(date) {
|
|
211
|
-
const selectedDate = this.stateManager?.getState()?.selectedDate;
|
|
212
|
-
return selectedDate && date.toDateString() === selectedDate.toDateString();
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
getStyles() {
|
|
216
|
-
return `
|
|
217
|
-
:host {
|
|
218
|
-
display: block;
|
|
219
|
-
height: 100%;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
.month-view {
|
|
223
|
-
display: flex;
|
|
224
|
-
flex-direction: column;
|
|
225
|
-
height: 100%;
|
|
226
|
-
background: var(--fc-background);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
.month-header {
|
|
230
|
-
display: grid;
|
|
231
|
-
grid-template-columns: repeat(7, 1fr);
|
|
232
|
-
background: var(--fc-background);
|
|
233
|
-
border-bottom: 1px solid var(--fc-border-color);
|
|
234
|
-
z-index: 5;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
.month-header-cell {
|
|
238
|
-
padding: var(--fc-spacing-sm);
|
|
239
|
-
text-align: left; /* Align with dates */
|
|
240
|
-
font-weight: var(--fc-font-weight-bold);
|
|
241
|
-
font-size: 10px;
|
|
242
|
-
color: var(--fc-text-light);
|
|
243
|
-
text-transform: uppercase;
|
|
244
|
-
letter-spacing: 0.1em;
|
|
245
|
-
border-left: 1px solid transparent; /* Alignment hack */
|
|
246
|
-
padding-left: 8px;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
.month-body {
|
|
250
|
-
flex: 1;
|
|
251
|
-
display: flex;
|
|
252
|
-
flex-direction: column;
|
|
253
|
-
overflow: hidden;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
.month-week {
|
|
257
|
-
flex: 1;
|
|
258
|
-
display: grid;
|
|
259
|
-
grid-template-columns: repeat(7, 1fr);
|
|
260
|
-
border-bottom: 1px solid var(--fc-border-color);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
.month-week:last-child {
|
|
264
|
-
border-bottom: none;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
.month-day {
|
|
268
|
-
background: var(--fc-background);
|
|
269
|
-
padding: 4px;
|
|
270
|
-
position: relative;
|
|
271
|
-
cursor: default;
|
|
272
|
-
overflow: hidden;
|
|
273
|
-
min-height: 80px;
|
|
274
|
-
border-right: 1px solid var(--fc-border-color);
|
|
275
|
-
display: flex;
|
|
276
|
-
flex-direction: column;
|
|
277
|
-
min-width: 0; /* Critical for Grid Item shrinking */
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
.month-day:last-child {
|
|
281
|
-
border-right: none;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
.month-day:hover {
|
|
285
|
-
background: var(--fc-background-alt);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
.month-day.other-month {
|
|
289
|
-
background: var(--fc-background-alt);
|
|
290
|
-
background-image: linear-gradient(45deg, #f9fafb 25%, transparent 25%, transparent 50%, #f9fafb 50%, #f9fafb 75%, transparent 75%, transparent);
|
|
291
|
-
background-size: 10px 10px;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
.month-day.other-month .day-number {
|
|
295
|
-
color: var(--fc-text-light);
|
|
296
|
-
opacity: 0.5;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
.month-day.selected {
|
|
300
|
-
background: var(--fc-background-hover);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
.day-header {
|
|
304
|
-
display: flex;
|
|
305
|
-
align-items: center;
|
|
306
|
-
justify-content: space-between;
|
|
307
|
-
padding: 4px;
|
|
308
|
-
margin-bottom: 2px;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
.day-number {
|
|
312
|
-
font-size: 12px;
|
|
313
|
-
font-family: var(--fc-font-family); /* Ensure monospaced feel if available */
|
|
314
|
-
font-weight: var(--fc-font-weight-medium);
|
|
315
|
-
color: var(--fc-text-color);
|
|
316
|
-
line-height: 1;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
.month-day.today .day-number {
|
|
320
|
-
color: white;
|
|
321
|
-
background: var(--fc-danger-color); /* Red for Today (Calendar standard) */
|
|
322
|
-
width: 20px;
|
|
323
|
-
height: 20px;
|
|
324
|
-
display: flex;
|
|
325
|
-
align-items: center;
|
|
326
|
-
justify-content: center;
|
|
327
|
-
border-radius: 50%;
|
|
328
|
-
margin-left: -4px; /* Optical adjustment */
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
.day-events {
|
|
332
|
-
display: flex;
|
|
333
|
-
flex-direction: column;
|
|
334
|
-
gap: 2px;
|
|
335
|
-
flex: 1;
|
|
336
|
-
overflow: hidden;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
/* Precision Event Style */
|
|
340
|
-
.event-item {
|
|
341
|
-
font-size: 11px;
|
|
342
|
-
padding: 2px 6px;
|
|
343
|
-
border-radius: 2px; /* Micro rounding */
|
|
344
|
-
|
|
345
|
-
/* High Contrast */
|
|
346
|
-
background: var(--fc-primary-color);
|
|
347
|
-
color: white;
|
|
348
|
-
|
|
349
|
-
overflow: hidden;
|
|
350
|
-
text-overflow: ellipsis;
|
|
351
|
-
white-space: nowrap;
|
|
352
|
-
cursor: pointer;
|
|
353
|
-
line-height: 1.3;
|
|
354
|
-
font-weight: var(--fc-font-weight-medium);
|
|
355
|
-
margin: 0 1px;
|
|
356
|
-
border: 1px solid rgba(0,0,0,0.05); /* Subtle border for definition */
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
.event-item:hover {
|
|
360
|
-
opacity: 0.9;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
.event-time {
|
|
364
|
-
font-weight: var(--fc-font-weight-bold);
|
|
365
|
-
margin-right: 4px;
|
|
366
|
-
opacity: 0.9;
|
|
367
|
-
font-size: 10px;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
.more-events {
|
|
371
|
-
font-size: 10px;
|
|
372
|
-
color: var(--fc-text-secondary);
|
|
373
|
-
cursor: pointer;
|
|
374
|
-
padding: 1px 4px;
|
|
375
|
-
font-weight: var(--fc-font-weight-medium);
|
|
376
|
-
text-align: right;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
.more-events:hover {
|
|
380
|
-
color: var(--fc-text-color);
|
|
381
|
-
text-decoration: underline;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
/* Responsive adjustments */
|
|
385
|
-
@media (max-width: 768px) {
|
|
386
|
-
.month-day {
|
|
387
|
-
min-height: 60px;
|
|
388
|
-
padding: 2px;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
.day-number {
|
|
392
|
-
font-size: 11px;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
.event-item {
|
|
396
|
-
font-size: 10px;
|
|
397
|
-
padding: 1px 3px;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
.month-header-cell {
|
|
401
|
-
font-size: 9px;
|
|
402
|
-
padding: 4px;
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
/* Loading state */
|
|
407
|
-
.month-loading {
|
|
408
|
-
display: flex;
|
|
409
|
-
align-items: center;
|
|
410
|
-
justify-content: center;
|
|
411
|
-
height: 100%;
|
|
412
|
-
color: var(--fc-text-secondary);
|
|
413
|
-
font-weight: var(--fc-font-weight-medium);
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
/* Empty state */
|
|
417
|
-
.month-empty {
|
|
418
|
-
display: flex;
|
|
419
|
-
flex-direction: column;
|
|
420
|
-
align-items: center;
|
|
421
|
-
justify-content: center;
|
|
422
|
-
height: 100%;
|
|
423
|
-
color: var(--fc-text-secondary);
|
|
424
|
-
gap: var(--fc-spacing-md);
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
.empty-icon {
|
|
428
|
-
width: 48px;
|
|
429
|
-
height: 48px;
|
|
430
|
-
opacity: 0.3;
|
|
431
|
-
}
|
|
432
|
-
`;
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
template() {
|
|
436
|
-
if (!this.viewData) {
|
|
437
|
-
return `
|
|
438
|
-
<div class="month-view">
|
|
439
|
-
<div class="month-loading">Loading calendar...</div>
|
|
440
|
-
</div>
|
|
441
|
-
`;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
return `
|
|
445
|
-
<div class="month-view">
|
|
446
|
-
${this.renderHeader()}
|
|
447
|
-
${this.renderBody()}
|
|
448
|
-
</div>
|
|
449
|
-
`;
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
renderHeader() {
|
|
453
|
-
const { config } = this.stateManager.getState();
|
|
454
|
-
const days = [];
|
|
455
|
-
const weekStartsOn = config.weekStartsOn || 0;
|
|
456
|
-
|
|
457
|
-
for (let i = 0; i < 7; i++) {
|
|
458
|
-
const dayIndex = (weekStartsOn + i) % 7;
|
|
459
|
-
const dayName = DateUtils.getDayAbbreviation(dayIndex, config.locale);
|
|
460
|
-
days.push(`<div class="month-header-cell">${dayName}</div>`);
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
return `
|
|
464
|
-
<div class="month-header">
|
|
465
|
-
${days.join('')}
|
|
466
|
-
</div>
|
|
467
|
-
`;
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
renderBody() {
|
|
471
|
-
if (!this.viewData.weeks || this.viewData.weeks.length === 0) {
|
|
472
|
-
return `
|
|
473
|
-
<div class="month-body">
|
|
474
|
-
<div class="month-empty">
|
|
475
|
-
<svg class="empty-icon" viewBox="0 0 24 24">
|
|
476
|
-
<path fill="currentColor" d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11z"/>
|
|
477
|
-
</svg>
|
|
478
|
-
<p>No calendar data available</p>
|
|
479
|
-
</div>
|
|
480
|
-
</div>
|
|
481
|
-
`;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
const weeks = this.viewData.weeks.map(week => this.renderWeek(week));
|
|
485
|
-
|
|
486
|
-
return `
|
|
487
|
-
<div class="month-body">
|
|
488
|
-
${weeks.join('')}
|
|
489
|
-
</div>
|
|
490
|
-
`;
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
renderWeek(weekDays) {
|
|
494
|
-
const days = weekDays.map(day => this.renderDay(day));
|
|
495
|
-
|
|
496
|
-
return `
|
|
497
|
-
<div class="month-week">
|
|
498
|
-
${days.join('')}
|
|
499
|
-
</div>
|
|
500
|
-
`;
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
renderDay(dayData) {
|
|
504
|
-
const { date, dayOfMonth, isOtherMonth, isToday, isSelected, isWeekend, events = [] } = dayData;
|
|
505
|
-
const dayNumber = dayOfMonth;
|
|
506
|
-
|
|
507
|
-
// Build classes
|
|
508
|
-
const classes = ['month-day'];
|
|
509
|
-
if (isOtherMonth) classes.push('other-month');
|
|
510
|
-
if (isToday) classes.push('today');
|
|
511
|
-
if (isSelected) classes.push('selected');
|
|
512
|
-
if (isWeekend) classes.push('weekend');
|
|
513
|
-
|
|
514
|
-
// Render events
|
|
515
|
-
const visibleEvents = events.slice(0, this.config.maxEventsToShow);
|
|
516
|
-
const remainingCount = events.length - this.config.maxEventsToShow;
|
|
517
|
-
|
|
518
|
-
const eventsHtml = visibleEvents.map(event => this.renderEvent(event)).join('');
|
|
519
|
-
const moreHtml = remainingCount > 0 ?
|
|
520
|
-
`<div class="more-events">+${remainingCount} more</div>` : '';
|
|
521
|
-
|
|
522
|
-
return `
|
|
523
|
-
<div class="${classes.join(' ')}"
|
|
524
|
-
data-date="${date.toISOString()}"
|
|
525
|
-
data-day="${dayNumber}">
|
|
526
|
-
<div class="day-header">
|
|
527
|
-
<span class="day-number">${dayNumber}</span>
|
|
528
|
-
</div>
|
|
529
|
-
<div class="day-events">
|
|
530
|
-
${eventsHtml}
|
|
531
|
-
${moreHtml}
|
|
532
|
-
</div>
|
|
533
|
-
</div>
|
|
534
|
-
`;
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
renderEvent(event) {
|
|
538
|
-
const { title, start, allDay, backgroundColor, textColor } = event;
|
|
539
|
-
|
|
540
|
-
let style = '';
|
|
541
|
-
if (backgroundColor) {
|
|
542
|
-
const safeColor = StyleUtils.sanitizeColor(backgroundColor);
|
|
543
|
-
const safeTextColor = StyleUtils.sanitizeColor(textColor, 'white');
|
|
544
|
-
style += `background-color: ${safeColor}; color: ${safeTextColor};`;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
let timeStr = '';
|
|
548
|
-
if (!allDay && start) {
|
|
549
|
-
timeStr = DateUtils.formatTime(new Date(start), false, false);
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
const classes = ['event-item'];
|
|
553
|
-
if (allDay) classes.push('all-day');
|
|
554
|
-
|
|
555
|
-
return `
|
|
556
|
-
<div class="${classes.join(' ')}"
|
|
557
|
-
style="${style}"
|
|
558
|
-
data-event-id="${event.id}"
|
|
559
|
-
title="${DOMUtils.escapeHTML(title)}">
|
|
560
|
-
${timeStr ? `<span class="event-time">${timeStr}</span>` : ''}
|
|
561
|
-
<span class="event-title">${DOMUtils.escapeHTML(title)}</span>
|
|
562
|
-
</div>
|
|
563
|
-
`;
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
afterRender() {
|
|
567
|
-
// Add click handlers for days
|
|
568
|
-
this.$$('.month-day').forEach(dayEl => {
|
|
569
|
-
this.addListener(dayEl, 'click', this.handleDayClick);
|
|
570
|
-
});
|
|
571
|
-
|
|
572
|
-
// Add click handlers for events
|
|
573
|
-
this.$$('.event-item').forEach(eventEl => {
|
|
574
|
-
this.addListener(eventEl, 'click', this.handleEventClick);
|
|
575
|
-
});
|
|
576
|
-
|
|
577
|
-
// Add click handlers for "more events"
|
|
578
|
-
this.$$('.more-events').forEach(moreEl => {
|
|
579
|
-
this.addListener(moreEl, 'click', this.handleMoreClick);
|
|
580
|
-
});
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
handleDayClick(event) {
|
|
584
|
-
event.stopPropagation();
|
|
585
|
-
const dayEl = event.currentTarget;
|
|
586
|
-
const date = new Date(dayEl.dataset.date);
|
|
587
|
-
|
|
588
|
-
this.stateManager.selectDate(date);
|
|
589
|
-
this.emit('day-click', { date });
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
handleEventClick(event) {
|
|
593
|
-
event.stopPropagation();
|
|
594
|
-
const eventEl = event.currentTarget;
|
|
595
|
-
const eventId = eventEl.dataset.eventId;
|
|
596
|
-
const calendarEvent = this.stateManager.getEvents().find(e => e.id === eventId);
|
|
597
|
-
|
|
598
|
-
if (calendarEvent) {
|
|
599
|
-
this.stateManager.selectEvent(calendarEvent);
|
|
600
|
-
this.emit('event-click', { event: calendarEvent });
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
handleMoreClick(event) {
|
|
605
|
-
event.stopPropagation();
|
|
606
|
-
const dayEl = event.currentTarget.closest('.month-day');
|
|
607
|
-
const date = new Date(dayEl.dataset.date);
|
|
608
|
-
const events = this.stateManager.getEventsForDate(date);
|
|
609
|
-
|
|
610
|
-
this.emit('more-events-click', { date, events });
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
unmount() {
|
|
614
|
-
if (this.unsubscribe) {
|
|
615
|
-
this.unsubscribe();
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
// Export both the class and as default
|
|
621
|
-
export default MonthView;
|