@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,444 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* WeekView - Professional Time-Grid Week View
|
|
3
|
-
*
|
|
4
|
-
* Displays a 7-day schedule with a time axis and event positioning.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { BaseComponent } from '../../core/BaseComponent.js';
|
|
8
|
-
import { DateUtils } from '../../utils/DateUtils.js';
|
|
9
|
-
import { StyleUtils } from '../../utils/StyleUtils.js';
|
|
10
|
-
import { DOMUtils } from '../../utils/DOMUtils.js';
|
|
11
|
-
|
|
12
|
-
export class WeekView extends BaseComponent {
|
|
13
|
-
constructor() {
|
|
14
|
-
super();
|
|
15
|
-
this._stateManager = null;
|
|
16
|
-
this.viewData = null;
|
|
17
|
-
this.hours = Array.from({ length: 24 }, (_, i) => i); // 0-23
|
|
18
|
-
this._registryCheckInterval = null;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
connectedCallback() {
|
|
22
|
-
super.connectedCallback();
|
|
23
|
-
this._startRegistryPolling();
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
disconnectedCallback() {
|
|
27
|
-
super.disconnectedCallback();
|
|
28
|
-
if (this._registryCheckInterval) {
|
|
29
|
-
clearInterval(this._registryCheckInterval);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
_startRegistryPolling() {
|
|
34
|
-
this._checkRegistry();
|
|
35
|
-
let attempts = 0;
|
|
36
|
-
this._registryCheckInterval = setInterval(() => {
|
|
37
|
-
attempts++;
|
|
38
|
-
if (this._stateManager || attempts > 50) {
|
|
39
|
-
clearInterval(this._registryCheckInterval);
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
this._checkRegistry();
|
|
43
|
-
}, 100);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
_checkRegistry() {
|
|
47
|
-
const registryId = this.getAttribute('data-state-registry');
|
|
48
|
-
if (registryId && window.__forceCalendarRegistry && window.__forceCalendarRegistry[registryId]) {
|
|
49
|
-
clearInterval(this._registryCheckInterval);
|
|
50
|
-
this.setStateManager(window.__forceCalendarRegistry[registryId]);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
set stateManager(manager) {
|
|
55
|
-
this.setStateManager(manager);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
setStateManager(manager) {
|
|
59
|
-
if (this._stateManager === manager) return;
|
|
60
|
-
this._stateManager = manager;
|
|
61
|
-
if (manager) {
|
|
62
|
-
this.unsubscribe = manager.subscribe(this.handleStateUpdate.bind(this));
|
|
63
|
-
this.loadViewData();
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
get stateManager() {
|
|
68
|
-
return this._stateManager;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
handleStateUpdate(newState, oldState) {
|
|
72
|
-
// Granular updates
|
|
73
|
-
if (newState.currentDate !== oldState?.currentDate || newState.view !== oldState?.view) {
|
|
74
|
-
this.loadViewData();
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (newState.events !== oldState?.events) {
|
|
79
|
-
this.loadViewData(); // Simple reload for now
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (newState.selectedDate !== oldState?.selectedDate) {
|
|
83
|
-
this.updateSelection(newState.selectedDate, oldState?.selectedDate);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
updateSelection(newDate, oldDate) {
|
|
88
|
-
// Selection in WeekView is often visual on the column
|
|
89
|
-
if (oldDate) {
|
|
90
|
-
const oldDayEl = this.shadowRoot.querySelector(`[data-date^="${oldDate.toISOString().split('T')[0]}"]`);
|
|
91
|
-
if (oldDayEl) oldDayEl.classList.remove('selected');
|
|
92
|
-
}
|
|
93
|
-
if (newDate) {
|
|
94
|
-
const newDayEl = this.shadowRoot.querySelector(`[data-date^="${newDate.toISOString().split('T')[0]}"]`);
|
|
95
|
-
if (newDayEl) newDayEl.classList.add('selected');
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
loadViewData() {
|
|
100
|
-
if (!this.stateManager) return;
|
|
101
|
-
const viewData = this.stateManager.getViewData();
|
|
102
|
-
this.viewData = this.processViewData(viewData);
|
|
103
|
-
this.render();
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
processViewData(viewData) {
|
|
107
|
-
if (!viewData) return null;
|
|
108
|
-
|
|
109
|
-
let days = [];
|
|
110
|
-
if (viewData.weeks && viewData.weeks.length > 0) {
|
|
111
|
-
days = viewData.weeks[0].days;
|
|
112
|
-
} else if (viewData.days) {
|
|
113
|
-
days = viewData.days;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (!days || days.length === 0) return null;
|
|
117
|
-
|
|
118
|
-
return {
|
|
119
|
-
...viewData,
|
|
120
|
-
days: days.map(day => {
|
|
121
|
-
const dayDate = new Date(day.date);
|
|
122
|
-
return {
|
|
123
|
-
...day,
|
|
124
|
-
date: dayDate,
|
|
125
|
-
isToday: DateUtils.isToday(dayDate),
|
|
126
|
-
timedEvents: (day.events || []).filter(e => !e.allDay),
|
|
127
|
-
allDayEvents: (day.events || []).filter(e => e.allDay)
|
|
128
|
-
};
|
|
129
|
-
})
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
getStyles() {
|
|
134
|
-
return `
|
|
135
|
-
:host {
|
|
136
|
-
display: flex;
|
|
137
|
-
flex-direction: column;
|
|
138
|
-
height: 100%;
|
|
139
|
-
min-height: 0;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
.week-view {
|
|
143
|
-
display: flex;
|
|
144
|
-
flex-direction: column;
|
|
145
|
-
height: 100%;
|
|
146
|
-
background: var(--fc-background);
|
|
147
|
-
min-height: 0;
|
|
148
|
-
overflow: hidden;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/* Header Section */
|
|
152
|
-
.week-header {
|
|
153
|
-
display: grid;
|
|
154
|
-
grid-template-columns: 60px repeat(7, 1fr);
|
|
155
|
-
border-bottom: 1px solid var(--fc-border-color);
|
|
156
|
-
background: var(--fc-background);
|
|
157
|
-
z-index: 20;
|
|
158
|
-
flex-shrink: 0;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
.day-column-header {
|
|
162
|
-
padding: 12px 8px;
|
|
163
|
-
text-align: center;
|
|
164
|
-
border-right: 1px solid var(--fc-border-color);
|
|
165
|
-
display: flex;
|
|
166
|
-
flex-direction: column;
|
|
167
|
-
align-items: center;
|
|
168
|
-
gap: 4px;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
.day-name {
|
|
172
|
-
font-size: 10px;
|
|
173
|
-
font-weight: 700;
|
|
174
|
-
color: var(--fc-text-light);
|
|
175
|
-
text-transform: uppercase;
|
|
176
|
-
letter-spacing: 0.1em;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
.day-number {
|
|
180
|
-
font-size: 16px;
|
|
181
|
-
font-weight: 500;
|
|
182
|
-
width: 28px;
|
|
183
|
-
height: 28px;
|
|
184
|
-
display: flex;
|
|
185
|
-
align-items: center;
|
|
186
|
-
justify-content: center;
|
|
187
|
-
border-radius: 50%;
|
|
188
|
-
color: var(--fc-text-color);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
.is-today .day-number {
|
|
192
|
-
background: var(--fc-danger-color);
|
|
193
|
-
color: white;
|
|
194
|
-
font-weight: 700;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/* All Day Events Row */
|
|
198
|
-
.all-day-row {
|
|
199
|
-
display: grid;
|
|
200
|
-
grid-template-columns: 60px repeat(7, 1fr);
|
|
201
|
-
border-bottom: 1px solid var(--fc-border-color);
|
|
202
|
-
background: var(--fc-background-alt);
|
|
203
|
-
min-height: 32px;
|
|
204
|
-
flex-shrink: 0;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
.all-day-label {
|
|
208
|
-
font-size: 9px;
|
|
209
|
-
color: var(--fc-text-light);
|
|
210
|
-
display: flex;
|
|
211
|
-
align-items: center;
|
|
212
|
-
justify-content: center;
|
|
213
|
-
border-right: 1px solid var(--fc-border-color);
|
|
214
|
-
text-transform: uppercase;
|
|
215
|
-
font-weight: 700;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
.all-day-cell {
|
|
219
|
-
border-right: 1px solid var(--fc-border-color);
|
|
220
|
-
padding: 4px;
|
|
221
|
-
display: flex;
|
|
222
|
-
flex-direction: column;
|
|
223
|
-
gap: 2px;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/* Body Section */
|
|
227
|
-
.week-body {
|
|
228
|
-
flex: 1;
|
|
229
|
-
overflow-y: auto;
|
|
230
|
-
overflow-x: hidden;
|
|
231
|
-
position: relative;
|
|
232
|
-
display: grid;
|
|
233
|
-
grid-template-columns: 60px repeat(7, 1fr);
|
|
234
|
-
background: var(--fc-background);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
.time-gutter {
|
|
238
|
-
border-right: 1px solid var(--fc-border-color);
|
|
239
|
-
background: var(--fc-background-alt);
|
|
240
|
-
height: 1440px;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
.time-slot-label {
|
|
244
|
-
height: 60px;
|
|
245
|
-
font-size: 10px;
|
|
246
|
-
color: var(--fc-text-light);
|
|
247
|
-
text-align: right;
|
|
248
|
-
padding-right: 8px;
|
|
249
|
-
font-weight: 500;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
.day-column {
|
|
253
|
-
border-right: 1px solid var(--fc-border-color);
|
|
254
|
-
position: relative;
|
|
255
|
-
height: 1440px;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
.day-column.selected {
|
|
259
|
-
background: var(--fc-background-hover);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/* Grid Lines Layer */
|
|
263
|
-
.grid-lines {
|
|
264
|
-
position: absolute;
|
|
265
|
-
top: 0;
|
|
266
|
-
left: 60px;
|
|
267
|
-
right: 0;
|
|
268
|
-
bottom: 0;
|
|
269
|
-
pointer-events: none;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
.grid-line {
|
|
273
|
-
height: 60px;
|
|
274
|
-
border-bottom: 1px solid var(--fc-border-color);
|
|
275
|
-
width: 100%;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
.grid-line:last-child {
|
|
279
|
-
border-bottom: none;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
.event-container {
|
|
283
|
-
position: absolute;
|
|
284
|
-
left: 2px;
|
|
285
|
-
right: 2px;
|
|
286
|
-
border-radius: 4px;
|
|
287
|
-
padding: 4px 8px;
|
|
288
|
-
font-size: 11px;
|
|
289
|
-
font-weight: 500;
|
|
290
|
-
color: white;
|
|
291
|
-
background: var(--fc-primary-color);
|
|
292
|
-
border: 1px solid rgba(0,0,0,0.1);
|
|
293
|
-
overflow: hidden;
|
|
294
|
-
box-shadow: var(--fc-shadow-sm);
|
|
295
|
-
cursor: pointer;
|
|
296
|
-
transition: transform 0.1s;
|
|
297
|
-
z-index: 5;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
.event-container:hover {
|
|
301
|
-
z-index: 10;
|
|
302
|
-
transform: scale(1.02);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
.now-indicator {
|
|
306
|
-
position: absolute;
|
|
307
|
-
left: 0;
|
|
308
|
-
right: 0;
|
|
309
|
-
height: 2px;
|
|
310
|
-
background: var(--fc-danger-color);
|
|
311
|
-
z-index: 15;
|
|
312
|
-
pointer-events: none;
|
|
313
|
-
}
|
|
314
|
-
`;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
template() {
|
|
318
|
-
if (!this.viewData) return '<div class="week-view">Loading...</div>';
|
|
319
|
-
|
|
320
|
-
return `
|
|
321
|
-
<div class="week-view">
|
|
322
|
-
<div class="week-header">
|
|
323
|
-
<div class="time-gutter-header"></div>
|
|
324
|
-
${this.viewData.days.map(day => `
|
|
325
|
-
<div class="day-column-header ${day.isToday ? 'is-today' : ''}">
|
|
326
|
-
<span class="day-name">${DateUtils.getDayAbbreviation(day.date.getDay())}</span>
|
|
327
|
-
<span class="day-number">${day.date.getDate()}</span>
|
|
328
|
-
</div>
|
|
329
|
-
`).join('')}
|
|
330
|
-
</div>
|
|
331
|
-
|
|
332
|
-
<div class="all-day-row">
|
|
333
|
-
<div class="all-day-label">All day</div>
|
|
334
|
-
${this.viewData.days.map(day => `
|
|
335
|
-
<div class="all-day-cell">
|
|
336
|
-
${day.allDayEvents.map(e => this.renderAllDayEvent(e)).join('')}
|
|
337
|
-
</div>
|
|
338
|
-
`).join('')}
|
|
339
|
-
</div>
|
|
340
|
-
|
|
341
|
-
<div class="week-body" id="scroll-container">
|
|
342
|
-
<div class="grid-lines">
|
|
343
|
-
${this.hours.map(() => `<div class="grid-line"></div>`).join('')}
|
|
344
|
-
</div>
|
|
345
|
-
|
|
346
|
-
<div class="time-gutter">
|
|
347
|
-
${this.hours.map(h => `
|
|
348
|
-
<div class="time-slot-label">
|
|
349
|
-
${h === 0 ? '' : DateUtils.formatTime(new Date().setHours(h, 0), false)}
|
|
350
|
-
</div>
|
|
351
|
-
`).join('')}
|
|
352
|
-
</div>
|
|
353
|
-
|
|
354
|
-
${this.viewData.days.map(day => `
|
|
355
|
-
<div class="day-column" data-date="${day.date.toISOString()}">
|
|
356
|
-
${day.isToday ? this.renderNowIndicator() : ''}
|
|
357
|
-
${day.timedEvents.map(e => this.renderTimedEvent(e)).join('')}
|
|
358
|
-
</div>
|
|
359
|
-
`).join('')}
|
|
360
|
-
</div>
|
|
361
|
-
</div>
|
|
362
|
-
`;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
renderTimedEvent(event) {
|
|
366
|
-
const start = new Date(event.start);
|
|
367
|
-
const end = new Date(event.end);
|
|
368
|
-
|
|
369
|
-
const startMinutes = start.getHours() * 60 + start.getMinutes();
|
|
370
|
-
const durationMinutes = (end - start) / (1000 * 60);
|
|
371
|
-
|
|
372
|
-
const top = startMinutes;
|
|
373
|
-
const height = Math.max(durationMinutes, 20);
|
|
374
|
-
|
|
375
|
-
const color = StyleUtils.sanitizeColor(event.backgroundColor);
|
|
376
|
-
const textColor = StyleUtils.sanitizeColor(StyleUtils.getContrastColor(color), 'white');
|
|
377
|
-
|
|
378
|
-
return `
|
|
379
|
-
<div class="event-container"
|
|
380
|
-
style="top: ${top}px; height: ${height}px; background-color: ${color}; color: ${textColor};"
|
|
381
|
-
data-event-id="${event.id}">
|
|
382
|
-
<span class="event-title">${DOMUtils.escapeHTML(event.title)}</span>
|
|
383
|
-
<span class="event-time">${DateUtils.formatTime(start)}</span>
|
|
384
|
-
</div>
|
|
385
|
-
`;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
renderAllDayEvent(event) {
|
|
389
|
-
const color = StyleUtils.sanitizeColor(event.backgroundColor);
|
|
390
|
-
const textColor = StyleUtils.sanitizeColor(StyleUtils.getContrastColor(color), 'white');
|
|
391
|
-
|
|
392
|
-
return `
|
|
393
|
-
<div class="event-item"
|
|
394
|
-
style="background-color: ${color}; color: ${textColor}; font-size: 10px; padding: 2px 4px; border-radius: 2px; cursor: pointer; margin-bottom: 2px;"
|
|
395
|
-
data-event-id="${event.id}">
|
|
396
|
-
${DOMUtils.escapeHTML(event.title)}
|
|
397
|
-
</div>
|
|
398
|
-
`;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
renderNowIndicator() {
|
|
402
|
-
const now = new Date();
|
|
403
|
-
const minutes = now.getHours() * 60 + now.getMinutes();
|
|
404
|
-
return `<div class="now-indicator" style="top: ${minutes}px"></div>`;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
afterRender() {
|
|
408
|
-
const container = this.$('#scroll-container');
|
|
409
|
-
if (container && !this._scrolled) {
|
|
410
|
-
container.scrollTop = 8 * 60 - 50;
|
|
411
|
-
this._scrolled = true;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
this.$$('[data-event-id]').forEach(el => {
|
|
415
|
-
this.addListener(el, 'click', (e) => {
|
|
416
|
-
e.stopPropagation();
|
|
417
|
-
const eventId = e.currentTarget.dataset.eventId;
|
|
418
|
-
const event = this.stateManager.getEvents().find(ev => ev.id === eventId);
|
|
419
|
-
if (event) this.emit('event-click', { event });
|
|
420
|
-
});
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
this.$$('.day-column').forEach(el => {
|
|
424
|
-
this.addListener(el, 'click', (e) => {
|
|
425
|
-
const col = e.currentTarget;
|
|
426
|
-
const container = this.$('#scroll-container');
|
|
427
|
-
const rect = col.getBoundingClientRect();
|
|
428
|
-
const y = e.clientY - rect.top + (container ? container.scrollTop : 0);
|
|
429
|
-
|
|
430
|
-
const date = new Date(col.dataset.date);
|
|
431
|
-
date.setHours(Math.floor(y / 60), Math.floor(y % 60), 0, 0);
|
|
432
|
-
|
|
433
|
-
this.stateManager.selectDate(date);
|
|
434
|
-
this.emit('day-click', { date });
|
|
435
|
-
});
|
|
436
|
-
});
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
unmount() {
|
|
440
|
-
if (this.unsubscribe) this.unsubscribe();
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
export default WeekView;
|