diff --git a/demos/demo-modules/draggable-external-events/component.ts b/demos/demo-modules/draggable-external-events/component.ts index 9ca4f7171..d1bb4ed7d 100644 --- a/demos/demo-modules/draggable-external-events/component.ts +++ b/demos/demo-modules/draggable-external-events/component.ts @@ -55,9 +55,13 @@ export class DemoComponent { eventDropped({ event, newStart, - newEnd + newEnd, + allDay }: CalendarEventTimesChangedEvent): void { const externalIndex = this.externalEvents.indexOf(event); + if (typeof allDay !== 'undefined') { + event.allDay = allDay; + } if (externalIndex > -1) { this.externalEvents.splice(externalIndex, 1); this.events.push(event); diff --git a/demos/demo-modules/kitchen-sink/component.ts b/demos/demo-modules/kitchen-sink/component.ts index ef33afea1..3eae90789 100644 --- a/demos/demo-modules/kitchen-sink/component.ts +++ b/demos/demo-modules/kitchen-sink/component.ts @@ -82,7 +82,13 @@ export class DemoComponent { end: addDays(new Date(), 1), title: 'A 3 day event', color: colors.red, - actions: this.actions + actions: this.actions, + allDay: true, + resizable: { + beforeStart: true, + afterEnd: true + }, + draggable: true }, { start: startOfDay(new Date()), @@ -94,7 +100,8 @@ export class DemoComponent { start: subDays(endOfMonth(new Date()), 3), end: addDays(endOfMonth(new Date()), 3), title: 'A long event that spans 2 months', - color: colors.blue + color: colors.blue, + allDay: true }, { start: addHours(startOfDay(new Date()), 2), diff --git a/package-lock.json b/package-lock.json index ddb153298..400183f83 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1718,9 +1718,9 @@ } }, "angular-draggable-droppable": { - "version": "4.0.0-beta.13", - "resolved": "https://registry.npmjs.org/angular-draggable-droppable/-/angular-draggable-droppable-4.0.0-beta.13.tgz", - "integrity": "sha512-MTUISR7mYCaIDVbWHjOKHLMDdSiSZZ+F3T7TQ920zpKcA6jTm48NuLUuQ6osTecaOck4EhqDa7iuiqlA6wbY8g==", + "version": "4.0.0-beta.16", + "resolved": "https://registry.npmjs.org/angular-draggable-droppable/-/angular-draggable-droppable-4.0.0-beta.16.tgz", + "integrity": "sha512-3dZvoaOEi/89k96lV3sJHIII7908l64RyEOIMmVAQpinLHcqTE67PO195cEs78DA+Q6206b63r62LWp/XJYA0w==", "requires": { "tslib": "^1.9.0" } @@ -3014,9 +3014,9 @@ } }, "calendar-utils": { - "version": "0.2.0-alpha.13", - "resolved": "https://registry.npmjs.org/calendar-utils/-/calendar-utils-0.2.0-alpha.13.tgz", - "integrity": "sha512-8Dgm6Hl7IvtvaKszyjZAwj4mzu0/8bXRXEEdjb9+kxGjoXYtGoRsudxuNC5xqHM5h32BirFPg0lz53SJkdTyiQ==" + "version": "0.2.0-alpha.15", + "resolved": "https://registry.npmjs.org/calendar-utils/-/calendar-utils-0.2.0-alpha.15.tgz", + "integrity": "sha512-7w0j1kF9EJgF4srDRdSirTjI7gIFF1tv3HhTo2nDfxIFU/kr7hanxUBU+foZGrbh2ZjaQDsvxUoa5J18zaIREQ==" }, "call-me-maybe": { "version": "1.0.1", diff --git a/package.json b/package.json index 78faf00e1..0cce5ea06 100644 --- a/package.json +++ b/package.json @@ -164,9 +164,9 @@ "@angular/core": ">=6.0.0 <8.0.0" }, "dependencies": { - "angular-draggable-droppable": "4.0.0-beta.13", + "angular-draggable-droppable": "^4.0.0-beta.16", "angular-resizable-element": "^3.2.0", - "calendar-utils": "0.2.0-alpha.13", + "calendar-utils": "0.2.0-alpha.15", "positioning": "^1.4.0" }, "sideEffects": [ diff --git a/src/date-adapters/date-adapter.ts b/src/date-adapters/date-adapter.ts index 7553595a9..f81b677d5 100644 --- a/src/date-adapters/date-adapter.ts +++ b/src/date-adapters/date-adapter.ts @@ -90,4 +90,8 @@ export abstract class DateAdapter implements BaseDateAdapter { date: Date | string | number, options?: { weekStartsOn?: number } ): Date; + + abstract getHours(date: Date | string | number): number; + + abstract getMinutes(date: Date | string | number): number; } diff --git a/src/modules/common/calendar-angular-date-formatter.provider.ts b/src/modules/common/calendar-angular-date-formatter.provider.ts index 9518e2362..9104613d7 100644 --- a/src/modules/common/calendar-angular-date-formatter.provider.ts +++ b/src/modules/common/calendar-angular-date-formatter.provider.ts @@ -66,6 +66,13 @@ export class CalendarAngularDateFormatter return `Week ${weekNumber} of ${year}`; } + /** + * The time formatting down the left hand side of the week view + */ + public weekViewHour({ date, locale }: DateFormatterParams): string { + return new DatePipe(locale).transform(date, 'h a', null, locale); + } + /** * The time formatting down the left hand side of the day view */ diff --git a/src/modules/common/calendar-date-formatter.interface.ts b/src/modules/common/calendar-date-formatter.interface.ts index b3967e190..559dd5f2f 100644 --- a/src/modules/common/calendar-date-formatter.interface.ts +++ b/src/modules/common/calendar-date-formatter.interface.ts @@ -47,6 +47,11 @@ export interface CalendarDateFormatterInterface { */ weekViewTitle({ date: Date }: DateFormatterParams): string; + /** + * The time formatting down the left hand side of the day view + */ + weekViewHour({ date: Date }: DateFormatterParams): string; + /** * The time formatting down the left hand side of the day view */ diff --git a/src/modules/common/calendar-event-times-changed-event.interface.ts b/src/modules/common/calendar-event-times-changed-event.interface.ts index 702ad75cb..b7c2dbb63 100644 --- a/src/modules/common/calendar-event-times-changed-event.interface.ts +++ b/src/modules/common/calendar-event-times-changed-event.interface.ts @@ -14,4 +14,5 @@ export interface CalendarEventTimesChangedEvent { event: CalendarEvent; newStart: Date; newEnd?: Date; + allDay?: boolean; } diff --git a/src/modules/common/calendar-moment-date-formatter.provider.ts b/src/modules/common/calendar-moment-date-formatter.provider.ts index 61503b0ca..c50882b9e 100644 --- a/src/modules/common/calendar-moment-date-formatter.provider.ts +++ b/src/modules/common/calendar-moment-date-formatter.provider.ts @@ -86,6 +86,15 @@ export class CalendarMomentDateFormatter .format('[Week] W [of] YYYY'); } + /** + * The time formatting down the left hand side of the week view + */ + public weekViewHour({ date, locale }: DateFormatterParams): string { + return this.moment(date) + .locale(locale) + .format('ha'); + } + /** * The time formatting down the left hand side of the day view */ diff --git a/src/modules/common/calendar-native-date-formatter.provider.ts b/src/modules/common/calendar-native-date-formatter.provider.ts index 29e265e5e..0faa076f6 100644 --- a/src/modules/common/calendar-native-date-formatter.provider.ts +++ b/src/modules/common/calendar-native-date-formatter.provider.ts @@ -70,6 +70,13 @@ export class CalendarNativeDateFormatter return `Week ${weekNumber} of ${year}`; } + /** + * The time formatting down the left hand side of the week view + */ + public weekViewHour({ date, locale }: DateFormatterParams): string { + return new Intl.DateTimeFormat(locale, { hour: 'numeric' }).format(date); + } + /** * The time formatting down the left hand side of the day view */ diff --git a/src/modules/common/calendar-resize-helper.provider.ts b/src/modules/common/calendar-resize-helper.provider.ts index 89212c1b3..764f00164 100644 --- a/src/modules/common/calendar-resize-helper.provider.ts +++ b/src/modules/common/calendar-resize-helper.provider.ts @@ -7,7 +7,10 @@ export class CalendarResizeHelper { ) {} validateResize({ rectangle }: { rectangle: ClientRect }): boolean { - if (this.minWidth && rectangle.width < this.minWidth) { + if ( + this.minWidth && + Math.ceil(rectangle.width) < Math.ceil(this.minWidth) + ) { return false; } diff --git a/src/modules/common/util.ts b/src/modules/common/util.ts index 554bc0e78..3fb48ea0e 100644 --- a/src/modules/common/util.ts +++ b/src/modules/common/util.ts @@ -1,8 +1,13 @@ import { CalendarEvent, + DayViewEvent, + DayViewHour, + DayViewHourSegment, validateEvents as validateEventsWithoutLog, - WeekDay + WeekDay, + WeekViewAllDayEvent } from 'calendar-utils'; +import { DateAdapter } from '../../date-adapters/date-adapter'; export const validateEvents = (events: CalendarEvent[]) => { const warn = (...args) => console.warn('angular-calendar', ...args); @@ -11,14 +16,14 @@ export const validateEvents = (events: CalendarEvent[]) => { export function isInside(outer: ClientRect, inner: ClientRect): boolean { return ( - outer.left <= inner.left && - inner.left <= outer.right && - outer.left <= inner.right && - inner.right <= outer.right && - outer.top <= inner.top && - inner.top <= outer.bottom && - outer.top <= inner.bottom && - inner.bottom <= outer.bottom + Math.ceil(outer.left) <= Math.ceil(inner.left) && + Math.ceil(inner.left) <= Math.ceil(outer.right) && + Math.ceil(outer.left) <= Math.ceil(inner.right) && + Math.ceil(inner.right) <= Math.ceil(outer.right) && + Math.ceil(outer.top) <= Math.ceil(inner.top) && + Math.ceil(inner.top) <= Math.ceil(outer.bottom) && + Math.ceil(outer.top) <= Math.ceil(inner.bottom) && + Math.ceil(inner.bottom) <= Math.ceil(outer.bottom) ); } @@ -33,3 +38,52 @@ export const trackByWeekDayHeaderDate = (index: number, day: WeekDay) => day.date.toISOString(); export const trackByIndex = (index: number) => index; + +export const trackByHourSegment = ( + index: number, + segment: DayViewHourSegment +) => segment.date.toISOString(); + +export const trackByHour = (index: number, hour: DayViewHour) => + hour.segments[0].date.toISOString(); + +export const trackByDayOrWeekEvent = ( + index: number, + weekEvent: WeekViewAllDayEvent | DayViewEvent +) => (weekEvent.event.id ? weekEvent.event.id : weekEvent.event); + +const MINUTES_IN_HOUR = 60; + +export function getMinutesMoved( + movedY: number, + hourSegments: number, + hourSegmentHeight: number, + eventSnapSize: number +): number { + const draggedInPixelsSnapSize = roundToNearest( + movedY, + eventSnapSize || hourSegmentHeight + ); + const pixelAmountInMinutes = + MINUTES_IN_HOUR / (hourSegments * hourSegmentHeight); + return draggedInPixelsSnapSize * pixelAmountInMinutes; +} + +export function getMinimumEventHeightInMinutes( + hourSegments: number, + hourSegmentHeight: number +) { + return (MINUTES_IN_HOUR / (hourSegments * hourSegmentHeight)) * 30; +} + +export function getDefaultEventEnd( + dateAdapter: DateAdapter, + event: CalendarEvent, + minimumMinutes: number +): Date { + if (event.end) { + return event.end; + } else { + return dateAdapter.addMinutes(event.start, minimumMinutes); + } +} diff --git a/src/modules/day/calendar-day-view.component.ts b/src/modules/day/calendar-day-view.component.ts index 7038c962b..92e8ee679 100644 --- a/src/modules/day/calendar-day-view.component.ts +++ b/src/modules/day/calendar-day-view.component.ts @@ -17,7 +17,8 @@ import { DayViewHour, DayViewHourSegment, DayViewEvent, - ViewPeriod + ViewPeriod, + WeekViewAllDayEvent } from 'calendar-utils'; import { Subject, Subscription } from 'rxjs'; import { ResizeEvent } from 'angular-resizable-element'; @@ -28,7 +29,16 @@ import { CalendarEventTimesChangedEventType } from '../common/calendar-event-times-changed-event.interface'; import { CalendarUtils } from '../common/calendar-utils.provider'; -import { validateEvents, trackByEventId, roundToNearest } from '../common/util'; +import { + validateEvents, + trackByEventId, + trackByHour, + trackByHourSegment, + getMinutesMoved, + getDefaultEventEnd, + getMinimumEventHeightInMinutes, + trackByDayOrWeekEvent +} from '../common/util'; import { DateAdapter } from '../../date-adapters/date-adapter'; import { DragEndEvent } from 'angular-draggable-droppable'; import { PlacementArray } from 'positioning'; @@ -41,11 +51,6 @@ export interface CalendarDayViewBeforeRenderEvent { period: ViewPeriod; } -/** - * @hidden - */ -const MINUTES_IN_HOUR: number = 60; - /** * @hidden */ @@ -80,7 +85,8 @@ export interface DayViewEventResize { class="cal-hour-rows" #dayEventsContainer mwlDroppable - (drop)="eventDroppedWithinContainer = true"> + (dragEnter)="eventDragEnter = eventDragEnter + 1" + (dragLeave)="eventDragEnter = eventDragEnter - 1">
- dayEvent.event.id ? dayEvent.event.id : dayEvent.event; + trackByHour = trackByHour; /** * @hidden */ - trackByHour = (index: number, hour: DayViewHour) => - hour.segments[0].date.toISOString(); + trackByHourSegment = trackByHourSegment; /** * @hidden */ - trackByHourSegment = (index: number, segment: DayViewHourSegment) => - segment.date.toISOString(); + trackByDayEvent = trackByDayOrWeekEvent; /** * @hidden @@ -399,13 +402,13 @@ export class CalendarDayViewComponent implements OnChanges, OnInit, OnDestroy { } eventDropped( - dropEvent: { dropData?: { event?: CalendarEvent; isInternal?: boolean } }, + dropEvent: { dropData?: { event?: CalendarEvent } }, segment: DayViewHourSegment ): void { if ( dropEvent.dropData && dropEvent.dropData.event && - !dropEvent.dropData.isInternal + this.events.indexOf(dropEvent.dropData.event) === -1 ) { this.eventTimesChanged.emit({ type: CalendarEventTimesChangedEventType.Drop, @@ -457,16 +460,19 @@ export class CalendarDayViewComponent implements OnChanges, OnInit, OnDestroy { dayEvent.top = currentResize.originalTop; dayEvent.height = currentResize.originalHeight; - const pixelAmountInMinutes: number = - MINUTES_IN_HOUR / (this.hourSegments * this.hourSegmentHeight); - const minutesMoved: number = pixelsMoved * pixelAmountInMinutes; + const minutesMoved = getMinutesMoved( + pixelsMoved, + this.hourSegments, + this.hourSegmentHeight, + this.eventSnapSize + ); + let newStart: Date = dayEvent.event.start; - let newEnd: Date = - dayEvent.event.end || - this.dateAdapter.addMinutes( - dayEvent.event.start, - 30 * pixelAmountInMinutes - ); + let newEnd: Date = getDefaultEventEnd( + this.dateAdapter, + dayEvent.event, + getMinimumEventHeightInMinutes(this.hourSegments, this.hourSegmentHeight) + ); if (resizingBeforeStart) { newStart = this.dateAdapter.addMinutes(newStart, minutesMoved); } else { @@ -489,20 +495,18 @@ export class CalendarDayViewComponent implements OnChanges, OnInit, OnDestroy { ); this.validateDrag = ({ x, y }) => this.currentResizes.size === 0 && dragHelper.validateDrag({ x, y }); - this.eventDroppedWithinContainer = false; + this.eventDragEnter = 0; this.cdr.markForCheck(); } dragEnded(dayEvent: DayViewEvent, dragEndEvent: DragEndEvent): void { - if (this.eventDroppedWithinContainer) { - const draggedInPixelsSnapSize = roundToNearest( + if (this.eventDragEnter > 0) { + const minutesMoved = getMinutesMoved( dragEndEvent.y, - this.eventSnapSize || this.hourSegmentHeight + this.hourSegments, + this.hourSegmentHeight, + this.eventSnapSize ); - const pixelAmountInMinutes: number = - MINUTES_IN_HOUR / (this.hourSegments * this.hourSegmentHeight); - const minutesMoved: number = - draggedInPixelsSnapSize * pixelAmountInMinutes; const newStart: Date = this.dateAdapter.addMinutes( dayEvent.event.start, minutesMoved @@ -511,12 +515,17 @@ export class CalendarDayViewComponent implements OnChanges, OnInit, OnDestroy { if (dayEvent.event.end) { newEnd = this.dateAdapter.addMinutes(dayEvent.event.end, minutesMoved); } - this.eventTimesChanged.emit({ - newStart, - newEnd, - event: dayEvent.event, - type: CalendarEventTimesChangedEventType.Drag - }); + if ( + newStart >= this.view.period.start && + (newEnd || newStart) <= this.view.period.end + ) { + this.eventTimesChanged.emit({ + newStart, + newEnd, + event: dayEvent.event, + type: CalendarEventTimesChangedEventType.Drag + }); + } } } diff --git a/src/modules/day/calendar-day-view.scss b/src/modules/day/calendar-day-view.scss index 52d20bdb1..c0dc8a894 100644 --- a/src/modules/day/calendar-day-view.scss +++ b/src/modules/day/calendar-day-view.scss @@ -57,17 +57,21 @@ } } - .cal-event { + .cal-event, + .cal-all-day-event { padding: 5px; font-size: 12px; + background-color: #d1e8ff; + border: 1px solid #1e90ff; + color: #1e90ff; + box-sizing: border-box; + } + + .cal-event { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; height: 100%; - box-sizing: border-box; - background-color: #d1e8ff; - border: 1px solid #1e90ff; - color: #1e90ff; } .cal-draggable { @@ -85,8 +89,6 @@ } .cal-all-day-event { - padding: 8px; - border: solid 1px; cursor: pointer; } diff --git a/src/modules/week/calendar-week-view-event.component.ts b/src/modules/week/calendar-week-view-event.component.ts index 5d90fc969..056324a0d 100644 --- a/src/modules/week/calendar-week-view-event.component.ts +++ b/src/modules/week/calendar-week-view-event.component.ts @@ -5,7 +5,7 @@ import { EventEmitter, TemplateRef } from '@angular/core'; -import { WeekViewEvent } from 'calendar-utils'; +import { WeekViewAllDayEvent, DayViewEvent } from 'calendar-utils'; import { PlacementArray } from 'positioning'; @Component({ @@ -17,12 +17,13 @@ import { PlacementArray } from 'positioning'; let-tooltipPlacement="tooltipPlacement" let-eventClicked="eventClicked" let-tooltipTemplate="tooltipTemplate" - let-tooltipAppendToBody="tooltipAppendToBody"> + let-tooltipAppendToBody="tooltipAppendToBody" + let-tooltipDisabled="tooltipDisabled">
` }) export class CalendarWeekViewEventComponent { - @Input() weekEvent: WeekViewEvent; + @Input() weekEvent: WeekViewAllDayEvent | DayViewEvent; @Input() tooltipPlacement: PlacementArray; @Input() tooltipAppendToBody: boolean; + @Input() tooltipDisabled: boolean; + @Input() customTemplate: TemplateRef; @Input() eventTitleTemplate: TemplateRef; diff --git a/src/modules/week/calendar-week-view-header.component.ts b/src/modules/week/calendar-week-view-header.component.ts index cad630520..a5d8de19f 100644 --- a/src/modules/week/calendar-week-view-header.component.ts +++ b/src/modules/week/calendar-week-view-header.component.ts @@ -28,7 +28,7 @@ import { trackByWeekDayHeaderDate } from '../common/util'; [ngClass]="day.cssClass" (mwlClick)="dayHeaderClicked.emit({day: day})" mwlDroppable - dragOVerClass="cal-drag-over" + dragOverClass="cal-drag-over" (drop)="eventDropped.emit({event: $event.dropData.event, newStart: day.date})"> {{ day.date | calendarDate:'weekViewColumnHeader':locale }}
{{ day.date | calendarDate:'weekViewColumnSubHeader':locale }} diff --git a/src/modules/week/calendar-week-view-hour-segment.component.ts b/src/modules/week/calendar-week-view-hour-segment.component.ts new file mode 100644 index 000000000..2a5986abc --- /dev/null +++ b/src/modules/week/calendar-week-view-hour-segment.component.ts @@ -0,0 +1,45 @@ +import { Component, Input, TemplateRef } from '@angular/core'; +import { WeekViewHourColumn } from 'calendar-utils'; + +@Component({ + selector: 'mwl-calendar-week-view-hour-segment', + template: ` + +
+
+ {{ segment.date | calendarDate:'weekViewHour':locale }} +
+
+
+ + + ` +}) +export class CalendarWeekViewHourSegmentComponent { + @Input() segment: WeekViewHourColumn; + + @Input() segmentHeight: number; + + @Input() locale: string; + + @Input() isTimeLabel: boolean; + + @Input() customTemplate: TemplateRef; +} diff --git a/src/modules/week/calendar-week-view.component.ts b/src/modules/week/calendar-week-view.component.ts index 23dd6e3eb..9970d1316 100644 --- a/src/modules/week/calendar-week-view.component.ts +++ b/src/modules/week/calendar-week-view.component.ts @@ -15,9 +15,13 @@ import { Subject, Subscription } from 'rxjs'; import { WeekDay, CalendarEvent, - WeekViewEvent, + WeekViewAllDayEvent, WeekView, - ViewPeriod + ViewPeriod, + WeekViewHourColumn, + DayViewHourSegment, + DayViewHour, + DayViewEvent } from 'calendar-utils'; import { ResizeEvent } from 'angular-resizable-element'; import { CalendarDragHelper } from '../common/calendar-drag-helper.provider'; @@ -27,12 +31,27 @@ import { CalendarEventTimesChangedEventType } from '../common/calendar-event-times-changed-event.interface'; import { CalendarUtils } from '../common/calendar-utils.provider'; -import { validateEvents, trackByIndex, roundToNearest } from '../common/util'; +import { + validateEvents, + trackByIndex, + roundToNearest, + trackByWeekDayHeaderDate, + trackByHourSegment, + trackByHour, + getMinutesMoved, + getDefaultEventEnd, + getMinimumEventHeightInMinutes, + trackByDayOrWeekEvent +} from '../common/util'; import { DateAdapter } from '../../date-adapters/date-adapter'; -import { DragEndEvent } from 'angular-draggable-droppable'; +import { + DragEndEvent, + DropEvent, + DragMoveEvent +} from 'angular-draggable-droppable'; import { PlacementArray } from 'positioning'; -export interface WeekViewEventResize { +export interface WeekViewAllDayEventResize { originalOffset: number; originalSpan: number; edge: string; @@ -62,63 +81,187 @@ export interface CalendarWeekViewBeforeRenderEvent { [locale]="locale" [customTemplate]="headerTemplate" (dayHeaderClicked)="dayHeaderClicked.emit($event)" - (eventDropped)="eventTimesChanged.emit({ - event: $event.event, - newStart: $event.newStart, - type: CalendarEventTimesChangedEventType.Drop - })"> + (eventDropped)="eventDropped({dropData: $event}, $event.newStart, true)"> -
+ (dragEnter)="eventDragEnter = eventDragEnter + 1" + (dragLeave)="eventDragEnter = eventDragEnter - 1"> +
+
+
+
+
+
+ (dragPointerDown)="dragStarted(eventRowContainer, event)" + (dragEnd)="dragEnded(allDayEvent, $event, dayColumnWidth)"> + (eventClicked)="eventClicked.emit({event: allDayEvent.event})">
+
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+ +
+ + +
+
+
+
` }) @@ -200,11 +343,56 @@ export class CalendarWeekViewComponent implements OnChanges, OnInit, OnDestroy { */ @Input() snapDraggedEvents: boolean = true; + /** + * The number of segments in an hour. Must be <= 6 + */ + @Input() hourSegments: number = 2; + + /** + * The height in pixels of each hour segment + */ + @Input() hourSegmentHeight: number = 30; + + /** + * The day start hours in 24 hour time. Must be 0-23 + */ + @Input() dayStartHour: number = 0; + + /** + * The day start minutes. Must be 0-59 + */ + @Input() dayStartMinute: number = 0; + + /** + * The day end hours in 24 hour time. Must be 0-23 + */ + @Input() dayEndHour: number = 23; + + /** + * The day end minutes. Must be 0-59 + */ + @Input() dayEndMinute: number = 59; + + /** + * A custom template to use to replace the hour segment + */ + @Input() hourSegmentTemplate: TemplateRef; + + /** + * The grid size to snap resizing and dragging of hourly events to + */ + @Input() eventSnapSize: number; + + /** + * A custom template to use for the all day events label text + */ + @Input() allDayEventsLabelTemplate: TemplateRef; + /** * Called when a header week day is clicked. Adding a `cssClass` property on `$event.day` will add that class to the header element */ @Output() - dayHeaderClicked: EventEmitter<{ day: WeekDay }> = new EventEmitter<{ + dayHeaderClicked = new EventEmitter<{ day: WeekDay; }>(); @@ -212,7 +400,7 @@ export class CalendarWeekViewComponent implements OnChanges, OnInit, OnDestroy { * Called when the event title is clicked */ @Output() - eventClicked: EventEmitter<{ event: CalendarEvent }> = new EventEmitter<{ + eventClicked = new EventEmitter<{ event: CalendarEvent; }>(); @@ -220,9 +408,7 @@ export class CalendarWeekViewComponent implements OnChanges, OnInit, OnDestroy { * Called when an event is resized or dragged and dropped */ @Output() - eventTimesChanged: EventEmitter< - CalendarEventTimesChangedEvent - > = new EventEmitter(); + eventTimesChanged = new EventEmitter(); /** * An output that will be called before the view is rendered for the current week. @@ -231,6 +417,14 @@ export class CalendarWeekViewComponent implements OnChanges, OnInit, OnDestroy { @Output() beforeViewRender = new EventEmitter(); + /** + * Called when an hour segment is clicked + */ + @Output() + hourSegmentClicked = new EventEmitter<{ + date: Date; + }>(); + /** * @hidden */ @@ -249,12 +443,25 @@ export class CalendarWeekViewComponent implements OnChanges, OnInit, OnDestroy { /** * @hidden */ - currentResizes: Map = new Map(); + allDayEventResizes: Map< + WeekViewAllDayEvent, + WeekViewAllDayEventResize + > = new Map(); + + /** + * @hidden + */ + timeEventResizes: Map = new Map(); + + /** + * @hidden + */ + eventDragEnter = 0; /** * @hidden */ - eventDroppedWithinContainer = false; + dragActive = false; /** * @hidden @@ -274,18 +481,33 @@ export class CalendarWeekViewComponent implements OnChanges, OnInit, OnDestroy { /** * @hidden */ - CalendarEventTimesChangedEventType = CalendarEventTimesChangedEventType; + trackByIndex = trackByIndex; /** * @hidden */ - trackByIndex = trackByIndex; + trackByWeekDayHeaderDate = trackByWeekDayHeaderDate; + + /** + * @hidden + */ + trackByHourSegment = trackByHourSegment; + + /** + * @hidden + */ + trackByHour = trackByHour; + + /** + * @hidden + */ + trackByDayOrWeekEvent = trackByDayOrWeekEvent; /** * @hidden */ - trackByEventId = (index: number, weekEvent: WeekViewEvent) => - weekEvent.event.id ? weekEvent.event.id : weekEvent; + trackByHourColumn = (index: number, column: WeekViewHourColumn) => + column.hours[0] ? column.hours[0].segments[0].date.toISOString() : column; /** * @hidden @@ -323,7 +545,19 @@ export class CalendarWeekViewComponent implements OnChanges, OnInit, OnDestroy { validateEvents(this.events); } - if (changes.events || changes.viewDate || changes.excludeDays) { + if ( + changes.viewDate || + changes.dayStartHour || + changes.dayStartMinute || + changes.dayEndHour || + changes.dayEndMinute || + changes.hourSegments || + changes.weekStartsOn || + changes.weekendDays || + changes.excludeDays || + changes.hourSegmentHeight || + changes.events + ) { this.refreshBody(); } } @@ -337,73 +571,134 @@ export class CalendarWeekViewComponent implements OnChanges, OnInit, OnDestroy { } } + private resizeStarted(eventsContainer: HTMLElement, minWidth?: number) { + this.dayColumnWidth = this.getDayColumnWidth(eventsContainer); + const resizeHelper: CalendarResizeHelper = new CalendarResizeHelper( + eventsContainer, + minWidth + ); + this.validateResize = ({ rectangle }) => + resizeHelper.validateResize({ rectangle }); + this.cdr.markForCheck(); + } + /** * @hidden */ - resizeStarted( - weekEventsContainer: HTMLElement, - weekEvent: WeekViewEvent, + timeEventResizeStarted( + eventsContainer: HTMLElement, + timeEvent: DayViewEvent, resizeEvent: ResizeEvent ): void { - this.currentResizes.set(weekEvent, { - originalOffset: weekEvent.offset, - originalSpan: weekEvent.span, + this.timeEventResizes.set(timeEvent.event, resizeEvent); + this.resizeStarted(eventsContainer); + } + + /** + * @hidden + */ + timeEventResizing(timeEvent: DayViewEvent, resizeEvent: ResizeEvent) { + this.timeEventResizes.set(timeEvent.event, resizeEvent); + const adjustedEvents = new Map(); + + const tempEvents = [...this.events]; + + this.timeEventResizes.forEach((lastResizeEvent, event) => { + const newEventDates = this.getTimeEventResizedDates( + event, + lastResizeEvent + ); + const adjustedEvent = { ...event, ...newEventDates }; + adjustedEvents.set(adjustedEvent, event); + const eventIndex = tempEvents.indexOf(event); + tempEvents[eventIndex] = adjustedEvent; + }); + + this.restoreOriginalEvents(tempEvents, adjustedEvents); + } + + /** + * @hidden + */ + timeEventResizeEnded(timeEvent: DayViewEvent) { + this.view = this.getWeekView(this.events); + const lastResizeEvent = this.timeEventResizes.get(timeEvent.event); + this.timeEventResizes.delete(timeEvent.event); + const newEventDates = this.getTimeEventResizedDates( + timeEvent.event, + lastResizeEvent + ); + this.eventTimesChanged.emit({ + newStart: newEventDates.start, + newEnd: newEventDates.end, + event: timeEvent.event, + type: CalendarEventTimesChangedEventType.Resize + }); + } + + /** + * @hidden + */ + allDayEventResizeStarted( + allDayEventsContainer: HTMLElement, + allDayEvent: WeekViewAllDayEvent, + resizeEvent: ResizeEvent + ): void { + this.allDayEventResizes.set(allDayEvent, { + originalOffset: allDayEvent.offset, + originalSpan: allDayEvent.span, edge: typeof resizeEvent.edges.left !== 'undefined' ? 'left' : 'right' }); - this.dayColumnWidth = this.getDayColumnWidth(weekEventsContainer); - const resizeHelper: CalendarResizeHelper = new CalendarResizeHelper( - weekEventsContainer, - this.dayColumnWidth + this.resizeStarted( + allDayEventsContainer, + this.getDayColumnWidth(allDayEventsContainer) ); - this.validateResize = ({ rectangle }) => - resizeHelper.validateResize({ rectangle }); - this.cdr.markForCheck(); } /** * @hidden */ - resizing( - weekEvent: WeekViewEvent, + allDayEventResizing( + allDayEvent: WeekViewAllDayEvent, resizeEvent: ResizeEvent, dayWidth: number ): void { - const currentResize: WeekViewEventResize = this.currentResizes.get( - weekEvent + const currentResize: WeekViewAllDayEventResize = this.allDayEventResizes.get( + allDayEvent ); if (resizeEvent.edges.left) { const diff: number = Math.round(+resizeEvent.edges.left / dayWidth); - weekEvent.offset = currentResize.originalOffset + diff; - weekEvent.span = currentResize.originalSpan - diff; + allDayEvent.offset = currentResize.originalOffset + diff; + allDayEvent.span = currentResize.originalSpan - diff; } else if (resizeEvent.edges.right) { const diff: number = Math.round(+resizeEvent.edges.right / dayWidth); - weekEvent.span = currentResize.originalSpan + diff; + allDayEvent.span = currentResize.originalSpan + diff; } } /** * @hidden */ - resizeEnded(weekEvent: WeekViewEvent): void { - const currentResize: WeekViewEventResize = this.currentResizes.get( - weekEvent + allDayEventResizeEnded(allDayEvent: WeekViewAllDayEvent): void { + const currentResize: WeekViewAllDayEventResize = this.allDayEventResizes.get( + allDayEvent ); - const resizingBeforeStart = currentResize.edge === 'left'; + const allDayEventResizingBeforeStart = currentResize.edge === 'left'; let daysDiff: number; - if (resizingBeforeStart) { - daysDiff = weekEvent.offset - currentResize.originalOffset; + if (allDayEventResizingBeforeStart) { + daysDiff = allDayEvent.offset - currentResize.originalOffset; } else { - daysDiff = weekEvent.span - currentResize.originalSpan; + daysDiff = allDayEvent.span - currentResize.originalSpan; } - weekEvent.offset = currentResize.originalOffset; - weekEvent.span = currentResize.originalSpan; + allDayEvent.offset = currentResize.originalOffset; + allDayEvent.span = currentResize.originalSpan; - let newStart: Date = weekEvent.event.start; - let newEnd: Date = weekEvent.event.end || weekEvent.event.start; - if (resizingBeforeStart) { + let newStart: Date = allDayEvent.event.start; + let newEnd: Date = allDayEvent.event.end || allDayEvent.event.start; + if (allDayEventResizingBeforeStart) { newStart = this.dateAdapter.addDays(newStart, daysDiff); } else { newEnd = this.dateAdapter.addDays(newEnd, daysDiff); @@ -412,10 +707,10 @@ export class CalendarWeekViewComponent implements OnChanges, OnInit, OnDestroy { this.eventTimesChanged.emit({ newStart, newEnd, - event: weekEvent.event, + event: allDayEvent.event, type: CalendarEventTimesChangedEventType.Resize }); - this.currentResizes.delete(weekEvent); + this.allDayEventResizes.delete(allDayEvent); } /** @@ -428,42 +723,116 @@ export class CalendarWeekViewComponent implements OnChanges, OnInit, OnDestroy { /** * @hidden */ - dragStarted(weekEventsContainer: HTMLElement, event: HTMLElement): void { - this.dayColumnWidth = this.getDayColumnWidth(weekEventsContainer); + eventDropped( + dropEvent: DropEvent<{ event?: CalendarEvent }>, + date: Date, + allDay: boolean + ): void { + if ( + dropEvent.dropData && + dropEvent.dropData.event && + (this.events.indexOf(dropEvent.dropData.event) === -1 || + (dropEvent.dropData.event.allDay && !allDay) || + (!dropEvent.dropData.event.allDay && allDay)) + ) { + this.eventTimesChanged.emit({ + type: CalendarEventTimesChangedEventType.Drop, + event: dropEvent.dropData.event, + newStart: date, + allDay + }); + } + } + + /** + * @hidden + */ + dragStarted( + eventsContainer: HTMLElement, + event: HTMLElement, + dayEvent?: DayViewEvent + ): void { + this.dayColumnWidth = this.getDayColumnWidth(eventsContainer); const dragHelper: CalendarDragHelper = new CalendarDragHelper( - weekEventsContainer, + eventsContainer, event ); this.validateDrag = ({ x, y }) => - this.currentResizes.size === 0 && dragHelper.validateDrag({ x, y }); - this.eventDroppedWithinContainer = false; + this.allDayEventResizes.size === 0 && + this.timeEventResizes.size === 0 && + dragHelper.validateDrag({ x, y }); + this.dragActive = true; + this.eventDragEnter = 0; + if (!this.snapDraggedEvents && dayEvent) { + this.view.hourColumns.forEach(column => { + const linkedEvent = column.events.find( + columnEvent => + columnEvent.event === dayEvent.event && columnEvent !== dayEvent + ); + // hide any linked events while dragging + if (linkedEvent) { + linkedEvent.width = 0; + linkedEvent.height = 0; + } + }); + } this.cdr.markForCheck(); } + /** + * @hidden + */ + dragMove(dayEvent: DayViewEvent, dragEvent: DragMoveEvent) { + if (this.snapDraggedEvents) { + const newEventTimes = this.getDragMovedEventTimes( + dayEvent, + dragEvent, + this.dayColumnWidth, + true + ); + const originalEvent = dayEvent.event; + const adjustedEvent = { ...originalEvent, ...newEventTimes }; + const tempEvents = this.events.map(event => { + if (event === originalEvent) { + return adjustedEvent; + } + return event; + }); + this.restoreOriginalEvents( + tempEvents, + new Map([[adjustedEvent, originalEvent]]) + ); + } + } + /** * @hidden */ dragEnded( - weekEvent: WeekViewEvent, + weekEvent: WeekViewAllDayEvent | DayViewEvent, dragEndEvent: DragEndEvent, - dayWidth: number + dayWidth: number, + useY = false ): void { - if (this.eventDroppedWithinContainer) { - const daysDragged = roundToNearest(dragEndEvent.x, dayWidth) / dayWidth; - const newStart = this.dateAdapter.addDays( - weekEvent.event.start, - daysDragged - ); - let newEnd: Date; - if (weekEvent.event.end) { - newEnd = this.dateAdapter.addDays(weekEvent.event.end, daysDragged); - } - + this.view = this.getWeekView(this.events); + this.dragActive = false; + const { start, end } = this.getDragMovedEventTimes( + weekEvent, + dragEndEvent, + dayWidth, + useY + ); + if ( + this.eventDragEnter > 0 && + start >= this.view.period.start && + (end || start) <= this.view.period.end + ) { this.eventTimesChanged.emit({ - newStart, - newEnd, + newStart: start, + newEnd: end, event: weekEvent.event, - type: CalendarEventTimesChangedEventType.Drag + type: CalendarEventTimesChangedEventType.Drag, + allDay: !useY }); } } @@ -479,14 +848,7 @@ export class CalendarWeekViewComponent implements OnChanges, OnInit, OnDestroy { } private refreshBody(): void { - this.view = this.utils.getWeekView({ - events: this.events, - viewDate: this.viewDate, - weekStartsOn: this.weekStartsOn, - excluded: this.excludeDays, - precision: this.precision, - absolutePositionedEvents: true - }); + this.view = this.getWeekView(this.events); this.emitBeforeViewRender(); } @@ -503,4 +865,179 @@ export class CalendarWeekViewComponent implements OnChanges, OnInit, OnDestroy { }); } } + + private getWeekView(events: CalendarEvent[]) { + return this.utils.getWeekView({ + events, + viewDate: this.viewDate, + weekStartsOn: this.weekStartsOn, + excluded: this.excludeDays, + precision: this.precision, + absolutePositionedEvents: true, + hourSegments: this.hourSegments, + dayStart: { + hour: this.dayStartHour, + minute: this.dayStartMinute + }, + dayEnd: { + hour: this.dayEndHour, + minute: this.dayEndMinute + }, + segmentHeight: this.hourSegmentHeight, + weekendDays: this.weekendDays + }); + } + + private getDragMovedEventTimes( + weekEvent: WeekViewAllDayEvent | DayViewEvent, + dragEndEvent: DragEndEvent | DragMoveEvent, + dayWidth: number, + useY: boolean + ) { + const daysDragged = roundToNearest(dragEndEvent.x, dayWidth) / dayWidth; + const minutesMoved = useY + ? getMinutesMoved( + dragEndEvent.y, + this.hourSegments, + this.hourSegmentHeight, + this.eventSnapSize + ) + : 0; + + const start = this.dateAdapter.addMinutes( + this.dateAdapter.addDays(weekEvent.event.start, daysDragged), + minutesMoved + ); + let end: Date; + if (weekEvent.event.end) { + end = this.dateAdapter.addMinutes( + this.dateAdapter.addDays(weekEvent.event.end, daysDragged), + minutesMoved + ); + } + + return { start, end }; + } + + private restoreOriginalEvents( + tempEvents: CalendarEvent[], + adjustedEvents: Map + ) { + this.view = this.getWeekView(tempEvents); + const adjustedEventsArray = tempEvents.filter(event => + adjustedEvents.has(event) + ); + this.view.hourColumns.forEach(column => { + adjustedEventsArray.forEach(adjustedEvent => { + const originalEvent = adjustedEvents.get(adjustedEvent); + const existingColumnEvent = column.events.find( + columnEvent => columnEvent.event === adjustedEvent + ); + if (existingColumnEvent) { + // restore the original event so trackBy kicks in and the dom isn't changed + existingColumnEvent.event = originalEvent; + } else { + // add a dummy event to the drop so if the event was removed from the original column the drag doesn't end early + column.events.push({ + event: originalEvent, + left: 0, + top: 0, + height: 0, + width: 0, + startsBeforeDay: false, + endsAfterDay: false + }); + } + }); + }); + adjustedEvents.clear(); + } + + private getTimeEventResizedDates( + calendarEvent: CalendarEvent, + resizeEvent: ResizeEvent + ) { + const minimumEventHeight = getMinimumEventHeightInMinutes( + this.hourSegments, + this.hourSegmentHeight + ); + const newEventDates = { + start: calendarEvent.start, + end: getDefaultEventEnd( + this.dateAdapter, + calendarEvent, + minimumEventHeight + ) + }; + const { end, ...eventWithoutEnd } = calendarEvent; + const smallestResizes = { + start: this.dateAdapter.addMinutes( + newEventDates.end, + minimumEventHeight * -1 + ), + end: getDefaultEventEnd( + this.dateAdapter, + eventWithoutEnd, + minimumEventHeight + ) + }; + + if (resizeEvent.edges.left) { + const daysDiff = Math.round( + +resizeEvent.edges.left / this.dayColumnWidth + ); + const newStart = this.dateAdapter.addDays(newEventDates.start, daysDiff); + if (newStart < smallestResizes.start) { + newEventDates.start = newStart; + } else { + newEventDates.start = smallestResizes.start; + } + } else if (resizeEvent.edges.right) { + const daysDiff = Math.round( + +resizeEvent.edges.right / this.dayColumnWidth + ); + const newEnd = this.dateAdapter.addDays(newEventDates.end, daysDiff); + if (newEnd > smallestResizes.end) { + newEventDates.end = newEnd; + } else { + newEventDates.end = smallestResizes.end; + } + } + + if (resizeEvent.edges.top) { + const minutesMoved = getMinutesMoved( + resizeEvent.edges.top as number, + this.hourSegments, + this.hourSegmentHeight, + this.eventSnapSize + ); + const newStart = this.dateAdapter.addMinutes( + newEventDates.start, + minutesMoved + ); + if (newStart < smallestResizes.start) { + newEventDates.start = newStart; + } else { + newEventDates.start = smallestResizes.start; + } + } else if (resizeEvent.edges.bottom) { + const minutesMoved = getMinutesMoved( + resizeEvent.edges.bottom as number, + this.hourSegments, + this.hourSegmentHeight, + this.eventSnapSize + ); + const newEnd = this.dateAdapter.addMinutes( + newEventDates.end, + minutesMoved + ); + if (newEnd > smallestResizes.end) { + newEventDates.end = newEnd; + } else { + newEventDates.end = smallestResizes.end; + } + } + + return newEventDates; + } } diff --git a/src/modules/week/calendar-week-view.scss b/src/modules/week/calendar-week-view.scss index 808fd1aa7..a62e25212 100644 --- a/src/modules/week/calendar-week-view.scss +++ b/src/modules/week/calendar-week-view.scss @@ -1,20 +1,22 @@ .cal-week-view { .cal-day-headers { display: flex; - margin-bottom: 3px; border: 1px solid #e1e1e1; - margin-left: 2px; - margin-right: 2px; + padding-left: 70px; } .cal-day-headers .cal-header { flex: 1; text-align: center; padding: 5px; - } - .cal-day-headers .cal-header:not(:last-child) { - border-right: 1px solid #e1e1e1; + &:not(:last-child) { + border-right: 1px solid #e1e1e1; + } + + &:first-child { + border-left: 1px solid #e1e1e1; + } } .cal-day-headers .cal-header:hover, @@ -27,46 +29,84 @@ opacity: 0.5; } - .cal-events-row { - position: relative; - height: 33px; - } - - .cal-event-container { - display: inline-block; - position: absolute; - cursor: pointer; - - &.resize-active { - z-index: 1; - pointer-events: none; - } + .cal-day-column { + flex-grow: 1; + border-left: solid 1px #e1e1e1; } .cal-event { - padding: 0 10px; font-size: 12px; - margin-left: 2px; - margin-right: 2px; - height: 30px; - line-height: 30px; background-color: #d1e8ff; border: 1px solid #1e90ff; color: #1e90ff; } - .cal-draggable { - cursor: move; + .cal-time-label-column { + width: 70px; + height: 100%; } - .cal-starts-within-week .cal-event { - border-top-left-radius: 5px; - border-bottom-left-radius: 5px; + .cal-all-day-events { + border: solid 1px #e1e1e1; + border-top: 0; + border-bottom-width: 3px; + padding-top: 3px; + position: relative; + + .cal-day-columns { + height: 100%; + width: 100%; + display: flex; + position: absolute; + top: 0; + z-index: 0; + } + + .cal-events-row { + position: relative; + height: 31px; + margin-left: 70px; + } + + .cal-event-container { + display: inline-block; + position: absolute; + cursor: pointer; + + &.resize-active { + z-index: 1; + pointer-events: none; + } + } + + .cal-event { + padding: 0 5px; + margin-left: 2px; + margin-right: 2px; + height: 28px; + line-height: 28px; + } + + .cal-starts-within-week .cal-event { + border-top-left-radius: 5px; + border-bottom-left-radius: 5px; + } + + .cal-ends-within-week .cal-event { + border-top-right-radius: 5px; + border-bottom-right-radius: 5px; + } + + .cal-time-label-column { + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + } } - .cal-ends-within-week .cal-event { - border-top-right-radius: 5px; - border-bottom-right-radius: 5px; + .cal-draggable { + cursor: move; } .cal-header.cal-today { @@ -85,10 +125,107 @@ } .cal-drag-active { + pointer-events: none; z-index: 1; & * { pointer-events: none; } } + + .cal-time-events { + position: relative; + border: solid 1px #e1e1e1; + border-top: 0; + display: flex; + + .cal-day-columns { + display: flex; + flex-grow: 1; + + &:not(.cal-resize-active) { + .cal-hour-segment:hover { + background-color: #ededed; + } + } + } + + .cal-day-column { + position: relative; + } + + .cal-event-container { + position: absolute; + z-index: 1; + } + + .cal-event { + width: calc(100% - 2px); + height: calc(100% - 2px); + margin: 1px; + padding: 0 5px; + line-height: 25px; + } + } + + .cal-hour-odd { + background-color: #fafafa; + } + + .cal-hour-segment { + position: relative; + + &::after { + content: '\00a0'; + } + } + + .cal-drag-over .cal-hour-segment { + background-color: #ededed; + } + + .cal-resize-handle { + width: 100%; + height: 3px; + cursor: row-resize; + position: absolute; + + &.cal-resize-handle-after-end { + margin-top: -3px; + } + } + + /* stylelint-disable-next-line selector-type-no-unknown */ + mwl-calendar-week-view-hour-segment, /* fix for https://github.com/mattlewis92/angular-calendar/issues/260*/ + .cal-hour-segment { + display: block; + } + + .cal-hour:not(:last-child) .cal-hour-segment, + .cal-hour:last-child :not(:last-child) .cal-hour-segment { + border-bottom: thin dashed #e1e1e1; + } + + .cal-time { + font-weight: bold; + padding-top: 5px; + width: 70px; + text-align: center; + } + + .cal-hour-segment.cal-after-hour-start { + .cal-time { + display: none; + } + } + + .cal-starts-within-day .cal-event { + border-top-left-radius: 5px; + border-top-right-radius: 5px; + } + + .cal-ends-within-day .cal-event { + border-bottom-left-radius: 5px; + border-bottom-right-radius: 5px; + } } diff --git a/src/modules/week/calendar-week.module.ts b/src/modules/week/calendar-week.module.ts index acce919c9..a615bf030 100644 --- a/src/modules/week/calendar-week.module.ts +++ b/src/modules/week/calendar-week.module.ts @@ -6,14 +6,15 @@ import { CalendarWeekViewComponent } from './calendar-week-view.component'; import { CalendarWeekViewHeaderComponent } from './calendar-week-view-header.component'; import { CalendarWeekViewEventComponent } from './calendar-week-view-event.component'; import { CalendarCommonModule } from '../common/calendar-common.module'; +import { CalendarWeekViewHourSegmentComponent } from './calendar-week-view-hour-segment.component'; export { CalendarWeekViewComponent, CalendarWeekViewBeforeRenderEvent } from './calendar-week-view.component'; export { - WeekViewEvent as CalendarWeekViewEvent, - WeekViewEventRow as CalendarWeekViewEventRow, + WeekViewAllDayEvent as CalendarWeekViewAllDayEvent, + WeekViewAllDayEventRow as CalendarWeekViewAllDayEventRow, GetWeekViewArgs as CalendarGetWeekViewArgs } from 'calendar-utils'; @@ -27,14 +28,16 @@ export { declarations: [ CalendarWeekViewComponent, CalendarWeekViewHeaderComponent, - CalendarWeekViewEventComponent + CalendarWeekViewEventComponent, + CalendarWeekViewHourSegmentComponent ], exports: [ ResizableModule, DragAndDropModule, CalendarWeekViewComponent, CalendarWeekViewHeaderComponent, - CalendarWeekViewEventComponent + CalendarWeekViewEventComponent, + CalendarWeekViewHourSegmentComponent ] }) export class CalendarWeekModule {} diff --git a/test/calendar-angular-date-formatter.provider.spec.ts b/test/calendar-angular-date-formatter.provider.spec.ts index 140b553b6..29266905a 100644 --- a/test/calendar-angular-date-formatter.provider.spec.ts +++ b/test/calendar-angular-date-formatter.provider.spec.ts @@ -76,6 +76,15 @@ describe('CalendarAngularDateFormatter provider', () => { ).to.equal('Week 1 of 2016'); }); + it('weekViewHour', () => { + expect( + dateFormatter.weekViewHour({ + date: startOfDay(new Date('2016-01-01')), + locale: 'en' + }) + ).to.equal('12 AM'); + }); + it('dayViewHour', () => { expect( dateFormatter.dayViewHour({ diff --git a/test/calendar-day-view.component.spec.ts b/test/calendar-day-view.component.spec.ts index e482b9cfb..8e4bad30d 100644 --- a/test/calendar-day-view.component.spec.ts +++ b/test/calendar-day-view.component.spec.ts @@ -881,7 +881,7 @@ describe('CalendarDayViewComponent component', () => { clientX: eventPosition.left + 10 }); fixture.detectChanges(); - expect(eventDropped).to.have.been.calledOnce; + expect(eventDropped).not.to.have.been.called; fixture.destroy(); }); diff --git a/test/calendar-moment-date-formatter.provider.spec.ts b/test/calendar-moment-date-formatter.provider.spec.ts index 0c3a12c21..83fcd09c5 100644 --- a/test/calendar-moment-date-formatter.provider.spec.ts +++ b/test/calendar-moment-date-formatter.provider.spec.ts @@ -73,6 +73,15 @@ describe('calendarMomentDateFormatter provider', () => { ).to.equal('Week 1 of 2016'); }); + it('weekViewHour', () => { + expect( + dateFormatter.weekViewHour({ + date: startOfDay(new Date('2016-01-01')), + locale: 'en' + }) + ).to.equal('12am'); + }); + it('dayViewHour', () => { expect( dateFormatter.dayViewHour({ diff --git a/test/calendar-native-date-formatter.provider.spec.ts b/test/calendar-native-date-formatter.provider.spec.ts index a700906c0..14554ce7a 100644 --- a/test/calendar-native-date-formatter.provider.spec.ts +++ b/test/calendar-native-date-formatter.provider.spec.ts @@ -76,6 +76,15 @@ describe('calendarNativeDateFormatter provider', () => { ).to.equal('Week 1 of 2016'); }); + it('weekViewHour', () => { + expect( + dateFormatter.weekViewHour({ + date: startOfDay(new Date('2016-01-01')), + locale: 'en' + }) + ).to.equal('12 AM'); + }); + it('dayViewHour', () => { expect( dateFormatter.dayViewHour({ diff --git a/test/calendar-week-view.component.spec.ts b/test/calendar-week-view.component.spec.ts index acca09ab7..718a2f070 100644 --- a/test/calendar-week-view.component.spec.ts +++ b/test/calendar-week-view.component.spec.ts @@ -211,20 +211,17 @@ describe('calendarWeekView component', () => { start: new Date('2016-06-01'), end: new Date('2016-06-02'), title: 'foo', - color: { - primary: 'blue', - secondary: 'lightblue' - } + allDay: true }; fixture.componentInstance.events.push(event); fixture.componentInstance.refresh.next(true); expect( - fixture.componentInstance.view.eventRows[0].row[0].event + fixture.componentInstance.view.allDayEventRows[0].row[0].event ).to.deep.equal(event); fixture.destroy(); }); - it('should allow the event title to be customised by the calendarConfig provider', () => { + it('should allow the event title to be customised', () => { const fixture: ComponentFixture< CalendarWeekViewComponent > = TestBed.createComponent(CalendarWeekViewComponent); @@ -353,7 +350,7 @@ describe('calendarWeekView component', () => { fixture.destroy(); }); - it('should resize the event by dragging from the left edge', () => { + it('should resize the all day event by dragging from the left edge', () => { const fixture: ComponentFixture< CalendarWeekViewComponent > = TestBed.createComponent(CalendarWeekViewComponent); @@ -370,7 +367,8 @@ describe('calendarWeekView component', () => { .toDate(), resizable: { beforeStart: true - } + }, + allDay: true } ]; fixture.componentInstance.ngOnChanges({ viewDate: {}, events: {} }); @@ -420,7 +418,7 @@ describe('calendarWeekView component', () => { }); }); - it('should resize the event by dragging from the right edge', () => { + it('should resize the all day event by dragging from the right edge', () => { const fixture: ComponentFixture< CalendarWeekViewComponent > = TestBed.createComponent(CalendarWeekViewComponent); @@ -437,7 +435,8 @@ describe('calendarWeekView component', () => { .toDate(), resizable: { afterEnd: true - } + }, + allDay: true } ]; fixture.componentInstance.ngOnChanges({ viewDate: {}, events: {} }); @@ -487,7 +486,7 @@ describe('calendarWeekView component', () => { }); }); - it('should resize events with no end date', () => { + it('should resize all day events with no end date', () => { const fixture: ComponentFixture< CalendarWeekViewComponent > = TestBed.createComponent(CalendarWeekViewComponent); @@ -501,7 +500,8 @@ describe('calendarWeekView component', () => { .toDate(), resizable: { afterEnd: true - } + }, + allDay: true } ]; fixture.componentInstance.ngOnChanges({ viewDate: {}, events: {} }); @@ -551,7 +551,7 @@ describe('calendarWeekView component', () => { }); }); - it('should allow 2 events next to each other to be resized at the same time', () => { + it('should allow 2 all day events next to each other to be resized at the same time', () => { const fixture: ComponentFixture< CalendarWeekViewComponent > = TestBed.createComponent(CalendarWeekViewComponent); @@ -569,7 +569,8 @@ describe('calendarWeekView component', () => { resizable: { beforeStart: true, afterEnd: true - } + }, + allDay: true }, { title: 'event 2', @@ -583,7 +584,8 @@ describe('calendarWeekView component', () => { resizable: { beforeStart: true, afterEnd: true - } + }, + allDay: true } ]; fixture.componentInstance.ngOnChanges({ viewDate: {}, events: {} }); @@ -654,7 +656,7 @@ describe('calendarWeekView component', () => { }); }); - it('should allow the event to be dragged and dropped', () => { + it('should allow the all day event to be dragged and dropped', () => { const fixture: ComponentFixture< CalendarWeekViewComponent > = TestBed.createComponent(CalendarWeekViewComponent); @@ -669,7 +671,8 @@ describe('calendarWeekView component', () => { end: moment('2016-12-08') .add(6, 'hours') .toDate(), - draggable: true + draggable: true, + allDay: true } ]; fixture.componentInstance.ngOnChanges({ viewDate: {}, events: {} }); @@ -699,7 +702,7 @@ describe('calendarWeekView component', () => { fixture.detectChanges(); const ghostElement = event.nextSibling as HTMLElement; expect(Math.round(ghostElement.getBoundingClientRect().left)).to.equal( - Math.round(eventPosition.left - dayWidth) + Math.round(eventPosition.left - dayWidth) + 1 ); triggerDomEvent('mouseup', document.body, { clientX: eventPosition.left - dayWidth, @@ -715,12 +718,13 @@ describe('calendarWeekView component', () => { .toDate(), newEnd: moment('2016-12-07') .add(6, 'hours') - .toDate() + .toDate(), + allDay: true }); expect(eventDropped).to.have.been.calledOnce; }); - it('should allow events to be dragged outside of the calendar', () => { + it('should allow all day events to be dragged outside of the calendar', () => { const fixture: ComponentFixture< CalendarWeekViewComponent > = TestBed.createComponent(CalendarWeekViewComponent); @@ -735,7 +739,8 @@ describe('calendarWeekView component', () => { end: moment('2016-12-08') .add(6, 'hours') .toDate(), - draggable: true + draggable: true, + allDay: true } ]; fixture.componentInstance.snapDraggedEvents = false; @@ -782,7 +787,7 @@ describe('calendarWeekView component', () => { expect(eventDropped).not.to.have.been.called; }); - it('should round event drag sizes to the event snap size when dragging and dropping non snapped events', () => { + it('should round all day event drag sizes to the event snap size when dragging and dropping non snapped events', () => { const fixture: ComponentFixture< CalendarWeekViewComponent > = TestBed.createComponent(CalendarWeekViewComponent); @@ -797,7 +802,8 @@ describe('calendarWeekView component', () => { end: moment('2016-12-08') .add(6, 'hours') .toDate(), - draggable: true + draggable: true, + allDay: true } ]; fixture.componentInstance.snapDraggedEvents = false; @@ -820,7 +826,7 @@ describe('calendarWeekView component', () => { clientY: eventPosition.top }); fixture.detectChanges(); - const dragLeft = event.parentElement.offsetWidth / 7 + 50; + const dragLeft = event.parentElement.offsetWidth / 7; triggerDomEvent('mousemove', document.body, { clientX: eventPosition.left - dragLeft, clientY: eventPosition.top @@ -840,12 +846,13 @@ describe('calendarWeekView component', () => { .toDate(), newEnd: moment('2016-12-07') .add(6, 'hours') - .toDate() + .toDate(), + allDay: true }); expect(eventDropped).to.have.been.calledOnce; }); - it('should not allow events to be resized smaller than 1 day', () => { + it('should not allow all day events to be resized smaller than 1 day', () => { const fixture: ComponentFixture< CalendarWeekViewComponent > = TestBed.createComponent(CalendarWeekViewComponent); @@ -862,7 +869,8 @@ describe('calendarWeekView component', () => { .toDate(), resizable: { beforeStart: true - } + }, + allDay: true } ]; fixture.componentInstance.ngOnChanges({ viewDate: {}, events: {} }); @@ -897,7 +905,7 @@ describe('calendarWeekView component', () => { fixture.destroy(); }); - it('should not allow events to be resized outside of the current view', () => { + it('should not allow all day events to be resized outside of the current view', () => { const fixture: ComponentFixture< CalendarWeekViewComponent > = TestBed.createComponent(CalendarWeekViewComponent); @@ -914,7 +922,8 @@ describe('calendarWeekView component', () => { .toDate(), resizable: { beforeStart: true - } + }, + allDay: true } ]; fixture.componentInstance.ngOnChanges({ viewDate: {}, events: {} }); @@ -1012,7 +1021,8 @@ describe('calendarWeekView component', () => { newStart: moment('2016-06-27') .startOf('week') .add(2, 'days') - .toDate() + .toDate(), + allDay: true }); }); @@ -1123,10 +1133,6 @@ describe('calendarWeekView component', () => { start: new Date('2016-06-26'), end: new Date('2016-06-28'), title: 'foo', - color: { - primary: 'blue', - secondary: 'rgb(238, 238, 238)' - }, actions: [ { label: '', @@ -1149,4 +1155,784 @@ describe('calendarWeekView component', () => { ).to.have.been.calledWith({ event: fixture.componentInstance.events[0] }); expect(eventClicked).not.to.have.been.called; }); + + it('should resize a time event from the end to another day on the right', () => { + const fixture: ComponentFixture< + CalendarWeekViewComponent + > = TestBed.createComponent(CalendarWeekViewComponent); + fixture.componentInstance.viewDate = new Date('2018-07-29'); + fixture.componentInstance.events = [ + { + start: moment(new Date('2018-07-29')) + .startOf('day') + .add(3, 'hours') + .toDate(), + end: moment(new Date('2018-07-29')) + .startOf('day') + .add(18, 'hours') + .toDate(), + title: 'foo', + resizable: { + afterEnd: true + } + } + ]; + fixture.componentInstance.ngOnChanges({ viewDate: {}, events: {} }); + fixture.detectChanges(); + document.body.appendChild(fixture.nativeElement); + const event: HTMLElement = fixture.nativeElement.querySelector( + '.cal-event-container' + ); + const dayWidth: number = event.parentElement.offsetWidth; + const rect: ClientRect = event.getBoundingClientRect(); + const resizeHandle = event.querySelector('.cal-resize-handle-after-end'); + let resizeEvent: CalendarEventTimesChangedEvent; + fixture.componentInstance.eventTimesChanged.subscribe(e => { + resizeEvent = e; + }); + triggerDomEvent('mousedown', resizeHandle, { + clientX: rect.right, + clientY: rect.bottom + }); + fixture.detectChanges(); + triggerDomEvent('mousemove', document.body, { + clientX: rect.right + dayWidth, + clientY: rect.bottom + 90 + }); + fixture.detectChanges(); + expect(event.getBoundingClientRect().top).to.equal(rect.top); + expect(event.getBoundingClientRect().height).to.equal(1259); + const events = fixture.nativeElement.querySelectorAll( + '.cal-event-container' + ); + expect(events[0]).to.equal(event); + expect(events[1].getBoundingClientRect().top).to.equal(rect.top - 180); + expect(events[1].getBoundingClientRect().height).to.equal(1170); + triggerDomEvent('mouseup', document.body, { + clientX: rect.right + dayWidth, + clientY: rect.bottom + 90 + }); + fixture.detectChanges(); + fixture.destroy(); + expect(resizeEvent).to.deep.equal({ + type: 'resize', + event: fixture.componentInstance.events[0], + newStart: fixture.componentInstance.events[0].start, + newEnd: moment(new Date('2018-07-29')) + .startOf('day') + .add(1, 'day') + .add(19, 'hours') + .add(30, 'minutes') + .toDate() + }); + }); + + it('should resize a time event from the end to another day on the left', () => { + const fixture: ComponentFixture< + CalendarWeekViewComponent + > = TestBed.createComponent(CalendarWeekViewComponent); + fixture.componentInstance.viewDate = new Date('2018-07-29'); + fixture.componentInstance.events = [ + { + start: moment(new Date('2018-07-29')) + .startOf('day') + .add(3, 'hours') + .toDate(), + end: moment(new Date('2018-07-29')) + .startOf('day') + .add(1, 'day') + .add(19, 'hours') + .add(30, 'minutes') + .toDate(), + title: 'foo', + resizable: { + afterEnd: true + } + } + ]; + fixture.componentInstance.ngOnChanges({ viewDate: {}, events: {} }); + fixture.detectChanges(); + document.body.appendChild(fixture.nativeElement); + const event: HTMLElement = fixture.nativeElement.querySelectorAll( + '.cal-event-container' + )[1]; + const dayWidth: number = event.parentElement.offsetWidth; + const rect: ClientRect = event.getBoundingClientRect(); + const resizeHandle = event.querySelector('.cal-resize-handle-after-end'); + let resizeEvent: CalendarEventTimesChangedEvent; + fixture.componentInstance.eventTimesChanged.subscribe(e => { + resizeEvent = e; + }); + triggerDomEvent('mousedown', resizeHandle, { + clientX: rect.right, + clientY: rect.bottom + }); + fixture.detectChanges(); + triggerDomEvent('mousemove', document.body, { + clientX: rect.right - dayWidth, + clientY: rect.bottom - 90 + }); + fixture.detectChanges(); + expect(event.getBoundingClientRect().width).to.equal(0); + expect(event.getBoundingClientRect().height).to.equal(0); + const events = fixture.nativeElement.querySelectorAll( + '.cal-event-container' + ); + expect(events[1]).to.equal(event); + expect(events[0].getBoundingClientRect().top).to.equal(rect.top + 180); + expect(events[0].getBoundingClientRect().height).to.equal(900); + triggerDomEvent('mouseup', document.body, { + clientX: rect.right - dayWidth, + clientY: rect.bottom - 90 + }); + fixture.detectChanges(); + fixture.destroy(); + expect(resizeEvent).to.deep.equal({ + type: 'resize', + event: fixture.componentInstance.events[0], + newStart: fixture.componentInstance.events[0].start, + newEnd: moment(new Date('2018-07-29')) + .startOf('day') + .add(18, 'hours') + .toDate() + }); + }); + + it('should resize a time event from the end and not allow it to end before it starts', () => { + const fixture: ComponentFixture< + CalendarWeekViewComponent + > = TestBed.createComponent(CalendarWeekViewComponent); + fixture.componentInstance.viewDate = new Date('2018-07-29'); + fixture.componentInstance.events = [ + { + start: moment(new Date('2018-07-29')) + .startOf('day') + .add(3, 'hours') + .toDate(), + end: moment(new Date('2018-07-29')) + .startOf('day') + .add(1, 'day') + .add(19, 'hours') + .add(30, 'minutes') + .toDate(), + title: 'foo', + resizable: { + afterEnd: true + } + } + ]; + fixture.componentInstance.ngOnChanges({ viewDate: {}, events: {} }); + fixture.detectChanges(); + document.body.appendChild(fixture.nativeElement); + const event: HTMLElement = fixture.nativeElement.querySelectorAll( + '.cal-event-container' + )[1]; + const dayWidth: number = event.parentElement.offsetWidth; + const rect: ClientRect = event.getBoundingClientRect(); + const resizeHandle = event.querySelector('.cal-resize-handle-after-end'); + let resizeEvent: CalendarEventTimesChangedEvent; + fixture.componentInstance.eventTimesChanged.subscribe(e => { + resizeEvent = e; + }); + triggerDomEvent('mousedown', resizeHandle, { + clientX: rect.right, + clientY: rect.bottom + }); + fixture.detectChanges(); + triggerDomEvent('mousemove', document.body, { + clientX: rect.right - dayWidth * 2, + clientY: rect.bottom - 1000 + }); + fixture.detectChanges(); + expect(event.getBoundingClientRect().width).to.equal(0); + expect(event.getBoundingClientRect().height).to.equal(0); + const events = fixture.nativeElement.querySelectorAll( + '.cal-event-container' + ); + expect(events[1]).to.equal(event); + expect(events[0].getBoundingClientRect().top).to.equal(rect.top + 180); + expect(events[0].getBoundingClientRect().height).to.equal(30); + triggerDomEvent('mouseup', document.body, { + clientX: rect.right - dayWidth * 2, + clientY: rect.bottom - 1000 + }); + fixture.detectChanges(); + fixture.destroy(); + expect(resizeEvent).to.deep.equal({ + type: 'resize', + event: fixture.componentInstance.events[0], + newStart: fixture.componentInstance.events[0].start, + newEnd: moment(fixture.componentInstance.events[0].start) + .add(30, 'minutes') + .toDate() + }); + }); + + it('should resize a time event from the start to another day on the left', () => { + const fixture: ComponentFixture< + CalendarWeekViewComponent + > = TestBed.createComponent(CalendarWeekViewComponent); + fixture.componentInstance.viewDate = new Date('2018-07-30'); + fixture.componentInstance.events = [ + { + start: moment(new Date('2018-07-30')) + .startOf('day') + .add(3, 'hours') + .toDate(), + end: moment(new Date('2018-07-30')) + .startOf('day') + .add(18, 'hours') + .toDate(), + title: 'foo', + resizable: { + beforeStart: true + } + } + ]; + fixture.componentInstance.ngOnChanges({ viewDate: {}, events: {} }); + fixture.detectChanges(); + document.body.appendChild(fixture.nativeElement); + const event: HTMLElement = fixture.nativeElement.querySelector( + '.cal-event-container' + ); + const dayWidth: number = event.parentElement.offsetWidth; + const rect: ClientRect = event.getBoundingClientRect(); + const resizeHandle = event.querySelector('.cal-resize-handle-before-start'); + let resizeEvent: CalendarEventTimesChangedEvent; + fixture.componentInstance.eventTimesChanged.subscribe(e => { + resizeEvent = e; + }); + triggerDomEvent('mousedown', resizeHandle, { + clientX: rect.left, + clientY: rect.top + }); + fixture.detectChanges(); + triggerDomEvent('mousemove', document.body, { + clientX: rect.left - dayWidth, + clientY: rect.top - 90 + }); + fixture.detectChanges(); + expect(event.getBoundingClientRect().top).to.equal(rect.top - 180); + expect(event.getBoundingClientRect().height).to.equal(rect.height + 180); + const events = fixture.nativeElement.querySelectorAll( + '.cal-event-container' + ); + expect(events[1]).to.equal(event); + expect(events[0].getBoundingClientRect().top).to.equal(rect.top - 90); + expect(events[0].getBoundingClientRect().height).to.equal(1349); + triggerDomEvent('mouseup', document.body, { + clientX: rect.left - dayWidth, + clientY: rect.top - 90 + }); + fixture.detectChanges(); + fixture.destroy(); + expect(resizeEvent).to.deep.equal({ + type: 'resize', + event: fixture.componentInstance.events[0], + newStart: moment(fixture.componentInstance.events[0].start) + .subtract(1, 'days') + .subtract(90, 'minutes') + .toDate(), + newEnd: fixture.componentInstance.events[0].end + }); + }); + + it('should resize a time event from the start to another day on the right', () => { + const fixture: ComponentFixture< + CalendarWeekViewComponent + > = TestBed.createComponent(CalendarWeekViewComponent); + fixture.componentInstance.viewDate = new Date('2018-07-30'); + fixture.componentInstance.events = [ + { + start: moment(new Date('2018-07-30')) + .startOf('day') + .add(3, 'hours') + .subtract(1, 'days') + .subtract(90, 'minutes') + .toDate(), + end: moment(new Date('2018-07-30')) + .startOf('day') + .add(18, 'hours') + .toDate(), + title: 'foo', + resizable: { + beforeStart: true + } + } + ]; + fixture.componentInstance.ngOnChanges({ viewDate: {}, events: {} }); + fixture.detectChanges(); + document.body.appendChild(fixture.nativeElement); + const event: HTMLElement = fixture.nativeElement.querySelector( + '.cal-event-container' + ); + const dayWidth: number = event.parentElement.offsetWidth; + const rect: ClientRect = event.getBoundingClientRect(); + const resizeHandle = event.querySelector('.cal-resize-handle-before-start'); + let resizeEvent: CalendarEventTimesChangedEvent; + fixture.componentInstance.eventTimesChanged.subscribe(e => { + resizeEvent = e; + }); + triggerDomEvent('mousedown', resizeHandle, { + clientX: rect.left, + clientY: rect.top + }); + fixture.detectChanges(); + triggerDomEvent('mousemove', document.body, { + clientX: rect.left + dayWidth, + clientY: rect.top + 90 + }); + fixture.detectChanges(); + expect(event.getBoundingClientRect().width).to.equal(0); + expect(event.getBoundingClientRect().height).to.equal(0); + const events = fixture.nativeElement.querySelectorAll( + '.cal-event-container' + ); + expect(events[0]).to.equal(event); + expect(events[1].getBoundingClientRect().top).to.equal(rect.top + 90); + expect(events[1].getBoundingClientRect().height).to.equal(900); + triggerDomEvent('mouseup', document.body, { + clientX: rect.left + dayWidth, + clientY: rect.top + 90 + }); + fixture.detectChanges(); + fixture.destroy(); + expect(resizeEvent).to.deep.equal({ + type: 'resize', + event: fixture.componentInstance.events[0], + newStart: moment(new Date('2018-07-30')) + .startOf('day') + .add(3, 'hours') + .toDate(), + newEnd: fixture.componentInstance.events[0].end + }); + }); + + it('should resize a time event and not allow it to start after it ends', () => { + const fixture: ComponentFixture< + CalendarWeekViewComponent + > = TestBed.createComponent(CalendarWeekViewComponent); + fixture.componentInstance.viewDate = new Date('2018-07-30'); + fixture.componentInstance.events = [ + { + start: moment(new Date('2018-07-30')) + .startOf('day') + .add(3, 'hours') + .subtract(1, 'days') + .subtract(90, 'minutes') + .toDate(), + end: moment(new Date('2018-07-30')) + .startOf('day') + .add(18, 'hours') + .toDate(), + title: 'foo', + resizable: { + beforeStart: true + } + } + ]; + fixture.componentInstance.ngOnChanges({ viewDate: {}, events: {} }); + fixture.detectChanges(); + document.body.appendChild(fixture.nativeElement); + const event: HTMLElement = fixture.nativeElement.querySelector( + '.cal-event-container' + ); + const dayWidth: number = event.parentElement.offsetWidth; + const rect: ClientRect = event.getBoundingClientRect(); + const resizeHandle = event.querySelector('.cal-resize-handle-before-start'); + let resizeEvent: CalendarEventTimesChangedEvent; + fixture.componentInstance.eventTimesChanged.subscribe(e => { + resizeEvent = e; + }); + triggerDomEvent('mousedown', resizeHandle, { + clientX: rect.left, + clientY: rect.top + }); + fixture.detectChanges(); + triggerDomEvent('mousemove', document.body, { + clientX: rect.left + dayWidth * 2, + clientY: rect.top + 90 + }); + fixture.detectChanges(); + expect(event.getBoundingClientRect().width).to.equal(0); + expect(event.getBoundingClientRect().height).to.equal(0); + const events = fixture.nativeElement.querySelectorAll( + '.cal-event-container' + ); + expect(events[0]).to.equal(event); + expect(events[1].getBoundingClientRect().top).to.equal(1106); + expect(events[1].getBoundingClientRect().height).to.equal(30); + triggerDomEvent('mouseup', document.body, { + clientX: rect.left + dayWidth * 2, + clientY: rect.top + 90 + }); + fixture.detectChanges(); + fixture.destroy(); + expect(resizeEvent).to.deep.equal({ + type: 'resize', + event: fixture.componentInstance.events[0], + newStart: moment(fixture.componentInstance.events[0].end) + .subtract(30, 'minutes') + .toDate(), + newEnd: fixture.componentInstance.events[0].end + }); + }); + + it('should drag time events to different days and columns while snapping to a grid', () => { + const fixture: ComponentFixture< + CalendarWeekViewComponent + > = TestBed.createComponent(CalendarWeekViewComponent); + fixture.componentInstance.viewDate = new Date('2018-07-29'); + fixture.componentInstance.events = [ + { + start: moment(new Date('2018-07-29')) + .startOf('day') + .add(3, 'hours') + .toDate(), + end: moment(new Date('2018-07-29')) + .startOf('day') + .add(1, 'day') + .add(18, 'hours') + .toDate(), + title: 'foo', + draggable: true + }, + { + start: moment(new Date('2018-07-29')) + .startOf('day') + .add(3, 'days') + .toDate(), + title: 'bar', + draggable: true + } + ]; + fixture.componentInstance.ngOnChanges({ viewDate: {}, events: {} }); + fixture.detectChanges(); + document.body.appendChild(fixture.nativeElement); + const events = fixture.nativeElement.querySelectorAll( + '.cal-event-container' + ); + const dayWidth: number = events[0].parentElement.offsetWidth; + const rect1: ClientRect = events[0].getBoundingClientRect(); + const rect2: ClientRect = events[1].getBoundingClientRect(); + let dragEvent: CalendarEventTimesChangedEvent; + fixture.componentInstance.eventTimesChanged.subscribe(e => { + dragEvent = e; + }); + triggerDomEvent('mousedown', events[1], { + clientX: rect2.right, + clientY: rect2.bottom + }); + fixture.detectChanges(); + triggerDomEvent('mousemove', events[1], { + clientX: rect2.right + dayWidth - 5, + clientY: rect2.bottom + 95 + }); + fixture.detectChanges(); + expect(events[0].getBoundingClientRect().height).to.equal(0); + expect(events[0].getBoundingClientRect().width).to.equal(0); + const updatedEvents = fixture.nativeElement.querySelectorAll( + '.cal-event-container' + ); + expect(updatedEvents[0]).to.equal(events[0]); + expect(updatedEvents[1].getBoundingClientRect().top).to.equal( + rect1.top + 90 + ); + expect(updatedEvents[1].getBoundingClientRect().height).to.equal( + rect1.height - 90 + ); + expect(updatedEvents[2].getBoundingClientRect().top).to.equal(rect2.top); + expect(updatedEvents[2].getBoundingClientRect().height).to.equal( + rect2.height + 90 + ); + triggerDomEvent('mouseup', events[1], { + clientX: rect2.right + dayWidth - 5, + clientY: rect2.bottom + 95 + }); + fixture.detectChanges(); + fixture.destroy(); + expect(dragEvent).to.deep.equal({ + type: 'drag', + allDay: false, + event: fixture.componentInstance.events[0], + newStart: moment(fixture.componentInstance.events[0].start) + .add(1, 'day') + .add(1, 'hour') + .add(30, 'minutes') + .toDate(), + newEnd: moment(fixture.componentInstance.events[0].end) + .add(1, 'day') + .add(1, 'hour') + .add(30, 'minutes') + .toDate() + }); + }); + + it('should drag time events without end dates', () => { + const fixture: ComponentFixture< + CalendarWeekViewComponent + > = TestBed.createComponent(CalendarWeekViewComponent); + fixture.componentInstance.viewDate = new Date('2018-07-29'); + fixture.componentInstance.events = [ + { + start: moment(new Date('2018-07-29')) + .startOf('day') + .add(3, 'hours') + .toDate(), + title: 'foo', + draggable: true + } + ]; + fixture.componentInstance.ngOnChanges({ viewDate: {}, events: {} }); + fixture.detectChanges(); + document.body.appendChild(fixture.nativeElement); + const event = fixture.nativeElement.querySelector('.cal-event-container'); + const dayWidth: number = event.parentElement.offsetWidth; + const rect: ClientRect = event.getBoundingClientRect(); + let dragEvent: CalendarEventTimesChangedEvent; + fixture.componentInstance.eventTimesChanged.subscribe(e => { + dragEvent = e; + }); + triggerDomEvent('mousedown', event, { + clientX: rect.right, + clientY: rect.bottom + }); + fixture.detectChanges(); + triggerDomEvent('mousemove', event, { + clientX: rect.right + dayWidth - 5, + clientY: rect.bottom + 95 + }); + fixture.detectChanges(); + expect(event.getBoundingClientRect().height).to.equal(0); + expect(event.getBoundingClientRect().width).to.equal(0); + const updatedEvents = fixture.nativeElement.querySelectorAll( + '.cal-event-container' + ); + expect(updatedEvents[0]).to.equal(event); + expect(updatedEvents[1].getBoundingClientRect().top).to.equal( + rect.top + 90 + ); + expect(updatedEvents[1].getBoundingClientRect().height).to.equal( + rect.height + ); + triggerDomEvent('mouseup', event, { + clientX: rect.right + dayWidth - 5, + clientY: rect.bottom + 95 + }); + fixture.detectChanges(); + fixture.destroy(); + expect(dragEvent).to.deep.equal({ + type: 'drag', + allDay: false, + event: fixture.componentInstance.events[0], + newStart: moment(fixture.componentInstance.events[0].start) + .add(1, 'day') + .add(1, 'hour') + .add(30, 'minutes') + .toDate(), + newEnd: undefined + }); + }); + + it('should drag time events to different days and columns while not snapping to a grid', () => { + const fixture: ComponentFixture< + CalendarWeekViewComponent + > = TestBed.createComponent(CalendarWeekViewComponent); + fixture.componentInstance.viewDate = new Date('2018-07-29'); + fixture.componentInstance.events = [ + { + start: moment(new Date('2018-07-29')) + .startOf('day') + .add(3, 'hours') + .toDate(), + end: moment(new Date('2018-07-29')) + .startOf('day') + .add(1, 'day') + .add(18, 'hours') + .toDate(), + title: 'foo', + draggable: true + } + ]; + fixture.componentInstance.snapDraggedEvents = false; + fixture.componentInstance.ngOnChanges({ viewDate: {}, events: {} }); + fixture.detectChanges(); + document.body.appendChild(fixture.nativeElement); + const events = fixture.nativeElement.querySelectorAll( + '.cal-event-container' + ); + const dayWidth: number = events[0].parentElement.offsetWidth; + const rect: ClientRect = events[0].getBoundingClientRect(); + let dragEvent: CalendarEventTimesChangedEvent; + fixture.componentInstance.eventTimesChanged.subscribe(e => { + dragEvent = e; + }); + triggerDomEvent('mousedown', events[0], { + clientX: rect.left, + clientY: rect.top + }); + fixture.detectChanges(); + const timeEvents = fixture.nativeElement.querySelector('.cal-time-events'); + triggerDomEvent('mousemove', timeEvents, { + clientX: rect.left + dayWidth - 5, + clientY: rect.top + 95 + }); + fixture.detectChanges(); + triggerDomEvent('mouseup', timeEvents, { + lientX: rect.left + dayWidth - 5, + clientY: rect.top + 95 + }); + fixture.detectChanges(); + fixture.destroy(); + expect(dragEvent).to.deep.equal({ + type: 'drag', + allDay: false, + event: fixture.componentInstance.events[0], + newStart: moment(fixture.componentInstance.events[0].start) + .add(1, 'day') + .add(1, 'hour') + .add(30, 'minutes') + .toDate(), + newEnd: moment(fixture.componentInstance.events[0].end) + .add(1, 'day') + .add(1, 'hour') + .add(30, 'minutes') + .toDate() + }); + }); + + it('should drag an all day event onto the time grid', () => { + const fixture: ComponentFixture< + CalendarWeekViewComponent + > = TestBed.createComponent(CalendarWeekViewComponent); + fixture.componentInstance.viewDate = new Date('2018-07-29'); + fixture.componentInstance.events = [ + { + start: moment(new Date('2018-07-29')) + .startOf('day') + .add(1, 'days') + .add(3, 'hours') + .toDate(), + end: moment(new Date('2018-07-29')) + .startOf('day') + .add(2, 'days') + .add(18, 'hours') + .toDate(), + title: 'foo', + draggable: true, + allDay: true + } + ]; + fixture.componentInstance.snapDraggedEvents = false; + fixture.componentInstance.ngOnChanges({ viewDate: {}, events: {} }); + fixture.detectChanges(); + document.body.appendChild(fixture.nativeElement); + const event = fixture.nativeElement.querySelector('.cal-event-container'); + const rect: ClientRect = event.getBoundingClientRect(); + let dragEvent: CalendarEventTimesChangedEvent; + fixture.componentInstance.eventTimesChanged.subscribe(e => { + dragEvent = e; + }); + triggerDomEvent('mousedown', event, { + clientX: rect.left, + clientY: rect.top + }); + fixture.detectChanges(); + const hourSegment = fixture.nativeElement.querySelectorAll( + '.cal-day-columns mwl-calendar-week-view-hour-segment' + )[3]; + const hourSegmentPosition = hourSegment.getBoundingClientRect(); + triggerDomEvent('mousemove', hourSegment, { + clientX: hourSegmentPosition.left, + clientY: hourSegmentPosition.top + }); + fixture.detectChanges(); + triggerDomEvent('mouseup', hourSegment, { + clientX: hourSegmentPosition.left, + clientY: hourSegmentPosition.top + }); + fixture.detectChanges(); + fixture.destroy(); + expect(dragEvent).to.deep.equal({ + type: 'drop', + allDay: false, + event: fixture.componentInstance.events[0], + newStart: moment(new Date('2018-07-29')) + .startOf('day') + .add(90, 'minutes') + .toDate() + }); + }); + + it('should drag a time event onto the all day grid', () => { + const fixture: ComponentFixture< + CalendarWeekViewComponent + > = TestBed.createComponent(CalendarWeekViewComponent); + fixture.componentInstance.viewDate = new Date('2018-07-29'); + fixture.componentInstance.events = [ + { + start: moment(new Date('2018-07-29')) + .startOf('day') + .add(1, 'days') + .add(3, 'hours') + .toDate(), + end: moment(new Date('2018-07-29')) + .startOf('day') + .add(2, 'days') + .add(18, 'hours') + .toDate(), + title: 'foo', + draggable: true, + allDay: false + }, + { + start: moment(new Date('2018-07-29')) + .startOf('day') + .toDate(), + allDay: true, + title: 'bar' + } + ]; + fixture.componentInstance.snapDraggedEvents = false; + fixture.componentInstance.ngOnChanges({ viewDate: {}, events: {} }); + fixture.detectChanges(); + document.body.appendChild(fixture.nativeElement); + const event = fixture.nativeElement.querySelector( + '.cal-time-events .cal-event-container' + ); + const rect: ClientRect = event.getBoundingClientRect(); + let dragEvent: CalendarEventTimesChangedEvent; + fixture.componentInstance.eventTimesChanged.subscribe(e => { + dragEvent = e; + }); + triggerDomEvent('mousedown', event, { + clientX: rect.left, + clientY: rect.top + }); + fixture.detectChanges(); + const dayColumn = fixture.nativeElement.querySelectorAll( + '.cal-all-day-events .cal-day-column' + )[2]; + const dayColumnPosition = dayColumn.getBoundingClientRect(); + triggerDomEvent('mousemove', dayColumn, { + clientX: dayColumnPosition.left, + clientY: dayColumnPosition.top + }); + fixture.detectChanges(); + triggerDomEvent('mouseup', dayColumn, { + clientX: dayColumnPosition.left, + clientY: dayColumnPosition.top + }); + fixture.detectChanges(); + fixture.destroy(); + expect(dragEvent).to.deep.equal({ + type: 'drop', + allDay: true, + event: fixture.componentInstance.events[0], + newStart: moment(new Date('2018-07-29')) + .startOf('day') + .add(2, 'days') + .toDate() + }); + }); });