UNPKG

ng-material-date-range-picker

Version:
759 lines (753 loc) 52.9 kB
import * as i0 from '@angular/core'; import { signal, ChangeDetectorRef, inject, ElementRef, Renderer2, Component, ChangeDetectionStrategy, Input, ViewChild, EventEmitter, computed, Output, NgModule } from '@angular/core'; import * as i1 from '@angular/material/datepicker'; import { DateRange, MatDatepickerModule } from '@angular/material/datepicker'; import * as i1$1 from '@angular/common'; import { DatePipe, CommonModule } from '@angular/common'; import * as i2 from '@angular/material/input'; import { MatInputModule } from '@angular/material/input'; import * as i3 from '@angular/material/form-field'; import { MatFormFieldModule } from '@angular/material/form-field'; import * as i4 from '@angular/cdk/overlay'; import { OverlayModule } from '@angular/cdk/overlay'; import * as i5 from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon'; import * as i6 from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button'; import * as i7 from '@angular/material/list'; import { MatListModule } from '@angular/material/list'; import * as i8 from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip'; import { FormsModule } from '@angular/forms'; import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { MatNativeDateModule } from '@angular/material/core'; /** * @(#)date-filter-enum.ts Sept 08, 2023 * * @author Aakash Kumar */ const ACTIVE_DATE_DEBOUNCE = 100; var DATE_OPTION_TYPE; (function (DATE_OPTION_TYPE) { DATE_OPTION_TYPE[DATE_OPTION_TYPE["DATE_DIFF"] = 1] = "DATE_DIFF"; DATE_OPTION_TYPE[DATE_OPTION_TYPE["LAST_MONTH"] = 2] = "LAST_MONTH"; DATE_OPTION_TYPE[DATE_OPTION_TYPE["THIS_MONTH"] = 3] = "THIS_MONTH"; DATE_OPTION_TYPE[DATE_OPTION_TYPE["YEAR_TO_DATE"] = 4] = "YEAR_TO_DATE"; DATE_OPTION_TYPE[DATE_OPTION_TYPE["CUSTOM"] = 5] = "CUSTOM"; DATE_OPTION_TYPE[DATE_OPTION_TYPE["MONTH_TO_DATE"] = 6] = "MONTH_TO_DATE"; DATE_OPTION_TYPE[DATE_OPTION_TYPE["WEEK_TO_DATE"] = 7] = "WEEK_TO_DATE"; })(DATE_OPTION_TYPE || (DATE_OPTION_TYPE = {})); /** * Resets the selection state for all options * and marks the given option as selected if provided. * * @param options - List of date options * @param selectedOption - Option to be marked as selected */ function resetOptionSelection(options, selectedOption) { options.forEach((option) => (option.isSelected = false)); if (selectedOption) { selectedOption.isSelected = true; } } /** * Marks the custom date option as selected. * * @param options - List of date options */ function selectCustomOption(options) { const customOption = options.find((option) => option.optionType === DATE_OPTION_TYPE.CUSTOM); if (customOption) customOption.isSelected = true; } /** * Returns a new date with the given year offset applied. * * @param offset - Number of years to add (negative for past years) * @returns Date object with updated year */ function getDateWithOffset(offset) { const date = new Date(); date.setFullYear(date.getFullYear() + offset); return date; } /** * Creates a deep clone of the provided object or array. * * @param data - Data to be cloned * @returns A deep copy of the data */ function getClone(data) { return JSON.parse(JSON.stringify(data)); } /** * Formats a date object into a string using Angular DatePipe. * * @param date - Date to be formatted * @param dateFormat - Desired date format (e.g., 'dd/MM/yyyy') * @returns Formatted date string */ function getDateString(date, dateFormat) { const datePipe = new DatePipe('en'); return datePipe.transform(date, dateFormat) ?? ''; } /** * Formats a date range into a string with start and end dates. * * @param range - Date range with start and end * @param dateFormat - Desired date format * @returns Formatted range string (e.g., '01/01/2023 - 07/01/2023') */ function getFormattedDateString(range, dateFormat) { if (!(range.start && range.end)) { return ''; } return (getDateString(range.start, dateFormat) + ' - ' + getDateString(range.end, dateFormat)); } /** * Creates a standardized date option object for dropdowns. * * @param label - Display label for the option * @param key - Option key from DEFAULT_DATE_OPTION_ENUM * @param dateDiff - Offset in days from current date (default: 0) * @param isVisible - Whether the option is visible (default: true) * @returns ISelectDateOption object */ function createOption(label, key, dateDiff = 0, isVisible = true) { return { optionLabel: label, optionType: key, dateDiff, isSelected: false, isVisible, }; } /** * Returns the date of the next month based on the given date. * * @param currDate - Current date * @returns A new Date object incremented by one month */ function getDateOfNextMonth(currDate) { const date = new Date(currDate); date.setMonth(currDate.getMonth() + 1); return date; } /** * Returns the first day of the month following the given date. * * @param currDate - The current date * @returns A Date object set to the first day of the next month */ function getFirstDateOfNextMonth(currDate) { return new Date(currDate.getFullYear(), currDate.getMonth() + 1, 1); } /** * Returns the number of days in the month of the given date. * * @param date The date to calculate the days for. * @returns Number of days in the month. */ function getDaysInMonth(date) { return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(); } /** * Overrides the `activeDate` setter for a MatCalendar instance, injecting custom handler logic * while preserving the original setter behavior. Useful for reacting to internal date navigation * events (e.g., month changes) in Angular Material's calendar. * * @param calendar - Instance of MatCalendar whose `activeDate` setter will be overridden. * @param cdref - ChangeDetectorRef to trigger view updates after the setter runs. * @param handler - Custom callback function executed whenever `activeDate` is set. */ function overrideActiveDateSetter(calendar, cdref, handler) { const proto = Object.getPrototypeOf(calendar); const descriptor = Object.getOwnPropertyDescriptor(proto, 'activeDate'); if (!(descriptor?.set && descriptor?.get)) { console.warn('overrideActiveDateSetter: activeDate setter/getter not found on MatCalendar prototype.'); return; } const originalSetter = descriptor.set; const originalGetter = descriptor.get; Object.defineProperty(calendar, 'activeDate', { configurable: true, enumerable: false, get() { return originalGetter.call(this); }, set(value) { const activeDate = { previous: originalGetter.call(this) ?? value, current: value, }; originalSetter.call(this, value); handler.call(this, activeDate); cdref.markForCheck(); }, }); } /** * @(#)default-date-options.ts Sept 08, 2023 * * @author Aakash Kumar */ const DEFAULT_DATE_OPTIONS = [ createOption('Today', DATE_OPTION_TYPE.DATE_DIFF, 0), createOption('Yesterday', DATE_OPTION_TYPE.DATE_DIFF, -1), createOption('Last 7 Days', DATE_OPTION_TYPE.DATE_DIFF, -7), createOption('Last 30 Days', DATE_OPTION_TYPE.DATE_DIFF, -30), createOption('Last Month', DATE_OPTION_TYPE.LAST_MONTH), createOption('This Month', DATE_OPTION_TYPE.THIS_MONTH), createOption('Month To Date', DATE_OPTION_TYPE.MONTH_TO_DATE), createOption('Week To Date', DATE_OPTION_TYPE.WEEK_TO_DATE, 0, false), createOption('Year To Date', DATE_OPTION_TYPE.YEAR_TO_DATE), createOption('Custom Range', DATE_OPTION_TYPE.CUSTOM), ]; /** * @(#)calendar.component.scss Sept 07, 2023 * * Custom Calendar Component that manages two side-by-side * month views with support for date range selection, hover * highlighting, and navigation controls. * * @author Aakash Kumar */ class CalendarComponent { constructor() { this.firstViewStartDate = signal(new Date()); this.secondViewStartDate = signal(getDateOfNextMonth(this.firstViewStartDate())); this.secondViewMinDate = signal(getFirstDateOfNextMonth(this.firstViewStartDate())); this.isAllowHoverEvent = false; this.cdref = inject(ChangeDetectorRef); this.el = inject(ElementRef); this.renderer = inject(Renderer2); } /** * Updates the selected date range and synchronizes both calendar views. */ set selectedDates(selectedDates) { this._selectedDates = selectedDates; if (!selectedDates || !(selectedDates.start && selectedDates.end)) return; const startDate = selectedDates.start ?? new Date(); const endDate = selectedDates.end; this.firstViewStartDate.set(startDate); this.secondViewMinDate.set(getFirstDateOfNextMonth(startDate)); const computedEndDate = startDate.getMonth() === endDate.getMonth() ? getDateOfNextMonth(endDate) : endDate; this.secondViewStartDate.set(computedEndDate); } get selectedDates() { return this._selectedDates; } /** * Lifecycle hook that is called after Angular has fully initialized * the component's view (and child views). * * Used here to attach hover events and register active date change * listeners once the calendar views are available in the DOM. */ ngAfterViewInit() { this.attachHoverEvent('firstCalendarView'); this.attachHoverEvent('secondCalendarView'); this.registerActiveDateChangeEvents(); } /** * Handles month selection in the first view. * * @param event - Selected month date */ monthSelected(viewName) { if (viewName === 'secondCalendarView') { this.removeDefaultFocus(this); } this.attachHoverEvent(viewName); } /** * Updates the selected date range when a date is clicked. * * @param date - Date clicked by the user */ updateDateRangeSelection(date) { const selectedDates = this.selectedDates; if (!selectedDates || (selectedDates.start && selectedDates.end) || (selectedDates.start && date && selectedDates.start > date)) { this._selectedDates = new DateRange(date, null); this.isAllowHoverEvent = true; } else { this.isAllowHoverEvent = false; this._selectedDates = new DateRange(selectedDates.start, date); } this.cdref.markForCheck(); } /** * Registers event handlers for active date changes on both calendar views. * * This method overrides the default `activeDate` property setter of each * calendar view to ensure custom handlers are executed whenever the * active date changes. */ registerActiveDateChangeEvents() { overrideActiveDateSetter(this.firstCalendarView, this.cdref, this.onFirstViewActiveDateChange.bind(this)); overrideActiveDateSetter(this.secondCalendarView, this.cdref, this.onSecondViewActiveDateChange.bind(this)); } /** * Handles the event when the active date of the first calendar view changes. * * @param activeDate - Object containing `previous` and `current` date values. */ onFirstViewActiveDateChange(activeDate) { const handler = this.isPrevious(activeDate) ? () => this.handleFirstViewPrevEvent(activeDate) : () => this.handleFirstViewNextEvent(activeDate.current); // Delay execution because active date event fires before view update setTimeout(handler, ACTIVE_DATE_DEBOUNCE); } /** * Handles the event when the active date of the second calendar view changes. * * @param activeDate - Object containing `previous` and `current` date values. */ onSecondViewActiveDateChange(activeDate) { this.attachHoverEvent('secondCalendarView'); } /** * Handles the "next" navigation event for the first calendar view. * * @param currDate - The currently active date in the first calendar view. * @param force - Optional flag that can be used to enforce updates (not used in current logic). */ handleFirstViewNextEvent(currDate, force) { if (this.firstCalendarView.currentView.toLocaleLowerCase() !== 'month') { return; } this.attachHoverEvent('firstCalendarView'); const nextMonthDate = getFirstDateOfNextMonth(currDate); let secondViewActiveDate = this.secondCalendarView.activeDate; if (nextMonthDate < secondViewActiveDate) { this.secondViewMinDate.set(nextMonthDate); this.attachHoverEvent('secondCalendarView'); return; } secondViewActiveDate = getDateOfNextMonth(currDate); this.secondViewMinDate.set(nextMonthDate); this.secondCalendarView.activeDate = secondViewActiveDate; this.cdref.detectChanges(); } /** * Handles the "previous" navigation event for the first calendar view. * * @param activeDate - Object containing `previous` and `current` date values. */ handleFirstViewPrevEvent(activeDate) { if (this.firstCalendarView.currentView.toLocaleLowerCase() !== 'month') { return; } this.secondViewMinDate.set(getFirstDateOfNextMonth(activeDate.current)); this.attachHoverEvent('firstCalendarView'); this.attachHoverEvent('secondCalendarView'); } /** * Checks whether the previous date is greater than the current date. * * @param activeDate - Object containing `previous` and `current` date values. * @returns `true` if the previous date is later than the current date, otherwise `false`. */ isPrevious(activeDate) { return activeDate.previous > activeDate.current; } /** * Attaches hover events to all date cells in the first view. */ attachHoverEvent(viewId) { const nodes = this.el.nativeElement.querySelectorAll(`#${viewId} .mat-calendar-body-cell`); setTimeout(() => this.addHoverEvents(nodes), ACTIVE_DATE_DEBOUNCE); } /** * Removes active focus from the second view. * * @param classRef - Reference to this component */ removeDefaultFocus(classRef) { setTimeout(() => { const btn = classRef.el.nativeElement.querySelectorAll('#secondCalendarView button.mat-calendar-body-active'); if (btn?.length) { btn[0].blur(); } }, 1); } /** * Updates the selection range dynamically on hover. * * @param date - Hovered date */ updateSelectionOnMouseHover(date) { const selectedDates = this.selectedDates; if (selectedDates?.start && date && selectedDates.start < date) { const dateRange = new DateRange(selectedDates.start, date); this.firstCalendarView.selected = dateRange; this.secondCalendarView.selected = dateRange; this.firstCalendarView['_changeDetectorRef'].markForCheck(); this.secondCalendarView['_changeDetectorRef'].markForCheck(); this.isAllowHoverEvent = true; } } /** * Attaches hover events to given nodes to update range selection. * * @param nodes - Date cell nodes */ addHoverEvents(nodes) { if (!nodes) { return; } Array.from(nodes).forEach((button) => { this.renderer.listen(button, 'mouseover', (event) => { if (this.isAllowHoverEvent) { const date = new Date(event.target['ariaLabel']); this.updateSelectionOnMouseHover(date); } }); }); this.firstCalendarView['_changeDetectorRef'].markForCheck(); this.secondCalendarView['_changeDetectorRef'].markForCheck(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.0.6", ngImport: i0, type: CalendarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.0.6", type: CalendarComponent, selector: "lib-calendar", inputs: { minDate: "minDate", maxDate: "maxDate", selectedDates: "selectedDates" }, viewQueries: [{ propertyName: "firstCalendarView", first: true, predicate: ["firstCalendarView"], descendants: true }, { propertyName: "secondCalendarView", first: true, predicate: ["secondCalendarView"], descendants: true }], ngImport: i0, template: "<!--**\r\n * @(#)calendar.component.html Sept 07, 2023\r\n\r\n * @author Aakash Kumar\r\n *-->\r\n<div class=\"calendar-container\">\r\n <div class=\"first-view\">\r\n <mat-calendar id=\"firstCalendarView\" #firstCalendarView [startAt]=\"firstViewStartDate()\" [selected]=\"selectedDates\"\r\n (selectedChange)=\"updateDateRangeSelection($event)\" (monthSelected)=\"monthSelected('firstCalendarView')\" [minDate]=\"minDate\"\r\n [maxDate]=\"maxDate\"></mat-calendar>\r\n </div>\r\n <div class=\"second-view\">\r\n <mat-calendar id=\"secondCalendarView\" #secondCalendarView [startAt]=\"secondViewStartDate()\" [minDate]=\"secondViewMinDate()\"\r\n [maxDate]=\"maxDate\" [selected]=\"selectedDates\" (selectedChange)=\"updateDateRangeSelection($event)\"\r\n (monthSelected)=\"monthSelected('secondCalendarView')\"></mat-calendar>\r\n </div>\r\n</div>\r\n", styles: [".mat-calendar{min-width:250px}.calendar-container{width:100%;display:block;float:left}.first-view,.second-view{width:50%;display:block;float:left}.first-view,.second-view{margin-top:.5rem}@media (max-width: 490px){.first-view,.second-view{width:100%}}\n"], dependencies: [{ kind: "component", type: i1.MatCalendar, selector: "mat-calendar", inputs: ["headerComponent", "startAt", "startView", "selected", "minDate", "maxDate", "dateFilter", "dateClass", "comparisonStart", "comparisonEnd", "startDateAccessibleName", "endDateAccessibleName"], outputs: ["selectedChange", "yearSelected", "monthSelected", "viewChanged", "_userSelection", "_userDragDrop"], exportAs: ["matCalendar"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.6", ngImport: i0, type: CalendarComponent, decorators: [{ type: Component, args: [{ selector: 'lib-calendar', changeDetection: ChangeDetectionStrategy.OnPush, template: "<!--**\r\n * @(#)calendar.component.html Sept 07, 2023\r\n\r\n * @author Aakash Kumar\r\n *-->\r\n<div class=\"calendar-container\">\r\n <div class=\"first-view\">\r\n <mat-calendar id=\"firstCalendarView\" #firstCalendarView [startAt]=\"firstViewStartDate()\" [selected]=\"selectedDates\"\r\n (selectedChange)=\"updateDateRangeSelection($event)\" (monthSelected)=\"monthSelected('firstCalendarView')\" [minDate]=\"minDate\"\r\n [maxDate]=\"maxDate\"></mat-calendar>\r\n </div>\r\n <div class=\"second-view\">\r\n <mat-calendar id=\"secondCalendarView\" #secondCalendarView [startAt]=\"secondViewStartDate()\" [minDate]=\"secondViewMinDate()\"\r\n [maxDate]=\"maxDate\" [selected]=\"selectedDates\" (selectedChange)=\"updateDateRangeSelection($event)\"\r\n (monthSelected)=\"monthSelected('secondCalendarView')\"></mat-calendar>\r\n </div>\r\n</div>\r\n", styles: [".mat-calendar{min-width:250px}.calendar-container{width:100%;display:block;float:left}.first-view,.second-view{width:50%;display:block;float:left}.first-view,.second-view{margin-top:.5rem}@media (max-width: 490px){.first-view,.second-view{width:100%}}\n"] }] }], propDecorators: { minDate: [{ type: Input }], maxDate: [{ type: Input }], firstCalendarView: [{ type: ViewChild, args: ['firstCalendarView'] }], secondCalendarView: [{ type: ViewChild, args: ['secondCalendarView'] }], selectedDates: [{ type: Input }] } }); /** * @(#)ng-date-picker.component.ts Sept 05, 2023 * * @author Aakash Kumar */ class NgDatePickerComponent { constructor() { this.isDateOptionList = false; this.isCustomRange = false; this.inputLabel = 'Date Range'; this.staticOptionId = 'static-options'; this.dynamicOptionId = 'dynamic-options'; this.calendarId = 'custom-calendar'; this.enableDefaultOptions = true; this.dateFormat = 'dd/MM/yyyy'; this.isShowStaticDefaultOptions = false; this.hideDefaultOptions = false; this.cdkConnectedOverlayOffsetX = 0; this.cdkConnectedOverlayOffsetY = 0; this.listCdkConnectedOverlayOffsetY = 0; this.listCdkConnectedOverlayOffsetX = 0; this.selectedOptionIndex = 3; this.displaySelectedLabel = false; this.cdkConnectedOverlayPush = true; this.cdkConnectedOverlayPositions = []; // default min date is current date - 10 years. this.minDate = getDateWithOffset(-10); // default max date is current date + 10 years. this.maxDate = getDateWithOffset(10); this.onDateSelectionChanged = new EventEmitter(); this.dateListOptions = new EventEmitter(); this.cdref = inject(ChangeDetectorRef); this.el = inject(ElementRef); this._dateOptions = signal([]); this.visibleOptions = computed(() => this._dateOptions().filter((op) => op.isVisible)); } set dateDropDownOptions(defaultDateList) { const options = [ ...(this.enableDefaultOptions ? getClone(DEFAULT_DATE_OPTIONS) : []), ...(defaultDateList ?? []), ]; this._dateOptions.set(options); } get dateDropDownOptions() { return this._dateOptions() ?? []; } ngOnInit() { if (this.isDefaultInitRequired()) { this.initDefaultOptions(); } this.dateListOptions.emit(this.dateDropDownOptions); } ngAfterViewInit() { this.updateDefaultDatesValues(); } /** * Toggles the visibility of the default date option list. * If the custom option is selected, toggles the custom date range view instead. * * @param event Optional MouseEvent triggering the toggle. */ toggleDateOptionSelectionList(event) { event?.preventDefault(); event?.stopImmediatePropagation(); const isCustomSelected = this.dateDropDownOptions.find((option) => option.isSelected) ?.optionType === DATE_OPTION_TYPE.CUSTOM; if (isCustomSelected) { this.toggleCustomDateRangeView(); return; } this.isDateOptionList = !this.isDateOptionList; } /** * Updates the custom date range selection from the input. * * @param input The HTML input element associated with the date picker. * @param selectedDates The selected date range. */ updateCustomRange(input, selectedDates) { if (this.isCustomRange) { resetOptionSelection(this.dateDropDownOptions); selectCustomOption(this.dateDropDownOptions); this.isCustomRange = false; } const start = selectedDates?.start ?? new Date(); const end = selectedDates?.end ?? new Date(); this.updateSelectedDates(input, start, end, null); } /** * Updates the selection when a specific date option is clicked. * * @param option The selected date option. * @param input The HTML input element to update with selected dates. */ updateSelection(option, input) { this.isDateOptionList = false; this.isCustomRange = option.optionType === DATE_OPTION_TYPE.CUSTOM; if (!this.isCustomRange) { resetOptionSelection(this.dateDropDownOptions, option); this.updateDateOnOptionSelect(option, input); } this.cdref.markForCheck(); } /** * Toggles the custom date range selection view visibility. */ toggleCustomDateRangeView() { this.isCustomRange = !this.isCustomRange; } /** * Clears the currently selected dates and resets all related properties. * * @param event The MouseEvent triggering the clear action. */ clearSelection(event) { event?.stopImmediatePropagation(); this.minDate = getDateWithOffset(-10); this.maxDate = getDateWithOffset(10); this.selectedDates = null; resetOptionSelection(this.dateDropDownOptions); this.clearDateInput(); this.cdref.markForCheck(); const selectedDateEventData = { range: null, selectedOption: null, }; this.onDateSelectionChanged.emit(selectedDateEventData); } /** * Clears the input field value for the date picker. */ clearDateInput() { const dateInputField = this.el.nativeElement.querySelector('#date-input-field'); if (dateInputField) { dateInputField.value = ''; } } /** * Updates selected dates based on a selected option and input element. * * @param option The selected date option. * @param input The HTML input element to update. */ updateDateOnOptionSelect(option, input) { // If there is a callback function, use it to get the date range if (option?.callBackFunction) { const dateRange = option.callBackFunction(); if (dateRange?.start && dateRange?.end) { this.updateSelectedDates(input, dateRange.start, dateRange.end, option); return; } } this.updateDateWithSelectedOption(option, input); } /** * Calculates and updates the start and end dates based on the selected option. * * @param option The selected date option. * @param input The HTML input element to update. */ updateDateWithSelectedOption(option, input) { const currDate = new Date(); let startDate = new Date(); let lastDate = new Date(); // Determine the date range based on the option key switch (option.optionType) { case DATE_OPTION_TYPE.DATE_DIFF: startDate.setDate(startDate.getDate() + (option.dateDiff ?? 0)); break; case DATE_OPTION_TYPE.LAST_MONTH: currDate.setMonth(currDate.getMonth() - 1); startDate = new Date(currDate.getFullYear(), currDate.getMonth(), 1); lastDate = new Date(currDate.getFullYear(), currDate.getMonth(), getDaysInMonth(currDate)); break; case DATE_OPTION_TYPE.THIS_MONTH: startDate = new Date(currDate.getFullYear(), currDate.getMonth(), 1); lastDate = new Date(currDate.getFullYear(), currDate.getMonth(), getDaysInMonth(currDate)); break; case DATE_OPTION_TYPE.YEAR_TO_DATE: startDate = new Date(currDate.getFullYear(), 0, 1); break; case DATE_OPTION_TYPE.MONTH_TO_DATE: startDate = new Date(currDate.getFullYear(), currDate.getMonth(), 1); break; default: break; } // Update the selected dates this.updateSelectedDates(input, startDate, lastDate, option); } /** * Updates the date range and input display. * * @param input The HTML input element. * @param start Start date of the range. * @param end End date of the range. * @param opt Optional selected date option. */ updateSelectedDates(input, start, end, opt) { const range = new DateRange(start, end); this.selectedDates = range; const label = this.displaySelectedLabel ? opt?.optionLabel : null; const rangeLabel = `${getDateString(start, this.dateFormat)} - ${getDateString(end, this.dateFormat)}`; input.value = label ?? rangeLabel; this.onDateSelectionChanged.emit({ range, selectedOption: this.dateDropDownOptions.find((o) => o.isSelected) ?? null, }); this.cdref.markForCheck(); } /** * Updates the input and internal state with default dates on initialization. */ updateDefaultDatesValues() { const input = this.el.nativeElement.querySelector('#date-input-field'); if (this.selectedDates?.start && this.selectedDates?.end) { this._dateOptions().find((option) => option.optionType === DATE_OPTION_TYPE.CUSTOM).isSelected = true; input.value = getFormattedDateString(this.selectedDates, this.dateFormat); this.cdref.detectChanges(); return; } const selectedOptions = this._dateOptions().find((option) => option.isSelected); if (selectedOptions && selectedOptions.optionType !== DATE_OPTION_TYPE.CUSTOM) { this.updatedFromListValueSelection(selectedOptions, input); this.cdref.detectChanges(); } } /** * Updates the input and selected dates based on a selected option from the list. * * @param selectedOption The selected date option. * @param input The HTML input element to update. */ updatedFromListValueSelection(selectedOption, input) { // This will update value if option is selected from default list. if (!selectedOption['callBackFunction']) { this.updateDateOnOptionSelect(selectedOption, input); return; } // This will update value if option is selected from provided custom list. const dateRange = selectedOption.callBackFunction(); this.updateSelectedDates(input, dateRange.start ?? new Date(), dateRange.end ?? new Date(), selectedOption); } /** * Checks whether default initialization of options is required. * * @returns True if default options need to be initialized, otherwise false. */ isDefaultInitRequired() { return this.enableDefaultOptions && !this._dateOptions.length; } /** * Initializes the default date options with the selected index. */ initDefaultOptions() { const options = getClone(DEFAULT_DATE_OPTIONS).map((opt, idx) => ({ ...opt, isSelected: idx === this.selectedOptionIndex, })); this._dateOptions.set(options); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.0.6", ngImport: i0, type: NgDatePickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.0.6", type: NgDatePickerComponent, selector: "ng-date-range-picker", inputs: { inputLabel: "inputLabel", staticOptionId: "staticOptionId", dynamicOptionId: "dynamicOptionId", calendarId: "calendarId", enableDefaultOptions: "enableDefaultOptions", selectedDates: "selectedDates", dateFormat: "dateFormat", isShowStaticDefaultOptions: "isShowStaticDefaultOptions", hideDefaultOptions: "hideDefaultOptions", cdkConnectedOverlayOffsetX: "cdkConnectedOverlayOffsetX", cdkConnectedOverlayOffsetY: "cdkConnectedOverlayOffsetY", listCdkConnectedOverlayOffsetY: "listCdkConnectedOverlayOffsetY", listCdkConnectedOverlayOffsetX: "listCdkConnectedOverlayOffsetX", selectedOptionIndex: "selectedOptionIndex", displaySelectedLabel: "displaySelectedLabel", cdkConnectedOverlayPush: "cdkConnectedOverlayPush", cdkConnectedOverlayPositions: "cdkConnectedOverlayPositions", minDate: "minDate", maxDate: "maxDate", dateDropDownOptions: "dateDropDownOptions" }, outputs: { onDateSelectionChanged: "onDateSelectionChanged", dateListOptions: "dateListOptions" }, ngImport: i0, template: "<!--**\r\n * @(#)ng-date-picker.component.html Sept 05, 2023\r\n\r\n * @author Aakash Kumar\r\n *-->\r\n<div class=\"date-picker-main\" cdkOverlayOrigin #trigger>\r\n <mat-form-field class=\"w-full\" [class]=\"{'display-hidden':isShowStaticDefaultOptions}\" (click)=\"toggleDateOptionSelectionList($event)\">\r\n <mat-label (click)=\"toggleDateOptionSelectionList($event)\">{{inputLabel}}</mat-label>\r\n <input matInput readonly=\"readonly\" #dateInput class=\"cursor-pointer\" id=\"date-input-field\">\r\n <button mat-icon-button matSuffix class=\"cursor-pointer pe-0 ps-0\" matTooltip=\"Clear\" *ngIf=\"!!dateInput.value\"\r\n (click)=\"clearSelection($event)\"><mat-icon>clear</mat-icon></button>\r\n <button mat-icon-button matSuffix class=\"cursor-pointer\"> <mat-icon>date_range</mat-icon></button>\r\n </mat-form-field>\r\n\r\n @if(dateDropDownOptions.length && isShowStaticDefaultOptions) {\r\n <ng-container *ngTemplateOutlet=\"dateOptionList; context: { $implicit: visibleOptions(), dateInput: dateInput,\r\n optionId: staticOptionId, className:'w-full custom-ckd-container range-input'\r\n }\"></ng-container>\r\n }\r\n\r\n <ng-template cdkConnectedOverlay [cdkConnectedOverlayHasBackdrop]=\"false\" [cdkConnectedOverlayOrigin]=\"trigger\"\r\n [cdkConnectedOverlayOpen]=\"isDateOptionList\" [cdkConnectedOverlayPush]=\"cdkConnectedOverlayPush\"\r\n [cdkConnectedOverlayOffsetX]=\"listCdkConnectedOverlayOffsetX\" [cdkConnectedOverlayOffsetY]=\"listCdkConnectedOverlayOffsetY\"\r\n (overlayOutsideClick)=\"!isShowStaticDefaultOptions && toggleDateOptionSelectionList()\">\r\n\r\n @if(dateDropDownOptions.length && !isShowStaticDefaultOptions) {\r\n <ng-container *ngTemplateOutlet=\"dateOptionList; context: { $implicit: visibleOptions(), dateInput: dateInput,\r\n optionId: dynamicOptionId, className:'w-full custom-ckd-container range-input'\r\n }\"></ng-container>\r\n }\r\n </ng-template>\r\n\r\n <ng-template cdkConnectedOverlay [cdkConnectedOverlayHasBackdrop]=\"false\" [cdkConnectedOverlayOrigin]=\"trigger\"\r\n [cdkConnectedOverlayOpen]=\"isCustomRange\" [cdkConnectedOverlayPush]=\"cdkConnectedOverlayPush\"\r\n [cdkConnectedOverlayPositions]=\"cdkConnectedOverlayPositions\" [cdkConnectedOverlayOffsetX]=\"cdkConnectedOverlayOffsetX\"\r\n [cdkConnectedOverlayOffsetY]=\"cdkConnectedOverlayOffsetY\" (overlayOutsideClick)=\"toggleCustomDateRangeView()\">\r\n <div class=\"custom-ckd-container custom-calendar-container\" [class]=\"{'without-default-opt':hideDefaultOptions}\">\r\n <div class=\"row-1\">\r\n <div class=\"pt-custom br-right column-1\" *ngIf=\"!hideDefaultOptions\">\r\n <ng-container *ngTemplateOutlet=\"dateOptionList; context: { $implicit: visibleOptions(), dateInput: dateInput}\"></ng-container>\r\n </div>\r\n <div class=\"mt-2 column-2\"><lib-calendar [selectedDates]=\"selectedDates\" #calendar [minDate]=\"minDate\"\r\n [maxDate]=\"maxDate\"></lib-calendar></div>\r\n </div>\r\n <div class=\"row-2 br-top\">\r\n <div class=\"text-end my-2 w-full\">\r\n <div class=\"footer-content\">\r\n <span id=\"range-label-text\">\r\n {{calendar?.selectedDates?.start | date: dateFormat}}\r\n @if (calendar?.selectedDates?.end) {\r\n <span> - {{calendar.selectedDates?.end | date:\r\n dateFormat}} </span>\r\n }\r\n </span>\r\n <div class=\"d-inline buttons\">\r\n <button mat-button mat-raised-button class=\"p-3 mx-2\" (click)=\"isCustomRange=false;\">Cancel</button>\r\n <button mat-button mat-raised-button color=\"primary\" class=\"ms-2 p-3\"\r\n [class.disabled]=\"!(calendar?.selectedDates?.start && calendar?.selectedDates?.end)\"\r\n (click)=\"updateCustomRange(dateInput,calendar.selectedDates);\">&nbsp;Apply&nbsp;</button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </ng-template>\r\n</div>\r\n\r\n<ng-template #dateOptionList let-options let-input=\"dateInput\" let-optionId=\"optionId\" let-className=\"className\">\r\n <mat-action-list [ngClass]=\"className\" [id]=\"optionId\">\r\n @for (option of options; track option.optionLabel) {\r\n <mat-list-item [activated]=\"option.isSelected\" (click)=\"updateSelection(option, input)\">\r\n {{option.optionLabel}}\r\n </mat-list-item>\r\n }\r\n </mat-action-list>\r\n</ng-template>\r\n", styles: ["::ng-deep .cdk-overlay-dark-backdrop{background:none}mat-list-item{height:35px}::ng-deep .cdk-overlay-pane:has(.custom-ckd-container){width:100%;background-color:var(--bg-color, white);max-height:100vh;overflow-y:auto;overflow-x:hidden;max-width:700px;margin-top:-22px;border:1px solid var(--border-color, #ddd)}::ng-deep .cdk-overlay-pane:has(.range-input){max-width:250px}.br-top{border-top:1px solid var(--border-color, #ddd)}.br-right{border-right:1px solid var(--border-color, #ddd)}.disabled{pointer-events:none;opacity:.5}.mat-button,.mdc-button{font-family:var(--mdc-list-list-item-label-text-font);line-height:var(--mdc-list-list-item-label-text-line-height);font-size:var(--mdc-list-list-item-label-text-size);font-weight:var(--mdc-list-list-item-label-text-weight);letter-spacing:var(--mdc-list-list-item-label-text-tracking)}.w-full{width:100%}.display-hidden{display:none}.custom-calendar-container,.row-1,.row-2{width:100%;display:block;float:left}.row-2{padding:.7rem}.footer-content{width:100%;align-items:center;display:flex;text-align:right;justify-content:end;gap:10px;margin-right:2rem;text-overflow:ellipsis}.buttons{margin-right:1.5rem;display:flex;gap:10px}.column-1{width:25%;display:block;float:left}.column-2{width:73%;display:block;float:left}@media (max-width: 400px){.footer-content{display:block}#range-label-text{margin-right:1.5rem}.buttons{float:right;margin-top:20px}}@media (max-width: 650px){.column-1,.column-2{width:100%}}@media (min-width: 650px){.column-1{width:25%}.column-2{width:74%}}.without-default-opt .column-1{display:none}.without-default-opt .column-2{width:100%;display:block;float:left}.pe-0{padding-right:0}.ps-0{padding-left:0}\n"], dependencies: [{ kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i2.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "directive", type: i4.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i4.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: i5.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i6.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i6.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: i7.MatActionList, selector: "mat-action-list", exportAs: ["matActionList"] }, { kind: "component", type: i7.MatListItem, selector: "mat-list-item, a[mat-list-item], button[mat-list-item]", inputs: ["activated"], exportAs: ["matListItem"] }, { kind: "directive", type: i8.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: CalendarComponent, selector: "lib-calendar", inputs: ["minDate", "maxDate", "selectedDates"] }, { kind: "pipe", type: i1$1.DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.6", ngImport: i0, type: NgDatePickerComponent, decorators: [{ type: Component, args: [{ selector: 'ng-date-range-picker', changeDetection: ChangeDetectionStrategy.OnPush, template: "<!--**\r\n * @(#)ng-date-picker.component.html Sept 05, 2023\r\n\r\n * @author Aakash Kumar\r\n *-->\r\n<div class=\"date-picker-main\" cdkOverlayOrigin #trigger>\r\n <mat-form-field class=\"w-full\" [class]=\"{'display-hidden':isShowStaticDefaultOptions}\" (click)=\"toggleDateOptionSelectionList($event)\">\r\n <mat-label (click)=\"toggleDateOptionSelectionList($event)\">{{inputLabel}}</mat-label>\r\n <input matInput readonly=\"readonly\" #dateInput class=\"cursor-pointer\" id=\"date-input-field\">\r\n <button mat-icon-button matSuffix class=\"cursor-pointer pe-0 ps-0\" matTooltip=\"Clear\" *ngIf=\"!!dateInput.value\"\r\n (click)=\"clearSelection($event)\"><mat-icon>clear</mat-icon></button>\r\n <button mat-icon-button matSuffix class=\"cursor-pointer\"> <mat-icon>date_range</mat-icon></button>\r\n </mat-form-field>\r\n\r\n @if(dateDropDownOptions.length && isShowStaticDefaultOptions) {\r\n <ng-container *ngTemplateOutlet=\"dateOptionList; context: { $implicit: visibleOptions(), dateInput: dateInput,\r\n optionId: staticOptionId, className:'w-full custom-ckd-container range-input'\r\n }\"></ng-container>\r\n }\r\n\r\n <ng-template cdkConnectedOverlay [cdkConnectedOverlayHasBackdrop]=\"false\" [cdkConnectedOverlayOrigin]=\"trigger\"\r\n [cdkConnectedOverlayOpen]=\"isDateOptionList\" [cdkConnectedOverlayPush]=\"cdkConnectedOverlayPush\"\r\n [cdkConnectedOverlayOffsetX]=\"listCdkConnectedOverlayOffsetX\" [cdkConnectedOverlayOffsetY]=\"listCdkConnectedOverlayOffsetY\"\r\n (overlayOutsideClick)=\"!isShowStaticDefaultOptions && toggleDateOptionSelectionList()\">\r\n\r\n @if(dateDropDownOptions.length && !isShowStaticDefaultOptions) {\r\n <ng-container *ngTemplateOutlet=\"dateOptionList; context: { $implicit: visibleOptions(), dateInput: dateInput,\r\n optionId: dynamicOptionId, className:'w-full custom-ckd-container range-input'\r\n }\"></ng-container>\r\n }\r\n </ng-template>\r\n\r\n <ng-template cdkConnectedOverlay [cdkConnectedOverlayHasBackdrop]=\"false\" [cdkConnectedOverlayOrigin]=\"trigger\"\r\n [cdkConnectedOverlayOpen]=\"isCustomRange\" [cdkConnectedOverlayPush]=\"cdkConnectedOverlayPush\"\r\n [cdkConnectedOverlayPositions]=\"cdkConnectedOverlayPositions\" [cdkConnectedOverlayOffsetX]=\"cdkConnectedOverlayOffsetX\"\r\n [cdkConnectedOverlayOffsetY]=\"cdkConnectedOverlayOffsetY\" (overlayOutsideClick)=\"toggleCustomDateRangeView()\">\r\n <div class=\"custom-ckd-container custom-calendar-container\" [class]=\"{'without-default-opt':hideDefaultOptions}\">\r\n <div class=\"row-1\">\r\n <div class=\"pt-custom br-right column-1\" *ngIf=\"!hideDefaultOptions\">\r\n <ng-container *ngTemplateOutlet=\"dateOptionList; context: { $implicit: visibleOptions(), dateInput: dateInput}\"></ng-container>\r\n </div>\r\n <div class=\"mt-2 column-2\"><lib-calendar [selectedDates]=\"selectedDates\" #calendar [minDate]=\"minDate\"\r\n [maxDate]=\"maxDate\"></lib-calendar></div>\r\n </div>\r\n <div class=\"row-2 br-top\">\r\n <div class=\"text-end my-2 w-full\">\r\n <div class=\"footer-content\">\r\n <span id=\"range-label-text\">\r\n {{calendar?.selectedDates?.start | date: dateFormat}}\r\n @if (calendar?.selectedDates?.end) {\r\n <span> - {{calendar.selectedDates?.end | date:\r\n dateFormat}} </span>\r\n }\r\n </span>\r\n <div class=\"d-inline buttons\">\r\n <button mat-button mat-raised-button class=\"p-3 mx-2\" (click)=\"isCustomRange=false;\">Cancel</button>\r\n <button mat-button mat-raised-button color=\"primary\" class=\"ms-2 p-3\"\r\n [class.disabled]=\"!(calendar?.selectedDates?.start && calendar?.selectedDates?.end)\"\r\n (click)=\"updateCustomRange(dateInput,calendar.selectedDates);\">&nbsp;Apply&nbsp;</button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </ng-template>\r\n</div>\r\n\r\n<ng-template #dateOptionList let-options let-input=\"dateInput\" let-optionId=\"optionId\" let-className=\"className\">\r\n <mat-action-list [ngClass]=\"className\" [id]=\"optionId\">\r\n @for (option of options; track option.optionLabel) {\r\n <mat-list-item [activated]=\"option.isSelected\" (click)=\"updateSelection(option, input)\">\r\n {{option.optionLabel}}\r\n </mat-list-item>\r\n }\r\n </mat-action-list>\r\n</ng-template>\r\n", styles: ["::ng-deep .cdk-overlay-dark-backdrop{background:none}mat-list-item{height:35px}::ng-deep .cdk-overlay-pane:has(.custom-ckd-container){width:100%;background-color:var(--bg-color, white);max-height:100vh;overflow-y:auto;overflow-x:hidden;max-width:700px;margin-top:-22px;border:1px solid var(--border-color, #ddd)}::ng-deep .cdk-overlay-pane:has(.range-input){max-width:250px}.br-top{border-top:1px solid var(--border-color, #ddd)}.br-right{border-right:1px solid var(--border-color, #ddd)}.disabled{pointer-events:none;opacity:.5}.mat-button,.mdc-button{font-family:var(--mdc-list-list-item-label-text-font);line-height:var(--mdc-list-list-item-label-text-line-height);font-size:var(--mdc-list-list-item-label-text-size);font-weight:var(--mdc-list-list-item-label-text-weight);letter-spacing:var(--mdc-list-list-item-label-text-tracking)}.w-full{width:100%}.display-hidden{display:none}.custom-calendar-container,.row-1,.row-2{width:100%;display:block;float:left}.row-2{padding:.7rem}.footer-content{width:100%;align-items:center;display:flex;text-align:right;justify-content:end;gap:10px;margin-right:2rem;text-overflow:ellipsis}.buttons{margin-right:1.5rem;display:flex;gap:10px}.column-1{width:25%;display:block;float:left}.column-2{width:73%;display:block;float:left}@media (max-width: 400px){.footer-content{display:block}#range-label-text{margin-right:1.5rem}.buttons{float:right;margin-top:20px}}@media (max-width: 650px){.column-1,.column-2{width:100%}}@media (min-width: 650px){.column-1{width:25%}.column-2{width:74%}}.without-default-opt .column-1{display:none}.without-default-opt .column-2{width:100%;display:block;float:left}.pe-0{padding-right:0}.ps-0{padding-left:0}\n"] }] }], ctorParameters: () => [], propDecorators: { inputLabel: [{ type: Input }], staticOptionId: [{ type: Input }], dynamicOptionId: [{ type: Input }], calendarId: [{ type: Input }], enableDefaultOptions: [{ type: Input }], selectedDates: [{ type: Input }], dateFormat: [{ type: Input }], isShowStaticDefaultOptions: [{ type: Input }], hideDefaultOptions: [{ type: Input }], cdkConnectedOverlayOffsetX: [{ type: Input }], cdkConnectedOverlayOffsetY: [{ type: Input }], listCdkConnectedOverlayOffsetY: [{ type: Input }], listCdkConnectedOverlayOffsetX: [{ type: Input }], selectedOptionIndex: [{ type: Input }], displaySelectedLabel: [{ type: Input }], cdkConnectedOverlayPush: [{ type: Input }], cdkConnectedOverlayPositions: [{ type: Input }], minDate: [{ type: Input }], maxDate: [{ type: Input }], onDateSelectionChanged: [{ type: Output }], dateListOptions: [{ type: Output }], dateDropDownOptions: [{ type: Input }] } }); /** * Default implementation of a selectable date opt