@forcecalendar/interface 1.0.18 → 1.0.19

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.
@@ -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;