@forcecalendar/interface 1.0.9 → 1.0.11

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.11",
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%;
@@ -344,11 +349,147 @@ export class ForceCalendar extends BaseComponent {
344
349
  justify-content: space-between;
345
350
  width: 100%;
346
351
  }
347
-
352
+
348
353
  #create-event-btn {
349
354
  flex: 1;
350
355
  }
351
356
  }
357
+
358
+ /* Month View Styles (inline rendering for Locker Service compatibility) */
359
+ .fc-month-view {
360
+ display: flex;
361
+ flex-direction: column;
362
+ height: 100%;
363
+ background: var(--fc-background);
364
+ }
365
+
366
+ .fc-month-header {
367
+ display: grid;
368
+ grid-template-columns: repeat(7, 1fr);
369
+ border-bottom: 1px solid var(--fc-border-color);
370
+ background: var(--fc-background-alt);
371
+ }
372
+
373
+ .fc-month-header-cell {
374
+ padding: 12px 8px;
375
+ text-align: center;
376
+ font-size: 11px;
377
+ font-weight: 600;
378
+ color: var(--fc-text-light);
379
+ text-transform: uppercase;
380
+ letter-spacing: 0.05em;
381
+ }
382
+
383
+ .fc-month-body {
384
+ display: flex;
385
+ flex-direction: column;
386
+ flex: 1;
387
+ }
388
+
389
+ .fc-month-week {
390
+ display: grid;
391
+ grid-template-columns: repeat(7, 1fr);
392
+ flex: 1;
393
+ min-height: 100px;
394
+ }
395
+
396
+ .fc-month-day {
397
+ background: var(--fc-background);
398
+ border-right: 1px solid var(--fc-border-color);
399
+ border-bottom: 1px solid var(--fc-border-color);
400
+ padding: 4px;
401
+ min-height: 80px;
402
+ cursor: pointer;
403
+ transition: background-color 0.15s ease;
404
+ display: flex;
405
+ flex-direction: column;
406
+ }
407
+
408
+ .fc-month-day:hover {
409
+ background: var(--fc-background-hover);
410
+ }
411
+
412
+ .fc-month-day:last-child {
413
+ border-right: none;
414
+ }
415
+
416
+ .fc-month-day.other-month {
417
+ background: var(--fc-background-alt);
418
+ }
419
+
420
+ .fc-month-day.other-month .fc-day-number {
421
+ color: var(--fc-text-light);
422
+ }
423
+
424
+ .fc-month-day.today {
425
+ background: rgba(37, 99, 235, 0.05);
426
+ }
427
+
428
+ .fc-month-day.today .fc-day-number {
429
+ background: var(--fc-primary-color);
430
+ color: white;
431
+ border-radius: 50%;
432
+ width: 24px;
433
+ height: 24px;
434
+ display: flex;
435
+ align-items: center;
436
+ justify-content: center;
437
+ }
438
+
439
+ .fc-day-number {
440
+ font-size: 13px;
441
+ font-weight: 500;
442
+ color: var(--fc-text-color);
443
+ padding: 2px 4px;
444
+ margin-bottom: 4px;
445
+ }
446
+
447
+ .fc-day-events {
448
+ display: flex;
449
+ flex-direction: column;
450
+ gap: 2px;
451
+ flex: 1;
452
+ overflow: hidden;
453
+ }
454
+
455
+ .fc-event {
456
+ font-size: 11px;
457
+ padding: 2px 6px;
458
+ border-radius: 3px;
459
+ color: white;
460
+ white-space: nowrap;
461
+ overflow: hidden;
462
+ text-overflow: ellipsis;
463
+ cursor: pointer;
464
+ transition: transform 0.1s ease;
465
+ }
466
+
467
+ .fc-event:hover {
468
+ transform: scale(1.02);
469
+ }
470
+
471
+ .fc-more-events {
472
+ font-size: 10px;
473
+ color: var(--fc-text-light);
474
+ padding: 2px 4px;
475
+ font-weight: 500;
476
+ }
477
+
478
+ /* Week View Styles (inline rendering for Locker Service compatibility) */
479
+ .fc-week-view {
480
+ display: flex;
481
+ flex-direction: column;
482
+ height: 100%;
483
+ background: var(--fc-background);
484
+ }
485
+
486
+ /* Day View Styles (inline rendering for Locker Service compatibility) */
487
+ .fc-day-view {
488
+ display: flex;
489
+ flex-direction: column;
490
+ height: 100%;
491
+ background: var(--fc-background);
492
+ }
352
493
  `;
353
494
  }
354
495
 
@@ -421,50 +562,64 @@ export class ForceCalendar extends BaseComponent {
421
562
  }
422
563
 
423
564
  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}>`;
565
+ // Use a plain div container - we'll manually instantiate view classes
566
+ // This bypasses Locker Service's custom element restrictions
567
+ return '<div id="calendar-view-container"></div>';
430
568
  }
431
569
 
432
570
  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);
447
- }
448
-
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);
465
- }
466
- } else {
467
- console.log('[ForceCalendar] Could not set stateManager - viewElement:', !!viewElement, 'stateManager:', !!this.stateManager);
571
+ // Manually instantiate and mount view component (bypasses Locker Service)
572
+ const container = this.$('#calendar-view-container');
573
+ console.log('[ForceCalendar] afterRender - container:', !!container, 'stateManager:', !!this.stateManager, 'currentView:', this.currentView);
574
+
575
+ // Only create view once per view type change
576
+ if (container && this.stateManager && this.currentView) {
577
+ // Check if container actually has content (render() clears shadow DOM)
578
+ if (this._currentViewInstance && this._currentViewInstance._viewType === this.currentView && container.children.length > 0) {
579
+ console.log('[ForceCalendar] View already exists with content, skipping creation');
580
+ return;
581
+ }
582
+
583
+ // Clean up previous view if exists
584
+ if (this._currentViewInstance) {
585
+ if (this._currentViewInstance.cleanup) {
586
+ this._currentViewInstance.cleanup();
587
+ }
588
+ if (this._viewUnsubscribe) {
589
+ this._viewUnsubscribe();
590
+ this._viewUnsubscribe = null;
591
+ }
592
+ }
593
+
594
+ console.log('[ForceCalendar] Creating view for:', this.currentView);
595
+
596
+ // Create a simple view renderer that doesn't use custom elements
597
+ try {
598
+ const viewRenderer = this._createViewRenderer(this.currentView);
599
+ if (viewRenderer) {
600
+ viewRenderer._viewType = this.currentView;
601
+ this._currentViewInstance = viewRenderer;
602
+ viewRenderer.stateManager = this.stateManager;
603
+ viewRenderer.container = container;
604
+
605
+ console.log('[ForceCalendar] Calling viewRenderer.render()');
606
+ viewRenderer.render();
607
+ console.log('[ForceCalendar] viewRenderer.render() completed');
608
+
609
+ // Subscribe to state changes (store unsubscribe function)
610
+ this._viewUnsubscribe = this.stateManager.subscribe((newState, oldState) => {
611
+ // Only re-render on data changes, not view changes
612
+ if (newState.events !== oldState?.events ||
613
+ newState.currentDate !== oldState?.currentDate) {
614
+ if (viewRenderer && viewRenderer.render) {
615
+ viewRenderer.render();
616
+ }
617
+ }
618
+ });
619
+ }
620
+ } catch (err) {
621
+ console.error('[ForceCalendar] Error creating/rendering view:', err);
622
+ }
468
623
  }
469
624
 
470
625
  // Add event listeners for buttons using tracked addListener
@@ -510,6 +665,129 @@ export class ForceCalendar extends BaseComponent {
510
665
  }
511
666
  }
512
667
 
668
+ _createViewRenderer(viewName) {
669
+ // Create a simple view renderer that bypasses custom elements
670
+ // This is necessary for Salesforce Locker Service compatibility
671
+ const self = this;
672
+
673
+ return {
674
+ stateManager: null,
675
+ container: null,
676
+ _listeners: [],
677
+
678
+ cleanup() {
679
+ this._listeners.forEach(({ element, event, handler }) => {
680
+ element.removeEventListener(event, handler);
681
+ });
682
+ this._listeners = [];
683
+ },
684
+
685
+ addListener(element, event, handler) {
686
+ element.addEventListener(event, handler);
687
+ this._listeners.push({ element, event, handler });
688
+ },
689
+
690
+ render() {
691
+ console.log('[ViewRenderer] render called, container:', !!this.container, 'stateManager:', !!this.stateManager);
692
+ if (!this.container || !this.stateManager) return;
693
+
694
+ const viewData = this.stateManager.getViewData();
695
+ console.log('[ViewRenderer] viewData:', viewData);
696
+ console.log('[ViewRenderer] viewData.weeks:', viewData?.weeks);
697
+
698
+ if (!viewData || !viewData.weeks) {
699
+ this.container.innerHTML = '<div style="padding: 20px; text-align: center; background: #fee; color: #c00;">No viewData.weeks available. viewData keys: ' + (viewData ? Object.keys(viewData).join(', ') : 'null') + '</div>';
700
+ return;
701
+ }
702
+
703
+ this.cleanup();
704
+ const config = this.stateManager.getState().config;
705
+ console.log('[ViewRenderer] Rendering month view with', viewData.weeks.length, 'weeks');
706
+ const html = this._renderMonthView(viewData, config);
707
+ console.log('[ViewRenderer] HTML length:', html.length);
708
+ this.container.innerHTML = html;
709
+ console.log('[ViewRenderer] innerHTML set, container children:', this.container.children.length);
710
+ this._attachEventHandlers();
711
+ },
712
+
713
+ _renderMonthView(viewData, config) {
714
+ const weekStartsOn = config.weekStartsOn || 0;
715
+ const dayNames = [];
716
+ for (let i = 0; i < 7; i++) {
717
+ const dayIndex = (weekStartsOn + i) % 7;
718
+ dayNames.push(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][dayIndex]);
719
+ }
720
+
721
+ // Using inline styles for Locker Service compatibility
722
+ let html = `
723
+ <div class="fc-month-view" style="display: flex; flex-direction: column; height: 100%; min-height: 400px; background: #fff; border: 1px solid #e5e7eb;">
724
+ <div class="fc-month-header" style="display: grid; grid-template-columns: repeat(7, 1fr); border-bottom: 1px solid #e5e7eb; background: #f9fafb;">
725
+ ${dayNames.map(d => `<div class="fc-month-header-cell" style="padding: 12px 8px; text-align: center; font-size: 11px; font-weight: 600; color: #6b7280; text-transform: uppercase;">${d}</div>`).join('')}
726
+ </div>
727
+ <div class="fc-month-body" style="display: flex; flex-direction: column; flex: 1;">
728
+ `;
729
+
730
+ viewData.weeks.forEach(week => {
731
+ html += '<div class="fc-month-week" style="display: grid; grid-template-columns: repeat(7, 1fr); flex: 1; min-height: 80px;">';
732
+ week.days.forEach(day => {
733
+ const isOtherMonth = !day.isCurrentMonth;
734
+ const isToday = day.isToday;
735
+
736
+ const dayBg = isOtherMonth ? '#f3f4f6' : '#fff';
737
+ const dayNumColor = isOtherMonth ? '#9ca3af' : '#111827';
738
+ const todayStyle = isToday ? 'background: #2563eb; color: white; border-radius: 50%; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center;' : '';
739
+
740
+ const events = day.events || [];
741
+ const visibleEvents = events.slice(0, 3);
742
+ const moreCount = events.length - 3;
743
+
744
+ html += `
745
+ <div class="fc-month-day" data-date="${day.date}" style="background: ${dayBg}; border-right: 1px solid #e5e7eb; border-bottom: 1px solid #e5e7eb; padding: 4px; min-height: 80px; cursor: pointer;">
746
+ <div class="fc-day-number" style="font-size: 13px; font-weight: 500; color: ${dayNumColor}; padding: 2px 4px; margin-bottom: 4px; ${todayStyle}">${day.dayOfMonth}</div>
747
+ <div class="fc-day-events" style="display: flex; flex-direction: column; gap: 2px;">
748
+ ${visibleEvents.map(evt => `
749
+ <div class="fc-event" data-event-id="${evt.id}" style="background-color: ${evt.backgroundColor || '#2563eb'}; font-size: 11px; padding: 2px 6px; border-radius: 3px; color: white; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: pointer;">
750
+ ${evt.title}
751
+ </div>
752
+ `).join('')}
753
+ ${moreCount > 0 ? `<div class="fc-more-events" style="font-size: 10px; color: #6b7280; padding: 2px 4px; font-weight: 500;">+${moreCount} more</div>` : ''}
754
+ </div>
755
+ </div>
756
+ `;
757
+ });
758
+ html += '</div>';
759
+ });
760
+
761
+ html += '</div></div>';
762
+ return html;
763
+ },
764
+
765
+ _attachEventHandlers() {
766
+ const stateManager = this.stateManager;
767
+
768
+ // Day click handlers
769
+ this.container.querySelectorAll('.fc-month-day').forEach(dayEl => {
770
+ this.addListener(dayEl, 'click', (e) => {
771
+ const date = new Date(dayEl.dataset.date);
772
+ stateManager.selectDate(date);
773
+ });
774
+ });
775
+
776
+ // Event click handlers
777
+ this.container.querySelectorAll('.fc-event').forEach(eventEl => {
778
+ this.addListener(eventEl, 'click', (e) => {
779
+ e.stopPropagation();
780
+ const eventId = eventEl.dataset.eventId;
781
+ const event = stateManager.getEvents().find(ev => ev.id === eventId);
782
+ if (event) {
783
+ stateManager.selectEvent(event);
784
+ }
785
+ });
786
+ });
787
+ }
788
+ };
789
+ }
790
+
513
791
  handleNavigation(event) {
514
792
  const action = event.currentTarget.dataset.action;
515
793
  switch (action) {