@forcecalendar/interface 1.0.9 → 1.0.10

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.9",
3
+ "version": "1.0.10",
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",
@@ -272,10 +272,15 @@ export class ForceCalendar extends BaseComponent {
272
272
  flex-direction: column;
273
273
  }
274
274
 
275
- /* Ensure view components have proper dimensions */
276
- forcecal-month,
277
- forcecal-week,
278
- forcecal-day {
275
+ /* Ensure view container has proper dimensions */
276
+ #calendar-view-container {
277
+ display: block;
278
+ width: 100%;
279
+ height: 100%;
280
+ flex: 1;
281
+ }
282
+
283
+ #calendar-view-container > * {
279
284
  display: block;
280
285
  width: 100%;
281
286
  height: 100%;
@@ -421,50 +426,41 @@ export class ForceCalendar extends BaseComponent {
421
426
  }
422
427
 
423
428
  renderView() {
424
- if (!this.currentView) {
425
- return '<div>Loading view...</div>';
426
- }
427
-
428
- const tagName = `forcecal-${this.currentView}`;
429
- return `<${tagName} id="calendar-view"></${tagName}>`;
429
+ // Use a plain div container - we'll manually instantiate view classes
430
+ // This bypasses Locker Service's custom element restrictions
431
+ return '<div id="calendar-view-container"></div>';
430
432
  }
431
433
 
432
434
  afterRender() {
433
- // Set up view component using global registry for Locker Service compatibility
434
- const viewElement = this.$('#calendar-view');
435
- console.log('[ForceCalendar] afterRender - viewElement:', viewElement);
436
- console.log('[ForceCalendar] afterRender - stateManager:', !!this.stateManager);
437
-
438
- if (viewElement && this.stateManager) {
439
- // Debug: check what's available on the viewElement
440
- console.log('[ForceCalendar] viewElement.constructor.name:', viewElement.constructor?.name);
441
- console.log('[ForceCalendar] viewElement methods:', Object.getOwnPropertyNames(Object.getPrototypeOf(viewElement)));
442
-
443
- // Try to force custom element upgrade (Locker Service may prevent auto-upgrade)
444
- if (typeof customElements !== 'undefined' && customElements.upgrade) {
445
- console.log('[ForceCalendar] Forcing custom element upgrade');
446
- customElements.upgrade(viewElement);
435
+ // Manually instantiate and mount view component (bypasses Locker Service)
436
+ const container = this.$('#calendar-view-container');
437
+ console.log('[ForceCalendar] afterRender - container:', !!container, 'stateManager:', !!this.stateManager, 'currentView:', this.currentView);
438
+
439
+ if (container && this.stateManager && this.currentView) {
440
+ // Clean up previous view if exists
441
+ if (this._currentViewInstance) {
442
+ if (this._currentViewInstance.cleanup) {
443
+ this._currentViewInstance.cleanup();
444
+ }
447
445
  }
448
446
 
449
- // Store stateManager in global registry (bypasses Locker Service proxy issues)
450
- const registryId = this._registryId || (this._registryId = 'fc-' + Math.random().toString(36).substr(2, 9));
451
- window.__forceCalendarRegistry = window.__forceCalendarRegistry || {};
452
- window.__forceCalendarRegistry[registryId] = this.stateManager;
453
-
454
- // Pass registry ID via attribute (attributes work through Locker Service)
455
- viewElement.setAttribute('data-state-registry', registryId);
456
- console.log('[ForceCalendar] Set registry ID:', registryId);
457
-
458
- // Also try direct initialization if the element has the method
459
- if (viewElement._checkRegistry) {
460
- console.log('[ForceCalendar] Calling _checkRegistry directly');
461
- viewElement._checkRegistry();
462
- } else if (viewElement.setStateManager) {
463
- console.log('[ForceCalendar] Calling setStateManager directly');
464
- viewElement.setStateManager(this.stateManager);
447
+ console.log('[ForceCalendar] Creating view for:', this.currentView);
448
+
449
+ // Create a simple view renderer that doesn't use custom elements
450
+ const viewRenderer = this._createViewRenderer(this.currentView);
451
+ if (viewRenderer) {
452
+ this._currentViewInstance = viewRenderer;
453
+ viewRenderer.stateManager = this.stateManager;
454
+ viewRenderer.container = container;
455
+ viewRenderer.render();
456
+
457
+ // Subscribe to state changes
458
+ this.stateManager.subscribe((newState, oldState) => {
459
+ if (viewRenderer && viewRenderer.render) {
460
+ viewRenderer.render();
461
+ }
462
+ });
465
463
  }
466
- } else {
467
- console.log('[ForceCalendar] Could not set stateManager - viewElement:', !!viewElement, 'stateManager:', !!this.stateManager);
468
464
  }
469
465
 
470
466
  // Add event listeners for buttons using tracked addListener
@@ -510,6 +506,138 @@ export class ForceCalendar extends BaseComponent {
510
506
  }
511
507
  }
512
508
 
509
+ _createViewRenderer(viewName) {
510
+ // Create a simple view renderer that bypasses custom elements
511
+ // This is necessary for Salesforce Locker Service compatibility
512
+ const self = this;
513
+
514
+ return {
515
+ stateManager: null,
516
+ container: null,
517
+ _listeners: [],
518
+
519
+ cleanup() {
520
+ this._listeners.forEach(({ element, event, handler }) => {
521
+ element.removeEventListener(event, handler);
522
+ });
523
+ this._listeners = [];
524
+ },
525
+
526
+ addListener(element, event, handler) {
527
+ element.addEventListener(event, handler);
528
+ this._listeners.push({ element, event, handler });
529
+ },
530
+
531
+ render() {
532
+ if (!this.container || !this.stateManager) return;
533
+
534
+ const viewData = this.stateManager.getViewData();
535
+ if (!viewData || !viewData.weeks) {
536
+ this.container.innerHTML = '<div style="padding: 20px; text-align: center;">Loading calendar...</div>';
537
+ return;
538
+ }
539
+
540
+ this.cleanup();
541
+ const config = this.stateManager.getState().config;
542
+ const html = this._renderMonthView(viewData, config);
543
+ this.container.innerHTML = html;
544
+ this._attachEventHandlers();
545
+ },
546
+
547
+ _renderMonthView(viewData, config) {
548
+ const weekStartsOn = config.weekStartsOn || 0;
549
+ const dayNames = [];
550
+ for (let i = 0; i < 7; i++) {
551
+ const dayIndex = (weekStartsOn + i) % 7;
552
+ dayNames.push(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][dayIndex]);
553
+ }
554
+
555
+ let html = `
556
+ <style>
557
+ .fc-month-view { display: flex; flex-direction: column; height: 100%; background: #fff; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; font-size: 13px; }
558
+ .fc-month-header { display: grid; grid-template-columns: repeat(7, 1fr); background: #fafafa; border-bottom: 1px solid #e5e7eb; }
559
+ .fc-month-header-cell { padding: 8px; text-align: left; font-weight: 600; font-size: 10px; color: #9ca3af; text-transform: uppercase; letter-spacing: 0.5px; }
560
+ .fc-month-body { flex: 1; display: flex; flex-direction: column; }
561
+ .fc-month-week { flex: 1; display: grid; grid-template-columns: repeat(7, 1fr); border-bottom: 1px solid #e5e7eb; }
562
+ .fc-month-week:last-child { border-bottom: none; }
563
+ .fc-month-day { background: #fff; padding: 4px; position: relative; border-right: 1px solid #e5e7eb; min-height: 80px; cursor: pointer; }
564
+ .fc-month-day:last-child { border-right: none; }
565
+ .fc-month-day:hover { background: #f9fafb; }
566
+ .fc-month-day.other-month { background: #f9fafb; }
567
+ .fc-month-day.other-month .fc-day-number { color: #d1d5db; }
568
+ .fc-month-day.today .fc-day-number { background: #ef4444; color: white; border-radius: 50%; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; }
569
+ .fc-day-number { font-size: 12px; font-weight: 500; color: #111827; padding: 4px; }
570
+ .fc-day-events { display: flex; flex-direction: column; gap: 2px; margin-top: 2px; }
571
+ .fc-event { font-size: 11px; padding: 2px 6px; border-radius: 2px; background: #2563eb; color: white; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; cursor: pointer; }
572
+ .fc-event:hover { opacity: 0.9; }
573
+ .fc-more-events { font-size: 10px; color: #6b7280; padding: 2px 4px; cursor: pointer; }
574
+ .fc-more-events:hover { color: #111827; text-decoration: underline; }
575
+ </style>
576
+ <div class="fc-month-view">
577
+ <div class="fc-month-header">
578
+ ${dayNames.map(d => `<div class="fc-month-header-cell">${d}</div>`).join('')}
579
+ </div>
580
+ <div class="fc-month-body">
581
+ `;
582
+
583
+ viewData.weeks.forEach(week => {
584
+ html += '<div class="fc-month-week">';
585
+ week.days.forEach(day => {
586
+ const classes = ['fc-month-day'];
587
+ if (!day.isCurrentMonth) classes.push('other-month');
588
+ if (day.isToday) classes.push('today');
589
+
590
+ const events = day.events || [];
591
+ const visibleEvents = events.slice(0, 3);
592
+ const moreCount = events.length - 3;
593
+
594
+ html += `
595
+ <div class="${classes.join(' ')}" data-date="${day.date}">
596
+ <div class="fc-day-number">${day.dayOfMonth}</div>
597
+ <div class="fc-day-events">
598
+ ${visibleEvents.map(evt => `
599
+ <div class="fc-event" data-event-id="${evt.id}" style="background-color: ${evt.backgroundColor || '#2563eb'}">
600
+ ${evt.title}
601
+ </div>
602
+ `).join('')}
603
+ ${moreCount > 0 ? `<div class="fc-more-events">+${moreCount} more</div>` : ''}
604
+ </div>
605
+ </div>
606
+ `;
607
+ });
608
+ html += '</div>';
609
+ });
610
+
611
+ html += '</div></div>';
612
+ return html;
613
+ },
614
+
615
+ _attachEventHandlers() {
616
+ const stateManager = this.stateManager;
617
+
618
+ // Day click handlers
619
+ this.container.querySelectorAll('.fc-month-day').forEach(dayEl => {
620
+ this.addListener(dayEl, 'click', (e) => {
621
+ const date = new Date(dayEl.dataset.date);
622
+ stateManager.selectDate(date);
623
+ });
624
+ });
625
+
626
+ // Event click handlers
627
+ this.container.querySelectorAll('.fc-event').forEach(eventEl => {
628
+ this.addListener(eventEl, 'click', (e) => {
629
+ e.stopPropagation();
630
+ const eventId = eventEl.dataset.eventId;
631
+ const event = stateManager.getEvents().find(ev => ev.id === eventId);
632
+ if (event) {
633
+ stateManager.selectEvent(event);
634
+ }
635
+ });
636
+ });
637
+ }
638
+ };
639
+ }
640
+
513
641
  handleNavigation(event) {
514
642
  const action = event.currentTarget.dataset.action;
515
643
  switch (action) {