@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forcecalendar/interface",
3
- "version": "1.0.18",
3
+ "version": "1.0.20",
4
4
  "type": "module",
5
5
  "description": "Official interface layer for forceCalendar Core - Enterprise calendar components",
6
6
  "main": "dist/force-calendar-interface.umd.js",
@@ -16,24 +16,9 @@ import { MonthViewRenderer } from '../renderers/MonthViewRenderer.js';
16
16
  import { WeekViewRenderer } from '../renderers/WeekViewRenderer.js';
17
17
  import { DayViewRenderer } from '../renderers/DayViewRenderer.js';
18
18
 
19
- // Import view components (Web Components, for non-Salesforce usage)
20
- import { MonthView } from './views/MonthView.js';
21
- import { WeekView } from './views/WeekView.js';
22
- import { DayView } from './views/DayView.js';
19
+ // Import EventForm component
23
20
  import { EventForm } from './EventForm.js';
24
21
 
25
- // Register view components
26
- if (!customElements.get('forcecal-month')) {
27
- customElements.define('forcecal-month', MonthView);
28
- }
29
- if (!customElements.get('forcecal-week')) {
30
- customElements.define('forcecal-week', WeekView);
31
- }
32
- if (!customElements.get('forcecal-day')) {
33
- customElements.define('forcecal-day', DayView);
34
- }
35
- // EventForm is self-registering in its file
36
-
37
22
 
38
23
  export class ForceCalendar extends BaseComponent {
39
24
  static get observedAttributes() {
package/src/index.js CHANGED
@@ -25,11 +25,6 @@ export { DayViewRenderer } from './renderers/DayViewRenderer.js';
25
25
  import './components/ForceCalendar.js';
26
26
  export { ForceCalendar } from './components/ForceCalendar.js';
27
27
 
28
- // Views (Web Components - for non-Salesforce usage)
29
- export { MonthView } from './components/views/MonthView.js';
30
- export { WeekView } from './components/views/WeekView.js';
31
- export { DayView } from './components/views/DayView.js';
32
-
33
28
  // Auto-register main component if in browser environment
34
29
  if (typeof window !== 'undefined' && typeof customElements !== 'undefined') {
35
30
  // The ForceCalendar component self-registers
@@ -1,437 +0,0 @@
1
- /**
2
- * DayView - Professional Time-Grid Day View
3
- *
4
- * Displays a single 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 DayView extends BaseComponent {
13
- constructor() {
14
- super();
15
- this._stateManager = null;
16
- this.viewData = null;
17
- this.hours = Array.from({ length: 24 }, (_, i) => i);
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
- const dayCol = this.shadowRoot.querySelector('.day-column');
89
- if (!dayCol) return;
90
-
91
- const isMatch = (date) => date && DateUtils.isSameDay(date, new Date(dayCol.dataset.date));
92
-
93
- if (isMatch(newDate)) {
94
- dayCol.classList.add('selected');
95
- } else {
96
- dayCol.classList.remove('selected');
97
- }
98
- }
99
-
100
- loadViewData() {
101
- if (!this.stateManager) return;
102
- const viewData = this.stateManager.getViewData();
103
- this.viewData = this.processViewData(viewData);
104
- this.render();
105
- }
106
-
107
- processViewData(viewData) {
108
- if (!viewData) return null;
109
-
110
- let dayData = null;
111
- const currentState = this.stateManager?.getState();
112
- const currentDate = currentState?.currentDate || new Date();
113
-
114
- if (viewData.days && Array.isArray(viewData.days) && viewData.days.length > 0) {
115
- dayData = viewData.days.find(d => DateUtils.isSameDay(new Date(d.date), currentDate)) || viewData.days[0];
116
- }
117
- else if (viewData.weeks && Array.isArray(viewData.weeks) && viewData.weeks.length > 0) {
118
- const allDays = viewData.weeks.flatMap(w => w.days || []);
119
- dayData = allDays.find(d => DateUtils.isSameDay(new Date(d.date), currentDate)) || allDays[0];
120
- }
121
- else if (viewData.date) {
122
- dayData = viewData;
123
- }
124
-
125
- if (!dayData) return null;
126
-
127
- const dayDate = new Date(dayData.date);
128
- return {
129
- ...viewData,
130
- day: {
131
- ...dayData,
132
- date: dayDate,
133
- isToday: DateUtils.isToday(dayDate),
134
- timedEvents: (dayData.events || []).filter(e => !e.allDay),
135
- allDayEvents: (dayData.events || []).filter(e => e.allDay)
136
- }
137
- };
138
- }
139
-
140
- getStyles() {
141
- return `
142
- :host {
143
- display: flex;
144
- flex-direction: column;
145
- height: 100%;
146
- min-height: 0;
147
- }
148
-
149
- .day-view {
150
- display: flex;
151
- flex-direction: column;
152
- height: 100%;
153
- background: var(--fc-background);
154
- min-height: 0;
155
- overflow: hidden;
156
- }
157
-
158
- /* Header */
159
- .day-header {
160
- display: grid;
161
- grid-template-columns: 60px 1fr;
162
- border-bottom: 1px solid var(--fc-border-color);
163
- background: var(--fc-background);
164
- z-index: 20;
165
- flex-shrink: 0;
166
- }
167
-
168
- .day-column-header {
169
- padding: 16px 24px;
170
- text-align: left;
171
- display: flex;
172
- flex-direction: column;
173
- gap: 4px;
174
- }
175
-
176
- .day-name {
177
- font-size: 12px;
178
- font-weight: 700;
179
- color: var(--fc-text-light);
180
- text-transform: uppercase;
181
- letter-spacing: 0.1em;
182
- }
183
-
184
- .day-number {
185
- font-size: 24px;
186
- font-weight: 600;
187
- color: var(--fc-text-color);
188
- }
189
-
190
- .is-today .day-number {
191
- color: var(--fc-danger-color);
192
- }
193
-
194
- /* All Day Events */
195
- .all-day-row {
196
- display: grid;
197
- grid-template-columns: 60px 1fr;
198
- border-bottom: 1px solid var(--fc-border-color);
199
- background: var(--fc-background-alt);
200
- min-height: 36px;
201
- flex-shrink: 0;
202
- }
203
-
204
- .all-day-label {
205
- font-size: 9px;
206
- color: var(--fc-text-light);
207
- display: flex;
208
- align-items: center;
209
- justify-content: center;
210
- border-right: 1px solid var(--fc-border-color);
211
- text-transform: uppercase;
212
- font-weight: 700;
213
- }
214
-
215
- .all-day-cell {
216
- padding: 6px 12px;
217
- display: flex;
218
- flex-wrap: wrap;
219
- gap: 4px;
220
- }
221
-
222
- /* Body */
223
- .day-body {
224
- flex: 1;
225
- overflow-y: auto;
226
- overflow-x: hidden;
227
- position: relative;
228
- display: grid;
229
- grid-template-columns: 60px 1fr;
230
- background: var(--fc-background);
231
- }
232
-
233
- .time-gutter {
234
- border-right: 1px solid var(--fc-border-color);
235
- background: var(--fc-background-alt);
236
- height: 1440px;
237
- }
238
-
239
- .time-slot-label {
240
- height: 60px;
241
- font-size: 11px;
242
- color: var(--fc-text-light);
243
- text-align: right;
244
- padding-right: 12px;
245
- font-weight: 500;
246
- }
247
-
248
- .day-column {
249
- position: relative;
250
- height: 1440px;
251
- }
252
-
253
- .day-column.selected {
254
- background: var(--fc-background-hover);
255
- }
256
-
257
- /* Grid Lines */
258
- .grid-lines {
259
- position: absolute;
260
- top: 0;
261
- left: 60px;
262
- right: 0;
263
- bottom: 0;
264
- pointer-events: none;
265
- }
266
-
267
- .grid-line {
268
- height: 60px;
269
- border-bottom: 1px solid var(--fc-border-color);
270
- width: 100%;
271
- }
272
-
273
- /* Event Style */
274
- .event-container {
275
- position: absolute;
276
- left: 12px;
277
- right: 24px;
278
- border-radius: 6px;
279
- padding: 8px 12px;
280
- font-size: 13px;
281
- font-weight: 500;
282
- color: white;
283
- background: var(--fc-primary-color);
284
- border: 1px solid rgba(0,0,0,0.1);
285
- overflow: hidden;
286
- box-shadow: var(--fc-shadow);
287
- cursor: pointer;
288
- transition: all 0.15s ease;
289
- z-index: 5;
290
- }
291
-
292
- .event-container:hover {
293
- z-index: 10;
294
- transform: translateX(4px);
295
- }
296
-
297
- .now-indicator {
298
- position: absolute;
299
- left: 0;
300
- right: 0;
301
- height: 2px;
302
- background: var(--fc-danger-color);
303
- z-index: 15;
304
- pointer-events: none;
305
- }
306
- `;
307
- }
308
-
309
- template() {
310
- if (!this.viewData || !this.viewData.day) {
311
- return '<div class="day-view" style="padding: 20px; color: var(--fc-text-light);">No data available.</div>';
312
- }
313
-
314
- const { day } = this.viewData;
315
- const locale = this.stateManager?.state?.config?.locale || 'en-US';
316
- const dayName = DateUtils.formatDate(day.date, 'day', locale).split(' ')[0];
317
-
318
- return `
319
- <div class="day-view">
320
- <div class="day-header">
321
- <div class="time-gutter-header"></div>
322
- <div class="day-column-header ${day.isToday ? 'is-today' : ''}">
323
- <span class="day-name">${dayName}</span>
324
- <span class="day-number">${day.date.getDate()}</span>
325
- </div>
326
- </div>
327
-
328
- <div class="all-day-row">
329
- <div class="all-day-label">All day</div>
330
- <div class="all-day-cell">
331
- ${day.allDayEvents.map(e => this.renderAllDayEvent(e)).join('')}
332
- </div>
333
- </div>
334
-
335
- <div class="day-body" id="scroll-container">
336
- <div class="grid-lines">
337
- ${this.hours.map(() => `<div class="grid-line"></div>`).join('')}
338
- </div>
339
-
340
- <div class="time-gutter">
341
- ${this.hours.map(h => `
342
- <div class="time-slot-label">
343
- ${h === 0 ? '' : DateUtils.formatTime(new Date().setHours(h, 0), false)}
344
- </div>
345
- `).join('')}
346
- </div>
347
-
348
- <div class="day-column" data-date="${day.date.toISOString()}">
349
- ${day.isToday ? this.renderNowIndicator() : ''}
350
- ${day.timedEvents.map(e => this.renderTimedEvent(e)).join('')}
351
- </div>
352
- </div>
353
- </div>
354
- `;
355
- }
356
-
357
- renderTimedEvent(event) {
358
- const start = new Date(event.start);
359
- const end = new Date(event.end);
360
-
361
- const startMinutes = start.getHours() * 60 + start.getMinutes();
362
- const durationMinutes = (end - start) / (1000 * 60);
363
-
364
- const top = startMinutes;
365
- const height = Math.max(durationMinutes, 30);
366
-
367
- const color = StyleUtils.sanitizeColor(event.backgroundColor);
368
- const textColor = StyleUtils.sanitizeColor(StyleUtils.getContrastColor(color), 'white');
369
-
370
- return `
371
- <div class="event-container"
372
- style="top: ${top}px; height: ${height}px; background-color: ${color}; color: ${textColor};"
373
- data-event-id="${event.id}">
374
- <span class="event-title">${DOMUtils.escapeHTML(event.title)}</span>
375
- <span class="event-time">${DateUtils.formatTime(start)} - ${DateUtils.formatTime(end)}</span>
376
- </div>
377
- `;
378
- }
379
-
380
- renderAllDayEvent(event) {
381
- const color = StyleUtils.sanitizeColor(event.backgroundColor);
382
- const textColor = StyleUtils.sanitizeColor(StyleUtils.getContrastColor(color), 'white');
383
-
384
- return `
385
- <div class="event-item"
386
- style="background-color: ${color}; color: ${textColor}; font-size: 12px; padding: 4px 8px; border-radius: 4px; cursor: pointer; font-weight: 500; margin-bottom: 2px;"
387
- data-event-id="${event.id}">
388
- ${DOMUtils.escapeHTML(event.title)}
389
- </div>
390
- `;
391
- }
392
-
393
- renderNowIndicator() {
394
- const now = new Date();
395
- const minutes = now.getHours() * 60 + now.getMinutes();
396
- return `<div class="now-indicator" style="top: ${minutes}px"></div>`;
397
- }
398
-
399
- afterRender() {
400
- const container = this.$('#scroll-container');
401
- if (container && !this._scrolled) {
402
- container.scrollTop = 8 * 60 - 50;
403
- this._scrolled = true;
404
- }
405
-
406
- this.$$('[data-event-id]').forEach(el => {
407
- this.addListener(el, 'click', (e) => {
408
- e.stopPropagation();
409
- const eventId = e.currentTarget.dataset.eventId;
410
- const event = this.stateManager.getEvents().find(ev => ev.id === eventId);
411
- if (event) this.emit('event-click', { event });
412
- });
413
- });
414
-
415
- const dayCol = this.$('.day-column');
416
- if (dayCol) {
417
- this.addListener(dayCol, 'click', (e) => {
418
- const col = e.currentTarget;
419
- const container = this.$('#scroll-container');
420
- const rect = col.getBoundingClientRect();
421
- const y = e.clientY - rect.top + (container ? container.scrollTop : 0);
422
-
423
- const date = new Date(col.dataset.date);
424
- date.setHours(Math.floor(y / 60), Math.floor(y % 60), 0, 0);
425
-
426
- this.stateManager.selectDate(date);
427
- this.emit('day-click', { date });
428
- });
429
- }
430
- }
431
-
432
- unmount() {
433
- if (this.unsubscribe) this.unsubscribe();
434
- }
435
- }
436
-
437
- export default DayView;