packages/calendar/src/lib/calendar/month.component.ts
Displays the days of a month in a calendarish table.
| selector | ec-month | 
            
| templateUrl | month.component.html | 
            
                        Properties | 
                
                        
  | 
                
                        Methods | 
                
                        Inputs | 
                
                        Outputs | 
                
constructor(defaultMonthFormat)
                     | 
                ||||
| 
                             
                                    Parameters :
                                     
                    
  | 
                
| colors | |
                        Type :         Object
                     | 
                |
| 
                         Color mapping for day cells. E.g. to view a month heatmap  | 
                |
| date | |
                        Type :         moment.Moment
                     | 
                |
| 
                         The current date (for showing month)  | 
                |
| disabled | |
| 
                         If true, nothing can be changed  | 
                |
| disableDrag | |
                        Default value : false
                     | 
                |
| 
                         If true, no dragging can be done at all (other drag flags will be ignored)  | 
                |
| disableDragAnywhere | |
                        Default value : false
                     | 
                |
| 
                         If true, cannot drag anywhere to select a span (can still drag start and end, if not disabled too)  | 
                |
| disableDragEnd | |
                        Default value : false
                     | 
                |
| 
                         If true, the timespan end cannot be dragged  | 
                |
| disableDragStart | |
                        Default value : false
                     | 
                |
| 
                         If true, the timespan start cannot be dragged  | 
                |
| disablePast | |
                        Type :         boolean
                     | 
                |
| 
                         If true, past dates cannot be selected  | 
                |
| heatmap | |
                        Type :         Object
                     | 
                |
| 
                         Class mapping for day cells. E.g. to apply different background classes  | 
                |
| selected | |
                        Type :         moment.Moment
                     | 
                |
| 
                         The current selected date  | 
                |
| selectSpan | |
                        Type :         moment.Moment[]
                     | 
                |
| 
                         Timespan in which the dates can be selected.  | 
                |
| spancolor | |
                        Default value : '#ccc'
                     | 
                |
| 
                         The color of days that are inside the timespan  | 
                |
| timespan | |
                        Type :         moment.Moment[]
                     | 
                |
| 
                         Timespan that is reflected. Marks days inside the span  | 
                |
| dayClicked | |
                        Type :         EventEmitter<any>
                     | 
                |
| 
                         Emits when the selected day changes.  | 
                |
| spanChanged | |
                        Type :         EventEmitter<any>
                     | 
                |
| 
                         Changed Timespan selection  | 
                |
| alter | |||||||||
alter(value, span: string)
                 | 
            |||||||||
| 
                     Updates the viewed date to reflect the given relative changes. 
                        Parameters :
                         
                    
 
                        Returns :          
                    void
                     | 
            
| canAlter | |||||||||
canAlter(value, span: string)
                 | 
            |||||||||
| 
                     
                        Parameters :
                         
                    
 
                        Returns :          
                    any
                     | 
            
| clearSelection | 
clearSelection()
                 | 
            
| 
                     Clears the current selected date 
                        Returns :          
                void
                     | 
            
| dragOverDay | |||||||||
dragOverDay(day: Day, e?)
                 | 
            |||||||||
| 
                     
                        Parameters :
                         
                    
 
                        Returns :          
                    void
                     | 
            
| dragStart | ||||||
dragStart(day, e)
                 | 
            ||||||
| 
                     
                        Parameters :
                         
                    
 
                        Returns :          
                    void
                     | 
            
| getDayColor | ||||||
getDayColor(_moment: moment.Moment)
                 | 
            ||||||
| 
                     
                        Parameters :
                         
                    
 
                        Returns :          
                    any
                     | 
            
| getDayHeat | ||||||
getDayHeat(_moment: moment.Moment)
                 | 
            ||||||
| 
                     
                        Parameters :
                         
                    
 
                        Returns :          
                    any
                     | 
            
| getMonth | ||||||||||||
getMonth(day, type?: string)
                 | 
            ||||||||||||
| 
                     Returns days of current month 
                        Parameters :
                         
                    
 
                        Returns :          
                    Array<Day>
                     | 
            
| isDraggable | ||||
isDraggable(day)
                 | 
            ||||
| 
                     
                        Parameters :
                         
                    
 
                        Returns :          
                    any
                     | 
            
| isInTimeSpan | ||||
isInTimeSpan(date)
                 | 
            ||||
| 
                     
                        Parameters :
                         
                    
 
                        Returns :          
                    any
                     | 
            
| isSelectable | ||||||||||||
isSelectable(date, span: string)
                 | 
            ||||||||||||
| 
                     
                        Parameters :
                         
                    
 
                        Returns :          
                    any
                     | 
            
| isSelected | ||||||
isSelected(_moment: moment.Moment)
                 | 
            ||||||
| 
                     Returns true if the given moment is currently selected (on a day basis) 
                        Parameters :
                         
                    
 
                        Returns :          
                    boolean
                     | 
            
| mouseOver | ||||||
mouseOver(day, e)
                 | 
            ||||||
| 
                     
                        Parameters :
                         
                    
 
                        Returns :          
                    void
                     | 
            
| mouseUp | ||||||
mouseUp(day, e)
                 | 
            ||||||
| 
                     
                        Parameters :
                         
                    
 
                        Returns :          
                    void
                     | 
            
| ngOnChanges | ||||
ngOnChanges(change)
                 | 
            ||||
| 
                     When changing the date or selected input, the calendar will update its view to display the month containing it. 
                        Parameters :
                         
                    
 
                        Returns :          
                    void
                     | 
            
| ngOnInit | 
ngOnInit()
                 | 
            
| 
                     Initializes the calendar. 
                        Returns :          
                void
                     | 
            
| selectDay | ||||||||||||
selectDay(_moment: moment.Moment, emit)
                 | 
            ||||||||||||
| 
                     Selects the day of the given moment. 
                        Parameters :
                         
                    
 
                        Returns :          
                    void
                     | 
            
| setDate | ||||||||
setDate(date: moment.Moment)
                 | 
            ||||||||
| 
                     Sets the calendars viewed date to the given moment's month. Renders always 42 cells to keep the layout consistent. 
                        Parameters :
                         
                    
 
                        Returns :          
                    void
                     | 
            
| setToday | 
setToday()
                 | 
            
| 
                     Sets the current selected date to today. 
                        Returns :          
                void
                     | 
            
| today | 
today()
                 | 
            
| 
                     Sets the current viewed date to today. 
                        Returns :          
                void
                     | 
            
| Public cells | 
                            Type :         Array<Day>
                         | 
                    
| 
                         The cells containing the days  | 
                
| Protected changeSpan | 
                            Type :         Subject<moment.Moment[]>
                         | 
                    
                            Default value : new Subject()
                         | 
                    
| dragged | 
                            Type :         any
                         | 
                    
| Public formatted | 
                            Type :         string
                         | 
                    
| 
                         The current month as string  | 
                
| Public monthFormat | 
                            Type :         string
                         | 
                    
                            Default value : 'MMMM YYYY'
                         | 
                    
| 
                         Format for month in header  | 
                
| move | 
                            Type :         boolean
                         | 
                    
| todayMoment | 
                            Default value : moment()
                         | 
                    
import { Component, EventEmitter, Inject, Input, OnChanges, OnInit, Output } from '@angular/core';
/* import { SymbolService } from '../../symbol/symbol.service'; */
import moment from 'moment-es6';
import { Subject } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
/** Interface for a day inside the a month.
 * <example-url>https://components.entrecode.de/ui/datetime?e=1</example-url>
 * */
export interface Day {
  /** The cell index */
  index: number;
  /** The moment that is represented by the day. */
  date: moment.Moment;
  /** Can be given a type, to set a class. */
  type?: string;
  /** The formatted day number. */
  format: string;
  /** Flag that is true if the day is today. */
  today: boolean;
  /** if the day is the first in the timespan */
  first: boolean;
  /** if the day is the last in the timespan */
  last: boolean;
  /** custom class */
  heat?: string;
}
/** Displays the days of a month in a calendarish table. */
@Component({
  selector: 'ec-month',
  templateUrl: 'month.component.html',
})
export class MonthComponent implements OnInit, OnChanges {
  dragged: any;
  move: boolean;
  todayMoment = moment();
  /** The current selected date */
  @Input() selected: moment.Moment;
  /** Color mapping for day cells. E.g. to view a month heatmap */
  @Input() colors: Object;
  /** Class mapping for day cells. E.g. to apply different background classes */
  @Input() heatmap: Object;
  /** Timespan that is reflected. Marks days inside the span */
  @Input() timespan: moment.Moment[];
  /** Timespan in which the dates can be selected. */
  @Input() selectSpan: moment.Moment[];
  /** If true, past dates cannot be selected */
  @Input() disablePast: boolean;
  /** The current date (for showing month) */
  @Input() date: moment.Moment;
  /** The color of days that are inside the timespan */
  @Input() spancolor = '#ccc';
  /** If true, the timespan start cannot be dragged */
  @Input() disableDragStart = false;
  /** If true, the timespan end cannot be dragged */
  @Input() disableDragEnd = false;
  /** If true, cannot drag anywhere to select a span (can still drag start and end, if not disabled too) */
  @Input() disableDragAnywhere = false;
  /** If true, no dragging can be done at all (other drag flags will be ignored) */
  @Input() disableDrag = false;
  /** If true, nothing can be changed */
  @Input() disabled;
  /** The current month as string */
  public formatted: string;
  /** The cells containing the days */
  public cells: Array<Day>;
  /** Format for month in header */
  public monthFormat = 'MMMM YYYY';
  /** Emits when the selected day changes. */
  @Output() dayClicked: EventEmitter<any> = new EventEmitter();
  /** Changed Timespan selection */
  @Output() spanChanged: EventEmitter<any> = new EventEmitter();
  protected changeSpan: Subject<moment.Moment[]> = new Subject();
  /* public symbol: SymbolService */
  constructor(@Inject('moment.format.month') protected defaultMonthFormat) {
    /* this.monthFormat = this.symbol.resolve('moment.format.month') || this.monthFormat; */
    this.monthFormat = this.defaultMonthFormat || this.monthFormat;
    this.changeSpan
      .asObservable()
      .pipe(debounceTime(500))
      .subscribe((timespan) => {
        this.spanChanged.emit(this.timespan);
      });
  }
  isDraggable(day) {
    return !this.disabled &&
      !this.disableDrag &&
      (
        (!this.disableDragAnywhere || this.isInTimeSpan(day.date)) ||
        ((day.first && !this.disableDragStart) || day.last && !this.disableDragEnd)
      );
  }
  dragOverDay(day: Day, e?) {
    if (!this.dragged) {
      return;
    }
    this.selected = null;
    /*  if (day.date.isSame(this.dragged.date)) {
       return;
     } */
    if (!day || !this.isSelectable(day.date)) {
      return;
    }
    let newTimespan;
    if (this.move) {
      const moved = day.date.diff(this.dragged.date, 'days');
      newTimespan = [this.timespan[0].clone().add(moved, 'days'), this.timespan[1].clone().add(moved, 'days')];
      this.dragged = day;
    } else {
      newTimespan = [].concat(this.timespan);
      newTimespan[this.dragged.first ? 0 : 1] = day.date.clone();
    }
    if (this.selectSpan && (
      newTimespan[0].isBefore(this.selectSpan[0].startOf('day')) || newTimespan[1].isAfter(this.selectSpan[1].endOf('day'))
    )) {
      return;
    }
    if (newTimespan[0].isSame(this.timespan[0]) && newTimespan[1].isSame(this.timespan[1])) {
      // nothing changes => no need to rerender
      return;
    }
    if (newTimespan[0].isAfter(newTimespan[1])) {
      this.dragged.first = !this.dragged.first;
      this.dragged.last = !this.dragged.last;
      newTimespan.reverse();
    }
    this.timespan = newTimespan;
    // this.changeSpan.next(this.timespan);
    this.setDate();
    /* if (this.cells[0] === day || this.cells[this.cells.length - 1] === day) {
      // change month if dragging to edge
      this.setDate(day.date.clone().subtract(1, 'days'));
    } else {
      this.setDate();
    } */
    /* this.cells = this.getMonth(this.date, 'current'); */
  }
  isInTimeSpan(date) {
    return this.timespan && date.isBetween(this.timespan[0], this.timespan[1], 'days', '][');
  }
  dragStart(day, e) {
    if (this.disabled || !this.isDraggable(day)) {
      return;
    }
    e.preventDefault();
    this.dragged = day;
    this.move = false;
    if (!day.first && !day.last) {
      if (this.isInTimeSpan(day.date)) {
        this.move = true;
        return;
      }
      this.timespan = [day.date, day.date];
      this.setDate();
    }
  }
  mouseUp(day, e) {
    if (!this.dragged) {
      return;
    }
    delete this.dragged;
    this.changeSpan.next(this.timespan);
    e.preventDefault();
  }
  mouseOver(day, e) {
    if (!this.dragged || this.dragged === day) {
      return;
    }
    e.preventDefault();
    this.dragOverDay(day, e);
  }
  getDayColor(_moment: moment.Moment) {
    if (this.colors && this.colors[_moment.toISOString()]) {
      return this.colors[_moment.toISOString()];
    }
  }
  getDayHeat(_moment: moment.Moment) {
    if (this.heatmap && this.heatmap[_moment.toISOString()]) {
      return this.heatmap[_moment.toISOString()];
    }
  }
  /** Initializes the calendar. */
  ngOnInit() {
    this.setDate();
  }
  /** When changing the date or selected input, the calendar will update its view to display the month containing it. */
  ngOnChanges(change) {
    if (change.selected) {
      this.setDate(this.selected);
      return;
    } else if (change.date) {
      this.setDate(this.date);
    } else if (change.timespan || change.selectSpan) {
      this.setDate();
    }
    if (change.colors || change.heatmap) {
      this.cells = this.getMonth(this.date.clone(), 'current');
    }
  }
  /** Returns days of current month */
  getMonth(day = moment(), type?: string): Array<Day> {
    const begin = day
      .clone()
      .startOf('month')
      .startOf('week'); // .subtract(weeksbefore * 7, 'days');
    return new Array(42)
      .fill(0)
      .map((d, index) => begin.clone().add(index, 'days'))
      .map((date, index) => {
        const isStart =
          this.timespan &&
          date
            .clone()
            .startOf('day')
            .isSame(this.timespan[0].clone().startOf('day'));
        const isEnd =
          this.timespan &&
          date
            .clone()
            .startOf('day')
            .isSame(this.timespan[1].clone().startOf('day'));
        return {
          index,
          date,
          type: date.format('MM YYYY') === day.format('MM YYYY') ? 'current' : 'other',
          active: this.timespan && date.isBetween(this.timespan[0], this.timespan[1], 'days', '[]'),
          first: isStart,
          last: isEnd,
          selectable: this.isSelectable(date),
          inside: this.isInTimeSpan(date),
          color: this.getDayColor(date),
          heat: this.getDayHeat(date),
          format: date.format('DD'),
          today:
            moment()
              .startOf('day')
              .diff(date, 'days') === 0,
        };
      });
  }
  /** Sets the calendars viewed date to the given moment's month. Renders always 42 cells to keep the layout consistent. */
  setDate(date: moment.Moment = this.selected || this.date) {
    if (date && date !== this.date) {
      this.date = date.clone();
    }
    if (!date) {
      this.date = this.selectSpan ? this.selectSpan[1].clone() : moment();
    }
    setTimeout(() => {
      this.formatted = this.date.format(this.monthFormat);
      this.cells = this.getMonth(this.date.clone(), 'current');
    });
  }
  /** Selects the day of the given moment. */
  selectDay(_moment: moment.Moment, emit = true): void {
    if (this.disabled || !this.isSelectable(_moment)) {
      return;
    }
    if (!this.disableDragAnywhere) {
      this.timespan = [_moment, _moment];
      this.spanChanged.emit(this.timespan);
    } /* else if (!this.isInTimeSpan(_moment)) {
      if (_moment.isBefore(this.timespan[0])) {
        this.timespan = [_moment, this.timespan[1]];
      } else if (_moment.isAfter(this.timespan[1])) {
        this.timespan = [this.timespan[0], _moment];
      }
      this.spanChanged.emit(this.timespan);
    } */ else if (!this.timespan) {
      this.selected = _moment;
    }
    this.setDate(_moment);
    if (emit) {
      this.dayClicked.emit(_moment);
    }
  }
  /** Clears the current selected date*/
  clearSelection(): void {
    delete this.selected;
  }
  /** Returns true if the given moment is currently selected (on a day basis) */
  isSelected(_moment: moment.Moment): boolean {
    if (!this.selected) {
      return;
    }
    return this.selected.startOf('day').diff(_moment, 'days') === 0;
  }
  isSelectable(date, span = 'days') {
    if (this.disablePast && date.diff(moment(), 'days') < 0) {
      return false;
    }
    return !this.selectSpan || date.isBetween(this.selectSpan[0], this.selectSpan[1], span, '[]');
  }
  canAlter(value, span: string) {
    const newDate = this.date?.clone().add(value, span);
    return this.isSelectable(newDate, 'months');
  }
  /** Updates the viewed date to reflect the given relative changes. */
  alter(value, span: string): void {
    if (!this.canAlter(value, span)) {
      return;
    }
    this.setDate(this.date.clone().add(value, span));
  }
  /** Sets the current viewed date to today. */
  today(): void {
    this.setDate(moment());
  }
  /** Sets the current selected date to today. */
  setToday(): void {
    this.selectDay(moment());
  }
}
    <ul class="ec-calendar-days">
  <li
    *ngFor="let day of cells; let i = index"
    [class.is-active]="day.active"
    [class.is-last]="day.last"
    [class.is-first]="day.first"
    [class.is-inside-timespan]="day.inside && !day.last && !day.first"
    [class.is-draggable]="isDraggable(day)"
    [class.is-disabled]="day.disabled"
    [class.heat-none]="day.heat === 0"
    [class.heat-low]="day.heat > 0 && day.heat < 30"
    [class.heat-medium]="day.heat >= 30 && day.heat < 70"
    [class.heat-high]="day.heat >= 70 && day.heat < 90"
    [class.heat-extreme]="day.heat >= 90"
    [draggable]="isDraggable(day)"
    (dragstart)="dragStart(day, $event)"
    (click)="selectDay(day.date)"
    (mouseover)="mouseOver(day, $event)"
    (mouseup)="mouseUp(day, $event)"
  >
    <div
      class="ec-calendar-day"
      [style.background-color]="day.color"
      [class.is-other]="day.type !== 'current'"
      [class.is-not-selectable]="!day.selectable"
      [class.is-today]="day.today"
      [class.is-selected]="isSelected(day.date)"
    >
      {{ day.class || day.format }}
    </div>
  </li>
</ul>