UNPKG

novo-elements

Version:

585 lines (577 loc) 53.3 kB
import * as i0 from '@angular/core'; import { InjectionToken, Injectable, Optional, SkipSelf, EventEmitter, Output, Input, HostBinding, ChangeDetectionStrategy, Component, NgModule } from '@angular/core'; import * as i2 from '@angular/platform-browser'; import { addDays, isToday, isDate, startOfDay, isSameDay, startOfMonth, addMonths, setMonth, setYear, subMonths } from 'date-fns'; import * as i1 from 'novo-elements/services'; import { DateUtil, BooleanInput, Helpers } from 'novo-elements/utils'; import * as i3 from '@angular/common'; import { CommonModule } from '@angular/common'; import * as i4 from 'novo-elements/elements/button'; import { NovoButtonModule } from 'novo-elements/elements/button'; import { FormsModule } from '@angular/forms'; import { NovoIconModule } from 'novo-elements/elements/icon'; import { NovoPipesModule } from 'novo-elements/pipes'; /** Injection token used to customize the date range selection behavior. */ const NOVO_DATE_SELECTION_STRATEGY = new InjectionToken('NOVO_DATE_SELECTION_STRATEGY'); /** Provides the default date selection behavior. Single Date */ class DefaultDateSelectionStrategy { selectionFinished(date, currentValue, event) { return [date]; } createPreview(activeDate, [currentDate]) { return [activeDate]; } isSelected(activeDate, [currentDate]) { return DateUtil.isSameDay(activeDate, currentDate); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DefaultDateSelectionStrategy, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DefaultDateSelectionStrategy }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DefaultDateSelectionStrategy, decorators: [{ type: Injectable }] }); /** @docs-private */ function NOVO_DATE_SELECTION_STRATEGY_PROVIDER_FACTORY(parent) { return parent || new DefaultDateSelectionStrategy(); } /** @docs-private */ const NOVO_DATE_SELECTION_STRATEGY_PROVIDER = { provide: NOVO_DATE_SELECTION_STRATEGY, deps: [[new Optional(), new SkipSelf(), NOVO_DATE_SELECTION_STRATEGY]], useFactory: NOVO_DATE_SELECTION_STRATEGY_PROVIDER_FACTORY, }; class MultiDateSelectionStrategy { selectionFinished(dateLike, currentValue, event) { const date = dateLike; const current = new Set(currentValue.map((c) => c.getTime())); if (current.has(date.getTime())) { current.delete(date.getTime()); } else { current.add(date.getTime()); } return [...current].map((c) => new Date(c)); } createPreview(activeDate, currentValue) { return [activeDate]; } isSelected(activeDate, currentValue) { return currentValue && currentValue.includes(activeDate); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: MultiDateSelectionStrategy, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: MultiDateSelectionStrategy }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: MultiDateSelectionStrategy, decorators: [{ type: Injectable }] }); class RangeSelectionStrategy { selectionFinished(date, currentRange) { let [start, end] = currentRange; if (start == null) { start = date; } else if (end == null && date && DateUtil.differenceInCalendarDays(date, start) >= 0) { end = date; } else { start = date; end = null; } return [start, end]; } createPreview(activeDate, currentRange) { let start = null; let end = null; const [currStart, currEnd] = currentRange; if (currStart && !currEnd && activeDate) { start = currStart; end = activeDate; } return [start, end]; } isSelected(activeDate, currentRange) { const [start, end] = currentRange; return DateUtil.isWithinRange(activeDate, start, end); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: RangeSelectionStrategy, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: RangeSelectionStrategy }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: RangeSelectionStrategy, decorators: [{ type: Injectable }] }); class WeekSelectionStrategy { constructor(weekStartsOn = 0) { this.weekStartsOn = weekStartsOn; } selectionFinished(date) { return this._createWeekRange(date); } createPreview(activeDate) { return this._createWeekRange(activeDate); } _createWeekRange(date) { if (date) { const { weekStartsOn } = this; const start = DateUtil.startOfWeek(date, { weekStartsOn }); const end = DateUtil.endOfWeek(date, { weekStartsOn }); return [start, end]; } return [null, null]; } isSelected(activeDate, currentRange) { const [start, end] = currentRange; return DateUtil.isWithinRange(activeDate, start, end); } } var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; class NovoMonthViewElement { get weekStartsOn() { return this._weekStartsOn; } set weekStartsOn(value) { this._weekStartsOn = value; this.weekdays = this.labels.getWeekdays(value); this.updateView(this.activeDate); } constructor(labels, element, cdr, _sanitizer) { this.labels = labels; this.element = element; this.cdr = cdr; this._sanitizer = _sanitizer; this.activeDate = new Date(); // Weekstart must be 0-6 (Sunday - Saturday) this.selected = []; this.preview = []; this.overlays = []; this.isRange = false; this.hideOverflowDays = false; this._weekStartsOn = 0; // Select callback for output this.select = new EventEmitter(false); // Select callback for output this.hover = new EventEmitter(false); // List of all the weekdays this.weekdays = this.labels.getWeekdays(this.weekStartsOn); // List of all months this.monthNames = this.labels.getMonths(); } ngOnInit() { // Set labels this.updateView(this.activeDate); } updateView(date) { this.monthLabel = this.labels.formatDateWithFormat(this.activeDate, { month: 'short' }); this.buildMonth(this.activeDate); } onSelect(event, day) { // Helpers.swallowEvent(event); this.select.next({ event, day }); this.cdr.markForCheck(); } onHover(event, day) { this.isRange && this.hover.next({ event, day }); } buildMonth(month) { // Reset the weeks this.weeks = []; const start = DateUtil.startOfMonth(month); // House keeping variables to know when we are done building the month let done = false; let date = DateUtil.startOfWeek(start, { weekStartsOn: this.weekStartsOn }); let monthIndex = date.getMonth(); let count = 0; while (!done) { // Build the days for the weeks this.weeks.push({ days: this.buildWeek(new Date(date.getTime()), month) }); // Increment variables for the next iteration date = addDays(date, 7); done = count++ > 2 && monthIndex !== date.getMonth(); monthIndex = date.getMonth(); } } buildWeek(date, month) { // Build out of the days of the week const days = []; // Iterate over the days of the week for (let i = 0; i < 7; i++) { // Push a variable on the day array with lots of helpers to make the template easier days.push({ name: this.weekdays[i], number: date.getDate(), isToday: isToday(date), date, }); // Increment for the next iteration date = addDays(date, 1); } return days; } isDisabled(day) { return (this.minDate && DateUtil.isBefore(day, this.minDate)) || (this.maxDate && DateUtil.isAfter(day, this.maxDate)); } /** Returns whether a cell should be marked as selected. */ _isSelected(value) { return this.selected && this.selected.find((d) => DateUtil.isSameDay(d, value)); } /** Returns whether a cell should be marked as preview. */ _isPreview(value) { return this.preview && this.preview.find((d) => DateUtil.isSameDay(d, value)); } /** Returns whether a cell should be marked as an overlay. */ _isOverlay(value) { return this.overlays && this.overlays.find((o) => DateUtil.isSameDay(o.date, value)); } /** Returns whether a cell should be marked as an overlay. */ _hasOverlayType(value) { const overlay = this.overlays && this.overlays.find((o) => DateUtil.isSameDay(o.date, value)); return overlay ? overlay.type : null; } /** Gets whether a value is the start of the main range. */ _isRangeStart(value) { return isStart(value, this.selected, this.isRange); } /** Gets whether a value is the end of the main range. */ _isRangeEnd(value) { return isEnd(value, this.selected, this.isRange); } /** Gets whether a value is within the currently-selected range. */ _isInRange(value) { return isInRange(value, this.selected, this.isRange); } /** Gets whether a value is the start of the preview range. */ _isPreviewStart(value) { return isStart(value, this.preview, this.isRange); } /** Gets whether a value is the end of the preview range. */ _isPreviewEnd(value) { return isEnd(value, this.preview, this.isRange); } /** Gets whether a value is inside the preview range. */ _isInPreview(value) { return isInRange(value, this.preview, this.isRange); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NovoMonthViewElement, deps: [{ token: i1.NovoLabelService }, { token: i0.ElementRef }, { token: i0.ChangeDetectorRef }, { token: i2.DomSanitizer }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: NovoMonthViewElement, isStandalone: false, selector: "novo-month-view", inputs: { minDate: "minDate", maxDate: "maxDate", activeDate: "activeDate", selected: "selected", preview: "preview", overlays: "overlays", disabledDateMessage: "disabledDateMessage", isRange: "isRange", hideOverflowDays: "hideOverflowDays", weekStartsOn: "weekStartsOn" }, outputs: { select: "select", hover: "hover" }, host: { properties: { "class.hide-overflow-days": "this.hideOverflowDays" } }, ngImport: i0, template: "<div class=\"calendar-table\" cellspacing=\"0\" cellpadding=\"0\">\n <div class=\"calendar-thead\">\n <div class=\"calendar-th weekday\" *ngFor=\"let day of weekdays\" title=\"{{ day }}\"\n [attr.data-automation-id]=\"day.substr(0, 2)\">\n {{ day.substr(0, 2) }}\n </div>\n </div>\n <div class=\"calendar-body\">\n <div class=\"calendar-week\" *ngFor=\"let week of weeks\">\n <div class=\"calendar-date\" *ngFor=\"let day of week.days\"\n [class.today]=\"day.isToday\"\n [class.notinmonth]=\"day.date.getMonth() !== activeDate.getMonth()\"\n [class.selected]=\"_isSelected(day.date)\"\n [class.preview]=\"_isPreview(day.date)\"\n [class.overlay]=\"_isOverlay(day.date)\"\n [class]=\"_hasOverlayType(day.date)\"\n [class.inRange]=\"_isInRange(day.date)\"\n [class.rangeStart]=\"_isRangeStart(day.date)\"\n [class.rangeEnd]=\"_isRangeEnd(day.date)\"\n [class.inPreview]=\"_isInPreview(day.date)\"\n [class.previewStart]=\"_isPreviewStart(day.date)\"\n [class.previewEnd]=\"_isPreviewEnd(day.date)\"\n [class.calendar-date]=\"true\"\n [attr.aria-label]=\"day.name\"\n [attr.aria-disabled]=\"isDisabled(day.date)\"\n [attr.aria-selected]=\"_isSelected(day.date)\"\n [attr.data-automation-id]=\"day.number\"\n [title]=\"isDisabled(day.date) ? disabledDateMessage : ''\"\n (mouseover)=\"onHover($event, day)\">\n <novo-button\n class=\"day\"\n tabindex=\"0\"\n [attr.data-automation-id]=\"day.number\"\n [disabled]=\"isDisabled(day.date)\"\n (click)=\"onSelect($event, day)\">\n {{ day.number }}\n </novo-button>\n </div>\n </div>\n </div>\n</div>", styles: [":host{background:var(--background-bright);width:100%;height:min-content;position:relative}:host .calendar-table{display:table}:host .calendar-table .calendar-thead{display:table-header-group}:host .calendar-table .calendar-th{display:table-cell;width:30px;padding:10px 0}:host .calendar-table .calendar-body{display:table-row-group}:host .calendar-table .calendar-week{display:table-row}:host .calendar-table .month,:host .calendar-table .year{text-align:center;padding:4px 15px;color:#666;overflow-x:hidden;text-overflow:ellipsis;margin:5px;font-weight:400;border-radius:3px}:host .calendar-table .month.selected,:host .calendar-table .year.selected{background-color:var(--selection);color:#fff}:host .calendar-table .month:hover,:host .calendar-table .year:hover{cursor:pointer;background-color:var(--selection);color:#fff}:host .calendar-table .day{height:3.2rem;width:3.2rem;line-height:1;font-size:1.2rem;padding:1px;border:none;background-color:transparent;border-radius:50%;box-shadow:inset 0 0 0 2px transparent;transition:box-shadow .14s ease-in-out;position:relative;color:var(--text-main, #3d464d)}:host .calendar-table .day:focus{outline:none}:host .calendar-table .day:disabled{color:var(--text-disabled);cursor:not-allowed!important;box-shadow:none!important}:host .calendar-table .calendar-date{display:table-cell}:host .calendar-table .calendar-date.notinmonth,:host .calendar-table .calendar-date.notinmonth:not(.selected)>.day{color:var(--text-disabled)}:host .calendar-table .calendar-date:hover .day{cursor:pointer;box-shadow:inset 0 0 0 2px var(--selection)}:host .calendar-table .calendar-date.inRange:hover .day{box-shadow:inset 0 0 0 2px #fff}:host .calendar-table .calendar-date.inRange{background:var(--selection);color:#fff;height:3.2rem;width:3.2rem;border-radius:0}:host .calendar-table .calendar-date.inRange .day{color:#fff}:host .calendar-table .calendar-date.rangeStart{border-radius:50% 0 0 50%;box-shadow:none!important;position:relative}:host .calendar-table .calendar-date.rangeStart:before{content:\"\";position:absolute;height:100%;background:#4a89dc;width:10px;top:0;right:-5px;z-index:-1}:host .calendar-table .calendar-date.rangeEnd{border-radius:0 50% 50% 0;box-shadow:none!important;position:relative}:host .calendar-table .calendar-date.rangeEnd:before{content:\"\";position:absolute;height:100%;background:var(--selection);width:10px;top:0;left:-5px;z-index:-1}:host .calendar-table .calendar-date.selected .day{background:var(--selection);color:#fff}:host .calendar-table .calendar-date.preview:not(.previewStart):not(.previewEnd) .day{border:1px dashed var(--selection)}:host .calendar-table .calendar-date.preview:not(.previewStart):not(.previewEnd).selected .day{border:1px dashed #9dbeff}:host .calendar-table .calendar-date.today .day:after{content:\"\";position:absolute;top:0;left:0;border-radius:100%;width:100%;height:100%;max-width:3.2rem;margin:0 auto;box-shadow:inset 0 0 0 2px #dbdbdb}:host .calendar-table .calendar-date.today.inRange .day:after,:host .calendar-table .calendar-date.today.selected .day:after{box-shadow:inset 0 0 0 2px #9dbeff}:host .calendar-table .calendar-date.inPreview .day{border-radius:0;border-top:1px dashed #4a89dc;border-bottom:1px dashed #4a89dc}:host .calendar-table .calendar-date.previewStart .day{border-radius:50% 0 0 50%;box-shadow:none!important;border-left:1px dashed #4a89dc}:host .calendar-table .calendar-date.previewEnd .day{border-radius:0 50% 50% 0;box-shadow:none!important;border-right:1px dashed #4a89dc}\n"], dependencies: [{ kind: "directive", type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: i4.NovoButtonElement, selector: "novo-button,button[theme]", inputs: ["color", "side", "size", "theme", "loading", "icon", "secondIcon", "disabled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } __decorate([ BooleanInput(), __metadata("design:type", Boolean) ], NovoMonthViewElement.prototype, "isRange", void 0); __decorate([ BooleanInput(), __metadata("design:type", Boolean) ], NovoMonthViewElement.prototype, "hideOverflowDays", void 0); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NovoMonthViewElement, decorators: [{ type: Component, args: [{ selector: 'novo-month-view', changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "<div class=\"calendar-table\" cellspacing=\"0\" cellpadding=\"0\">\n <div class=\"calendar-thead\">\n <div class=\"calendar-th weekday\" *ngFor=\"let day of weekdays\" title=\"{{ day }}\"\n [attr.data-automation-id]=\"day.substr(0, 2)\">\n {{ day.substr(0, 2) }}\n </div>\n </div>\n <div class=\"calendar-body\">\n <div class=\"calendar-week\" *ngFor=\"let week of weeks\">\n <div class=\"calendar-date\" *ngFor=\"let day of week.days\"\n [class.today]=\"day.isToday\"\n [class.notinmonth]=\"day.date.getMonth() !== activeDate.getMonth()\"\n [class.selected]=\"_isSelected(day.date)\"\n [class.preview]=\"_isPreview(day.date)\"\n [class.overlay]=\"_isOverlay(day.date)\"\n [class]=\"_hasOverlayType(day.date)\"\n [class.inRange]=\"_isInRange(day.date)\"\n [class.rangeStart]=\"_isRangeStart(day.date)\"\n [class.rangeEnd]=\"_isRangeEnd(day.date)\"\n [class.inPreview]=\"_isInPreview(day.date)\"\n [class.previewStart]=\"_isPreviewStart(day.date)\"\n [class.previewEnd]=\"_isPreviewEnd(day.date)\"\n [class.calendar-date]=\"true\"\n [attr.aria-label]=\"day.name\"\n [attr.aria-disabled]=\"isDisabled(day.date)\"\n [attr.aria-selected]=\"_isSelected(day.date)\"\n [attr.data-automation-id]=\"day.number\"\n [title]=\"isDisabled(day.date) ? disabledDateMessage : ''\"\n (mouseover)=\"onHover($event, day)\">\n <novo-button\n class=\"day\"\n tabindex=\"0\"\n [attr.data-automation-id]=\"day.number\"\n [disabled]=\"isDisabled(day.date)\"\n (click)=\"onSelect($event, day)\">\n {{ day.number }}\n </novo-button>\n </div>\n </div>\n </div>\n</div>", styles: [":host{background:var(--background-bright);width:100%;height:min-content;position:relative}:host .calendar-table{display:table}:host .calendar-table .calendar-thead{display:table-header-group}:host .calendar-table .calendar-th{display:table-cell;width:30px;padding:10px 0}:host .calendar-table .calendar-body{display:table-row-group}:host .calendar-table .calendar-week{display:table-row}:host .calendar-table .month,:host .calendar-table .year{text-align:center;padding:4px 15px;color:#666;overflow-x:hidden;text-overflow:ellipsis;margin:5px;font-weight:400;border-radius:3px}:host .calendar-table .month.selected,:host .calendar-table .year.selected{background-color:var(--selection);color:#fff}:host .calendar-table .month:hover,:host .calendar-table .year:hover{cursor:pointer;background-color:var(--selection);color:#fff}:host .calendar-table .day{height:3.2rem;width:3.2rem;line-height:1;font-size:1.2rem;padding:1px;border:none;background-color:transparent;border-radius:50%;box-shadow:inset 0 0 0 2px transparent;transition:box-shadow .14s ease-in-out;position:relative;color:var(--text-main, #3d464d)}:host .calendar-table .day:focus{outline:none}:host .calendar-table .day:disabled{color:var(--text-disabled);cursor:not-allowed!important;box-shadow:none!important}:host .calendar-table .calendar-date{display:table-cell}:host .calendar-table .calendar-date.notinmonth,:host .calendar-table .calendar-date.notinmonth:not(.selected)>.day{color:var(--text-disabled)}:host .calendar-table .calendar-date:hover .day{cursor:pointer;box-shadow:inset 0 0 0 2px var(--selection)}:host .calendar-table .calendar-date.inRange:hover .day{box-shadow:inset 0 0 0 2px #fff}:host .calendar-table .calendar-date.inRange{background:var(--selection);color:#fff;height:3.2rem;width:3.2rem;border-radius:0}:host .calendar-table .calendar-date.inRange .day{color:#fff}:host .calendar-table .calendar-date.rangeStart{border-radius:50% 0 0 50%;box-shadow:none!important;position:relative}:host .calendar-table .calendar-date.rangeStart:before{content:\"\";position:absolute;height:100%;background:#4a89dc;width:10px;top:0;right:-5px;z-index:-1}:host .calendar-table .calendar-date.rangeEnd{border-radius:0 50% 50% 0;box-shadow:none!important;position:relative}:host .calendar-table .calendar-date.rangeEnd:before{content:\"\";position:absolute;height:100%;background:var(--selection);width:10px;top:0;left:-5px;z-index:-1}:host .calendar-table .calendar-date.selected .day{background:var(--selection);color:#fff}:host .calendar-table .calendar-date.preview:not(.previewStart):not(.previewEnd) .day{border:1px dashed var(--selection)}:host .calendar-table .calendar-date.preview:not(.previewStart):not(.previewEnd).selected .day{border:1px dashed #9dbeff}:host .calendar-table .calendar-date.today .day:after{content:\"\";position:absolute;top:0;left:0;border-radius:100%;width:100%;height:100%;max-width:3.2rem;margin:0 auto;box-shadow:inset 0 0 0 2px #dbdbdb}:host .calendar-table .calendar-date.today.inRange .day:after,:host .calendar-table .calendar-date.today.selected .day:after{box-shadow:inset 0 0 0 2px #9dbeff}:host .calendar-table .calendar-date.inPreview .day{border-radius:0;border-top:1px dashed #4a89dc;border-bottom:1px dashed #4a89dc}:host .calendar-table .calendar-date.previewStart .day{border-radius:50% 0 0 50%;box-shadow:none!important;border-left:1px dashed #4a89dc}:host .calendar-table .calendar-date.previewEnd .day{border-radius:0 50% 50% 0;box-shadow:none!important;border-right:1px dashed #4a89dc}\n"] }] }], ctorParameters: () => [{ type: i1.NovoLabelService }, { type: i0.ElementRef }, { type: i0.ChangeDetectorRef }, { type: i2.DomSanitizer }], propDecorators: { minDate: [{ type: Input }], maxDate: [{ type: Input }], activeDate: [{ type: Input }], selected: [{ type: Input }], preview: [{ type: Input }], overlays: [{ type: Input }], disabledDateMessage: [{ type: Input }], isRange: [{ type: Input }], hideOverflowDays: [{ type: Input }, { type: HostBinding, args: ['class.hide-overflow-days'] }], weekStartsOn: [{ type: Input }], select: [{ type: Output }], hover: [{ type: Output }] } }); /** Checks whether a value is the start of a range. */ function isStart(value, range, rangeEnabled) { const [start, end] = range ?? []; return rangeEnabled && end !== null && !DateUtil.isSameDay(start, end) && value < end && DateUtil.isSameDay(value, start); } /** Checks whether a value is the end of a range. */ function isEnd(value, range, rangeEnabled) { const [start, end] = range ?? []; return rangeEnabled && start !== null && !DateUtil.isSameDay(start, end) && value >= start && DateUtil.isSameDay(value, end); } /** Checks whether a value is inside of a range. */ function isInRange(value, range, rangeEnabled) { const [start, end] = range ?? []; return rangeEnabled && start !== null && end !== null && !DateUtil.isSameDay(start, end) && value >= start && value <= end; } // NG2 class NovoMonthSelectElement { constructor(labels) { this.labels = labels; this.activeDate = new Date(); this.selected = []; // Select callback for output this.select = new EventEmitter(false); // List of all months this.monthNames = this.labels.getMonths(); } ngOnInit() { } onSelect(event, month) { // Helpers.swallowEvent(event); this.select.next({ event, month }); } _isActive(month) { return this.activeDate && month === DateUtil.getMonth(this.activeDate); } _isSelected(month) { return this.selected && month === DateUtil.getMonth(this.selected[0]); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NovoMonthSelectElement, deps: [{ token: i1.NovoLabelService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: NovoMonthSelectElement, isStandalone: false, selector: "novo-month-select", inputs: { activeDate: "activeDate", selected: "selected" }, outputs: { select: "select" }, ngImport: i0, template: "<div *ngFor=\"let month of monthNames; let i = index\" (click)=\"onSelect($event, i)\" tabindex=\"0\">\n <div class=\"month\" [class.selected]=\"_isSelected(i)\" [attr.data-automation-id]=\"month\">\n {{ month }}</div>\n</div>", styles: [":host{display:grid;grid-template-columns:1fr 1fr 1fr;flex:1}:host .month{padding:1rem;cursor:pointer;border-radius:.4rem}:host .month.selected{background-color:#4a89dc;color:#fff}:host .month:hover{background-color:#4a89dc;color:#fff}\n"], dependencies: [{ kind: "directive", type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NovoMonthSelectElement, decorators: [{ type: Component, args: [{ selector: 'novo-month-select', changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "<div *ngFor=\"let month of monthNames; let i = index\" (click)=\"onSelect($event, i)\" tabindex=\"0\">\n <div class=\"month\" [class.selected]=\"_isSelected(i)\" [attr.data-automation-id]=\"month\">\n {{ month }}</div>\n</div>", styles: [":host{display:grid;grid-template-columns:1fr 1fr 1fr;flex:1}:host .month{padding:1rem;cursor:pointer;border-radius:.4rem}:host .month.selected{background-color:#4a89dc;color:#fff}:host .month:hover{background-color:#4a89dc;color:#fff}\n"] }] }], ctorParameters: () => [{ type: i1.NovoLabelService }], propDecorators: { activeDate: [{ type: Input }], selected: [{ type: Input }], select: [{ type: Output }] } }); class NovoYearSelectElement { constructor(labels) { this.labels = labels; this.activeDate = new Date(); this.selected = []; // Select callback for output this.select = new EventEmitter(false); // List of all years (generated in ngOnInit) this.years = []; } ngOnInit() { // Determine the year array const now = new Date(); const start = this.minYear ? Number(this.minYear) : now.getFullYear() - 100; const end = this.maxYear ? Number(this.maxYear) : now.getFullYear() + 10; const years = []; for (let i = start; i <= end; i++) { years.push(i); } this.years = years.reverse(); } onSelect(event, year) { // Helpers.swallowEvent(event); this.select.next({ event, year }); } _isActive(year) { return this.activeDate && year === DateUtil.getYear(this.activeDate); } _isSelected(year) { return this.selected && year === DateUtil.getYear(this.selected[0]); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NovoYearSelectElement, deps: [{ token: i1.NovoLabelService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: NovoYearSelectElement, isStandalone: false, selector: "novo-year-select", inputs: { minYear: "minYear", maxYear: "maxYear", activeDate: "activeDate", selected: "selected" }, outputs: { select: "select" }, ngImport: i0, template: "<div *ngFor=\"let year of years\" (click)=\"onSelect($event, year)\" tabindex=\"0\">\n <div class=\"year\" [class.selected]=\"_isSelected(year)\" [attr.data-automation-id]=\"year\">{{ year }}</div>\n</div>", styles: [":host{display:grid;grid-template-columns:1fr 1fr 1fr 1fr;flex:1;max-height:320px;overflow-y:scroll}:host .year{padding:1rem;cursor:pointer;border-radius:.4rem}:host .year.selected{background-color:#4a89dc;color:#fff}:host .year:hover{background-color:#4a89dc;color:#fff}\n"], dependencies: [{ kind: "directive", type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NovoYearSelectElement, decorators: [{ type: Component, args: [{ selector: 'novo-year-select', changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "<div *ngFor=\"let year of years\" (click)=\"onSelect($event, year)\" tabindex=\"0\">\n <div class=\"year\" [class.selected]=\"_isSelected(year)\" [attr.data-automation-id]=\"year\">{{ year }}</div>\n</div>", styles: [":host{display:grid;grid-template-columns:1fr 1fr 1fr 1fr;flex:1;max-height:320px;overflow-y:scroll}:host .year{padding:1rem;cursor:pointer;border-radius:.4rem}:host .year.selected{background-color:#4a89dc;color:#fff}:host .year:hover{background-color:#4a89dc;color:#fff}\n"] }] }], ctorParameters: () => [{ type: i1.NovoLabelService }], propDecorators: { minYear: [{ type: Input }], maxYear: [{ type: Input }], activeDate: [{ type: Input }], selected: [{ type: Input }], select: [{ type: Output }] } }); // NG2 class NovoCalendarElement { get selected() { return this._selected; } set selected(value) { this._selected = value ? value.filter(isDate).map((d) => startOfDay(d)) : []; } get activeDate() { return this._activeDate; } set activeDate(value) { if (!isSameDay(value, this._activeDate)) { this._activeDate = value; this.activeDateChange.next(value); this.updateView(value); } } get weekStartsOn() { return this._weekStartsOn; } set weekStartsOn(value) { this._weekStartsOn = value; if (this.mode === 'week') { this._strategy = new WeekSelectionStrategy(this.weekStartsOn); } } get numberOfMonths() { return this._numberOfMonths.length; } set numberOfMonths(value) { this._numberOfMonths = Array.from(Array(Number(value)).keys()); this.updateView(this.activeDate); } get mode() { return this._mode; } set mode(value) { if (this._mode !== value) { this._mode = value; switch (value) { case 'multiple': this._strategy = new MultiDateSelectionStrategy(); break; case 'range': this._strategy = new RangeSelectionStrategy(); break; case 'week': this._strategy = new WeekSelectionStrategy(this.weekStartsOn); break; case 'single': default: this._strategy = new DefaultDateSelectionStrategy(); break; } } } get hb_width() { if (this.layout === 'vertical') { return this._sanitizer.bypassSecurityTrustStyle('min-content'); } return this._sanitizer.bypassSecurityTrustStyle('min-content'); } get hb_horiztonal() { return this.layout !== 'vertical'; } get hb_vertical() { return this.layout === 'vertical'; } constructor(labels, element, cdr, _sanitizer) { this.labels = labels; this.element = element; this.cdr = cdr; this._sanitizer = _sanitizer; // Default view mode (select days) this.activeView = 'days'; this.layout = 'horizontal'; this._selected = []; this.selectedChange = new EventEmitter(); this.preview = []; this.previewChange = new EventEmitter(); this.activeDateChange = new EventEmitter(); this.overlays = []; this._activeDate = new Date(); this._mode = 'single'; this._numberOfMonths = [0]; this._weekStartsOn = 0; this._strategy = new DefaultDateSelectionStrategy(); } ngOnInit() { if (!this.activeDate) { this.activeDate = this.selected.length ? this.selected[0] : new Date(); } this.updateView(this.activeDate); } updateView(activeDate) { this.activeDate = new Date(activeDate ? new Date(activeDate) : new Date()); this.months = []; const month = startOfMonth(this.activeDate); for (const i of this._numberOfMonths) { const date = addMonths(month, i); const label = this.labels.formatDateWithFormat(date, { month: 'short' }); this.months.push({ date, label }); } } setToday() { const tmp = new Date(); this.updateView(tmp); // Go back to days this.openView(null, 'days'); } monthSelected({ event, month }) { const date = this.activeDate ? this.activeDate : new Date().getMonth(); const tmp = setMonth(date, month); this.updateView(tmp); // Go back to days this.openView(null, 'days'); } yearSelected({ event, year }) { const date = this.activeDate ? this.activeDate : new Date(); const tmp = setYear(date, year); this.updateView(tmp); // Go back to days this.openView(null, 'days'); } dateSelected({ event, day }) { // Helpers.swallowEvent(event); this.selected = this._strategy.selectionFinished(day.date, this.selected, event); this.selectedChange.emit(this.selected); this.cdr.markForCheck(); } updatePreview({ event, day }) { this.preview = this._strategy.createPreview(day.date, this.selected, event); this.previewChange.emit(this.preview); } prevMonth(event) { Helpers.swallowEvent(event); const tmp = subMonths(this.activeDate, 1); this.updateView(tmp); } nextMonth(event) { Helpers.swallowEvent(event); const tmp = addMonths(this.activeDate, 1); this.updateView(tmp); } openView(event, type) { Helpers.swallowEvent(event); // If they click the toggle two time in a row, close it (go back to days) if (type === this.activeView) { this.activeView = 'days'; } else { this.activeView = type; } // Make sure to scroll the selected one into view if (this.activeView === 'years' || this.activeView === 'months') { setTimeout(() => { const container = this.element.nativeElement.querySelector(`.calendar-content.${this.activeView}`); const selectedItem = this.element.nativeElement.querySelector(`.calendar-content.${this.activeView} .${this.activeView === 'years' ? 'year' : 'month'}.selected`); if (container && selectedItem) { container.scrollTop = selectedItem.offsetTop - 100; } }); } } _isRange() { return ['week', 'range'].includes(this.mode); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NovoCalendarElement, deps: [{ token: i1.NovoLabelService }, { token: i0.ElementRef }, { token: i0.ChangeDetectorRef }, { token: i2.DomSanitizer }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: NovoCalendarElement, isStandalone: false, selector: "novo-calendar", inputs: { minYear: "minYear", maxYear: "maxYear", minDate: "minDate", maxDate: "maxDate", activeView: "activeView", layout: "layout", selected: "selected", preview: "preview", overlays: "overlays", disabledDateMessage: "disabledDateMessage", activeDate: "activeDate", weekStartsOn: "weekStartsOn", numberOfMonths: "numberOfMonths", mode: "mode" }, outputs: { selectedChange: "selectedChange", previewChange: "previewChange", activeDateChange: "activeDateChange" }, host: { properties: { "style.width": "this.hb_width", "class.layout-horizontal": "this.hb_horiztonal", "class.layout-vertical": "this.hb_vertical" } }, ngImport: i0, template: "<div class=\"calendar-header\">\n <novo-button theme=\"icon\" icon=\"previous\" size=\"small\" (click)=\"prevMonth($event)\"\n data-automation-id=\"calendar-previous\" tabindex=\"0\"></novo-button>\n <ng-container *ngFor=\"let month of months; let i = index;\">\n <span class=\"heading\" [class.secondary]=\"i > 0\">\n <span class=\"month\" (click)=\"openView($event, 'months')\"\n data-automation-id=\"header-month\" tabindex=\"0\">{{ month.label }}</span>\n <span class=\"year\" (click)=\"openView($event, 'years')\"\n data-automation-id=\"header-year\" tabindex=\"0\">{{ month.date?.getFullYear() }}</span>\n </span>\n </ng-container>\n <novo-button theme=\"icon\" icon=\"next\" size=\"small\" (click)=\"nextMonth($event)\" data-automation-id=\"calendar-next\" tabindex=\"0\">\n </novo-button>\n</div>\n<section class=\"calendar-content\" [ngSwitch]=\"activeView\">\n <ng-container *ngSwitchCase=\"'days'\">\n <ng-container *ngFor=\"let month of months; let i = index\">\n <div class=\"calendar-header\" *ngIf=\"layout==='vertical' && i > 0\">\n <span class=\"previous\" (click)=\"prevMonth($event)\" data-automation-id=\"calendar-previous\" tabindex=\"0\"></span>\n <span class=\"heading\">\n <span class=\"month\" (click)=\"openView($event, 'months')\"\n data-automation-id=\"header-month\" tabindex=\"0\">{{ month.label }}</span>\n <span class=\"year\" (click)=\"openView($event, 'years')\"\n data-automation-id=\"header-year\" tabindex=\"0\">{{ month.date?.getFullYear() }}</span>\n </span>\n <span class=\"next\" (click)=\"nextMonth($event)\" data-automation-id=\"calendar-next\"></span>\n </div>\n <novo-month-view\n class=\"month-view\"\n [activeDate]=\"month.date\"\n [selected]=\"selected\"\n [preview]=\"preview\"\n [overlays]=\"overlays\"\n [isRange]=\"_isRange()\"\n [hideOverflowDays]=\"months.length > 1\"\n [weekStartsOn]=\"weekStartsOn\"\n [disabledDateMessage]=\"disabledDateMessage\"\n [minDate]=\"minDate\"\n [maxDate]=\"maxDate\"\n (select)=\"dateSelected($event)\"\n (hover)=\"updatePreview($event)\"></novo-month-view>\n </ng-container>\n </ng-container>\n <novo-month-select\n *ngSwitchCase=\"'months'\"\n [activeDate]=\"activeDate\"\n [selected]=\"selected\"\n (select)=\"monthSelected($event)\">\n </novo-month-select>\n <novo-year-select\n *ngSwitchCase=\"'years'\"\n [activeDate]=\"activeDate\"\n [selected]=\"selected\"\n (select)=\"yearSelected($event)\">\n </novo-year-select>\n</section>", styles: [":host(.layout-horizontal){font-size:1.2rem}:host(.layout-horizontal) .calendar-content{flex-flow:row nowrap}:host(.layout-horizontal) .month-view+.month-view{border-collapse:unset;border-left:1px solid #dbdbdb;margin-left:.5rem;padding-left:.5rem}:host(.layout-vertical) .calendar-content{flex-flow:column nowrap}:host(.layout-vertical) .calendar-header .heading.secondary{display:none}:host{display:block;width:100%;text-align:center;background:var(--background-bright);color:var(--text-main);position:relative;-webkit-user-select:none;user-select:none}:host .calendar-content{display:flex;width:100%;height:min-content;position:static;top:0;left:0;overflow:hidden}:host .calendar-header{width:100%;display:flex;flex-flow:row nowrap;border-collapse:collapse;padding:1rem .8rem;-webkit-user-select:none;justify-content:space-between;align-items:center;cursor:default;border-bottom:1px solid var(--border)}:host .calendar-header .previous{width:30px;height:15px;display:inline-block;cursor:pointer}:host .calendar-header .previous:after{content:\"\";border-bottom:4px solid transparent;border-top:4px solid transparent;border-right:4px solid #aaa;display:inline-block;height:0;vertical-align:middle;width:0}:host .calendar-header .previous:hover:after{border-right:4px solid #4a89dc;cursor:pointer}:host .calendar-header .heading{flex:1;display:inline-block;vertical-align:middle;color:#4a89dc;font-weight:600}:host .calendar-header .heading .month{border-radius:2px;padding:3px 8px}:host .calendar-header .heading .month:hover{background:#4a89dc;color:#fff;cursor:pointer}:host .calendar-header .heading .year{border-radius:2px;padding:3px 8px}:host .calendar-header .heading .year:hover{background:#4a89dc;color:#fff;cursor:pointer}:host .calendar-header .next{width:30px;height:15px;display:inline-block;cursor:pointer}:host .calendar-header .next:before{content:\"\";border-bottom:4px solid transparent;border-top:4px solid transparent;border-left:4px solid #aaa;display:inline-block;height:0;vertical-align:middle;width:0}:host .calendar-header .next:hover:before{opacity:1;border-left:4px solid #4a89dc;cursor:pointer}\n"], dependencies: [{ kind: "directive", type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i3.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i3.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "component", type: i4.NovoButtonElement, selector: "novo-button,button[theme]", inputs: ["color", "side", "size", "theme", "loading", "icon", "secondIcon", "disabled"] }, { kind: "component", type: NovoMonthViewElement, selector: "novo-month-view", inputs: ["minDate", "maxDate", "activeDate", "selected", "preview", "overlays", "disabledDateMessage", "isRange", "hideOverflowDays", "weekStartsOn"], outputs: ["select", "hover"] }, { kind: "component", type: NovoMonthSelectElement, selector: "novo-month-select", inputs: ["activeDate", "selected"], outputs: ["select"] }, { kind: "component", type: NovoYearSelectElement, selector: "novo-year-select", inputs: ["minYear", "maxYear", "activeDate", "selected"], outputs: ["select"] }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NovoCalendarElement, decorators: [{ type: Component, args: [{ selector: 'novo-calendar', standalone: false, template: "<div class=\"calendar-header\">\n <novo-button theme=\"icon\" icon=\"previous\" size=\"small\" (click)=\"prevMonth($event)\"\n data-automation-id=\"calendar-previous\" tabindex=\"0\"></novo-button>\n <ng-container *ngFor=\"let month of months; let i = index;\">\n <span class=\"heading\" [class.secondary]=\"i > 0\">\n <span class=\"month\" (click)=\"openView($event, 'months')\"\n data-automation-id=\"header-month\" tabindex=\"0\">{{ month.label }}</span>\n <span class=\"year\" (click)=\"openView($event, 'years')\"\n data-automation-id=\"header-year\" tabindex=\"0\">{{ month.date?.getFullYear() }}</span>\n </span>\n </ng-container>\n <novo-button theme=\"icon\" icon=\"next\" size=\"small\" (click)=\"nextMonth($event)\" data-automation-id=\"calendar-next\" tabindex=\"0\">\n </novo-button>\n</div>\n<section class=\"calendar-content\" [ngSwitch]=\"activeView\">\n <ng-container *ngSwitchCase=\"'days'\">\n <ng-container *ngFor=\"let month of months; let i = index\">\n <div class=\"calendar-header\" *ngIf=\"layout==='vertical' && i > 0\">\n <span class=\"previous\" (click)=\"prevMonth($event)\" data-automation-id=\"calendar-previous\" tabindex=\"0\"></span>\n <span class=\"heading\">\n <span class=\"month\" (click)=\"openView($event, 'months')\"\n data-automation-id=\"header-month\" tabindex=\"0\">{{ month.label }}</span>\n <span class=\"year\" (click)=\"openView($event, 'years')\"\n data-automation-id=\"header-year\" tabindex=\"0\">{{ month.date?.getFullYear() }}</span>\n </span>\n <span class=\"next\" (click)=\"nextMonth($event)\" data-automation-id=\"calendar-next\"></span>\n </div>\n <novo-month-view\n class=\"month-view\"\n [activeDate]=\"month.date\"\n [selected]=\"selected\"\n [preview]=\"preview\"\n [overlays]=\"overlays\"\n [isRange]=\"_isRange()\"\n [hideOverflowDays]=\"months.length > 1\"\n [weekStartsOn]=\"weekStartsOn\"\n [disabledDateMessage]=\"disabledDateMessage\"\n [minDate]=\"minDate\"\n [maxDate]=\"maxDate\"\n (select)=\"dateSelected($event)\"\n (hover)=\"updatePreview($event)\"></novo-month-view>\n </ng-container>\n </ng-container>\n <novo-month-select\n *ngSwitchCase=\"'months'\"\n [activeDate]=\"activeDate\"\n [selected]=\"selected\"\n (select)=\"monthSelected($event)\">\n </novo-month-select>\n <novo-year-select\n *ngSwitchCase=\"'years'\"\n [activeDate]=\"activeDate\"\n [selected]=\"selected\"\n (select)=\"yearSelected($event)\">\n </novo-year-select>\n</section>", styles: [":host(.layout-horizontal){font-size:1.2rem}:host(.layout-horizontal) .calendar-content{flex-flow:row nowrap}:host(.layout-horizontal) .month-view+.month-view{border-collapse:unset;border-left:1px solid #dbdbdb;margin-left:.5rem;padding-left:.5rem}:host(.layout-vertical) .calendar-content{flex-flow:column nowrap}:host(.layout-vertical) .calendar-header .heading.secondary{display:none}:host{display:block;width:100%;text-align:center;background:var(--background-bright);color:var(--text-main);position:relative;-webkit-user-select:none;user-select:none}:host .calendar-content{display:flex;width:100%;height:min-content;position:static;top:0;left:0;overflow:hidden}:host .calendar-header{width:100%;display:flex;flex-flow:row nowrap;border-collapse:collapse;padding:1rem .8rem;-webkit-user-select:none;justify-content:space-between;align-items:center;cursor:default;border-bottom:1px solid var(--border)}:host .calendar-header .previous{width:30px;height:15px;display:inline-block;cursor:pointer}:host .calendar-header .previous:after{content:\"\";border-bottom:4px solid transparent;border-top:4px solid transparent;border-right:4px solid #aaa;display:inline-block;height:0;vertical-align:middle;width:0}:host .calendar-header .previous:hover:after{border-right:4px solid #4a89dc;cursor:pointer}:host .calendar-header .heading{flex:1;display:inline-block;vertical-align:middle;color:#4a89dc;font-weight:600}:host .calendar-header .heading .month{border-radius:2px;padding:3px 8px}:host .calendar-header .heading .month:hover{background:#4a89dc;color:#fff;cursor:pointer}:host .calendar-header .heading .year{border-radius:2px;padding:3px 8px}:host .calendar-header .heading .year:hover{background:#4a89dc;color:#fff;cursor:pointer}:host .calendar-header .next{width:30px;height:15px;display:inline-block;cursor:pointer}:host .calendar-header .next:before{content:\"\";border-bottom:4px solid transparent;border-top:4px solid transparent;border-left:4px solid #aaa;display:inline-block;height:0;vertical-align:middle;width:0}:host .calendar-header .next:hover:before{opacity:1;border-left:4px solid #4a89dc;cursor:pointer}\n"] }] }], ctorParameters: () => [{ type: i1.NovoLabelService }, { type: i0.ElementRef }, { type: i0.ChangeDetectorRef }, { type: i2.DomSanitizer }], propDecorators: { minYear: [{ type: Input }], maxYear: