UNPKG

@ng-bootstrap/ng-bootstrap

Version:
1,124 lines (1,110 loc) 165 kB
import * as i0 from '@angular/core'; import { Injectable, inject, LOCALE_ID, Input, ViewEncapsulation, ChangeDetectionStrategy, Component, EventEmitter, ElementRef, ViewChild, Output, TemplateRef, Directive, Injector, NgZone, DestroyRef, ChangeDetectorRef, afterNextRender, forwardRef, ContentChild, ViewContainerRef, DOCUMENT, afterEveryRender, NgModule } from '@angular/core'; import { Subject, fromEvent, merge } from 'rxjs'; import { filter } from 'rxjs/operators'; import { NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms'; import { formatDate, NgTemplateOutlet } from '@angular/common'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { isInteger, toInteger, isNumber, padNumber, ngbPositioning, ngbFocusTrap, isString, addPopperOffset, ngbAutoClose } from './_ngb-ngbootstrap-utilities.mjs'; /** * A simple class that represents a date that datepicker also uses internally. * * It is the implementation of the `NgbDateStruct` interface that adds some convenience methods, * like `.equals()`, `.before()`, etc. * * All datepicker APIs consume `NgbDateStruct`, but return `NgbDate`. * * In many cases it is simpler to manipulate these objects together with * [`NgbCalendar`](#/components/datepicker/api#NgbCalendar) than native JS Dates. * * See the [date format overview](#/components/datepicker/overview#date-model) for more details. * * @since 3.0.0 */ class NgbDate { /** * A **static method** that creates a new date object from the `NgbDateStruct`, * * ex. `NgbDate.from({year: 2000, month: 5, day: 1})`. * * If the `date` is already of `NgbDate` type, the method will return the same object. */ static from(date) { if (date instanceof NgbDate) { return date; } return date ? new NgbDate(date.year, date.month, date.day) : null; } constructor(year, month, day) { this.year = isInteger(year) ? year : null; this.month = isInteger(month) ? month : null; this.day = isInteger(day) ? day : null; } /** * Checks if the current date is equal to another date. */ equals(other) { return other != null && this.year === other.year && this.month === other.month && this.day === other.day; } /** * Checks if the current date is before another date. */ before(other) { if (!other) { return false; } if (this.year === other.year) { if (this.month === other.month) { return this.day === other.day ? false : this.day < other.day; } else { return this.month < other.month; } } else { return this.year < other.year; } } /** * Checks if the current date is after another date. */ after(other) { if (!other) { return false; } if (this.year === other.year) { if (this.month === other.month) { return this.day === other.day ? false : this.day > other.day; } else { return this.month > other.month; } } else { return this.year > other.year; } } } function fromJSDate(jsDate) { return new NgbDate(jsDate.getFullYear(), jsDate.getMonth() + 1, jsDate.getDate()); } function toJSDate(date) { const jsDate = new Date(date.year, date.month - 1, date.day, 12); // this is done avoid 30 -> 1930 conversion if (!isNaN(jsDate.getTime())) { jsDate.setFullYear(date.year); } return jsDate; } function NGB_DATEPICKER_CALENDAR_FACTORY() { return new NgbCalendarGregorian(); } /** * A service that represents the calendar used by the datepicker. * * The default implementation uses the Gregorian calendar. You can inject it in your own * implementations if necessary to simplify `NgbDate` calculations. */ class NgbCalendar { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgbCalendar, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgbCalendar, providedIn: 'root', useFactory: NGB_DATEPICKER_CALENDAR_FACTORY }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgbCalendar, decorators: [{ type: Injectable, args: [{ providedIn: 'root', useFactory: NGB_DATEPICKER_CALENDAR_FACTORY }] }] }); class NgbCalendarGregorian extends NgbCalendar { getDaysPerWeek() { return 7; } getMonths() { return [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; } getWeeksPerMonth() { return 6; } getNext(date, period = 'd', number = 1) { let jsDate = toJSDate(date); let checkMonth = true; let expectedMonth = jsDate.getMonth(); switch (period) { case 'y': jsDate.setFullYear(jsDate.getFullYear() + number); break; case 'm': expectedMonth += number; jsDate.setMonth(expectedMonth); expectedMonth = expectedMonth % 12; if (expectedMonth < 0) { expectedMonth = expectedMonth + 12; } break; case 'd': jsDate.setDate(jsDate.getDate() + number); checkMonth = false; break; default: return date; } if (checkMonth && jsDate.getMonth() !== expectedMonth) { // this means the destination month has less days than the initial month // let's go back to the end of the previous month: jsDate.setDate(0); } return fromJSDate(jsDate); } getPrev(date, period = 'd', number = 1) { return this.getNext(date, period, -number); } getWeekday(date) { let jsDate = toJSDate(date); let day = jsDate.getDay(); // in JS Date Sun=0, in ISO 8601 Sun=7 return day === 0 ? 7 : day; } getWeekNumber(week, firstDayOfWeek) { // in JS Date Sun=0, in ISO 8601 Sun=7 if (firstDayOfWeek === 7) { firstDayOfWeek = 0; } const thursdayIndex = (4 + 7 - firstDayOfWeek) % 7; let date = week[thursdayIndex]; const jsDate = toJSDate(date); jsDate.setDate(jsDate.getDate() + 4 - (jsDate.getDay() || 7)); // Thursday const time = jsDate.getTime(); jsDate.setMonth(0); // Compare with Jan 1 jsDate.setDate(1); return Math.floor(Math.round((time - jsDate.getTime()) / 86400000) / 7) + 1; } getToday() { return fromJSDate(new Date()); } isValid(date) { if (!date || !isInteger(date.year) || !isInteger(date.month) || !isInteger(date.day)) { return false; } // year 0 doesn't exist in Gregorian calendar if (date.year === 0) { return false; } const jsDate = toJSDate(date); return (!isNaN(jsDate.getTime()) && jsDate.getFullYear() === date.year && jsDate.getMonth() + 1 === date.month && jsDate.getDate() === date.day); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgbCalendarGregorian, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgbCalendarGregorian }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgbCalendarGregorian, decorators: [{ type: Injectable }] }); function isChangedDate(prev, next) { return !dateComparator(prev, next); } function isChangedMonth(prev, next) { return !prev && !next ? false : !prev || !next ? true : prev.year !== next.year || prev.month !== next.month; } function dateComparator(prev, next) { return (!prev && !next) || (!!prev && !!next && prev.equals(next)); } function checkMinBeforeMax(minDate, maxDate) { if (maxDate && minDate && maxDate.before(minDate)) { throw new Error(`'maxDate' ${maxDate} should be greater than 'minDate' ${minDate}`); } } function checkDateInRange(date, minDate, maxDate) { if (date && minDate && date.before(minDate)) { return minDate; } if (date && maxDate && date.after(maxDate)) { return maxDate; } return date || null; } function isDateSelectable(date, state) { const { minDate, maxDate, disabled, markDisabled } = state; return !(date === null || date === undefined || disabled || (markDisabled && markDisabled(date, { year: date.year, month: date.month })) || (minDate && date.before(minDate)) || (maxDate && date.after(maxDate))); } function generateSelectBoxMonths(calendar, date, minDate, maxDate) { if (!date) { return []; } let months = calendar.getMonths(date.year); if (minDate && date.year === minDate.year) { const index = months.findIndex((month) => month === minDate.month); months = months.slice(index); } if (maxDate && date.year === maxDate.year) { const index = months.findIndex((month) => month === maxDate.month); months = months.slice(0, index + 1); } return months; } function generateSelectBoxYears(date, minDate, maxDate) { if (!date) { return []; } const start = minDate ? Math.max(minDate.year, date.year - 500) : date.year - 10; const end = maxDate ? Math.min(maxDate.year, date.year + 500) : date.year + 10; const length = end - start + 1; const numbers = Array(length); for (let i = 0; i < length; i++) { numbers[i] = start + i; } return numbers; } function nextMonthDisabled(calendar, date, maxDate) { const nextDate = Object.assign(calendar.getNext(date, 'm'), { day: 1 }); return maxDate != null && nextDate.after(maxDate); } function prevMonthDisabled(calendar, date, minDate) { const prevDate = Object.assign(calendar.getPrev(date, 'm'), { day: 1 }); return (minDate != null && ((prevDate.year === minDate.year && prevDate.month < minDate.month) || (prevDate.year < minDate.year && minDate.month === 1))); } function buildMonths(calendar, date, state, i18n, force) { const { displayMonths, months } = state; // move old months to a temporary array const monthsToReuse = months.splice(0, months.length); // generate new first dates, nullify or reuse months const firstDates = Array.from({ length: displayMonths }, (_, i) => { const firstDate = Object.assign(calendar.getNext(date, 'm', i), { day: 1 }); months[i] = null; if (!force) { const reusedIndex = monthsToReuse.findIndex((month) => month.firstDate.equals(firstDate)); // move reused month back to months if (reusedIndex !== -1) { months[i] = monthsToReuse.splice(reusedIndex, 1)[0]; } } return firstDate; }); // rebuild nullified months firstDates.forEach((firstDate, i) => { if (months[i] === null) { months[i] = buildMonth(calendar, firstDate, state, i18n, monthsToReuse.shift() || {}); } }); return months; } function buildMonth(calendar, date, state, i18n, month = {}) { const { dayTemplateData, minDate, maxDate, firstDayOfWeek, markDisabled, outsideDays, weekdayWidth, weekdaysVisible, } = state; const calendarToday = calendar.getToday(); month.firstDate = null; month.lastDate = null; month.number = date.month; month.year = date.year; month.weeks = month.weeks || []; month.weekdays = month.weekdays || []; date = getFirstViewDate(calendar, date, firstDayOfWeek); // clearing weekdays, if not visible if (!weekdaysVisible) { month.weekdays.length = 0; } // month has weeks for (let week = 0; week < calendar.getWeeksPerMonth(); week++) { let weekObject = month.weeks[week]; if (!weekObject) { weekObject = month.weeks[week] = { number: 0, days: [], collapsed: true }; } const days = weekObject.days; // week has days for (let day = 0; day < calendar.getDaysPerWeek(); day++) { if (week === 0 && weekdaysVisible) { month.weekdays[day] = i18n.getWeekdayLabel(calendar.getWeekday(date), weekdayWidth); } const newDate = new NgbDate(date.year, date.month, date.day); const nextDate = calendar.getNext(newDate); const ariaLabel = i18n.getDayAriaLabel(newDate); // marking date as disabled let disabled = !!((minDate && newDate.before(minDate)) || (maxDate && newDate.after(maxDate))); if (!disabled && markDisabled) { disabled = markDisabled(newDate, { month: month.number, year: month.year }); } // today let today = newDate.equals(calendarToday); // adding user-provided data to the context let contextUserData = dayTemplateData ? dayTemplateData(newDate, { month: month.number, year: month.year }) : undefined; // saving first date of the month if (month.firstDate === null && newDate.month === month.number) { month.firstDate = newDate; } // saving last date of the month if (newDate.month === month.number && nextDate.month !== month.number) { month.lastDate = newDate; } let dayObject = days[day]; if (!dayObject) { dayObject = days[day] = {}; } dayObject.date = newDate; dayObject.context = Object.assign(dayObject.context || {}, { $implicit: newDate, date: newDate, data: contextUserData, currentMonth: month.number, currentYear: month.year, disabled, focused: false, selected: false, today, }); dayObject.tabindex = -1; dayObject.ariaLabel = ariaLabel; dayObject.hidden = false; date = nextDate; } weekObject.number = calendar.getWeekNumber(days.map((day) => day.date), firstDayOfWeek); // marking week as collapsed weekObject.collapsed = outsideDays === 'collapsed' && days[0].date.month !== month.number && days[days.length - 1].date.month !== month.number; } return month; } function getFirstViewDate(calendar, date, firstDayOfWeek) { const daysPerWeek = calendar.getDaysPerWeek(); const firstMonthDate = new NgbDate(date.year, date.month, 1); const dayOfWeek = calendar.getWeekday(firstMonthDate) % daysPerWeek; return calendar.getPrev(firstMonthDate, 'd', (daysPerWeek + dayOfWeek - firstDayOfWeek) % daysPerWeek); } /** * A service supplying i18n data to the datepicker component. * * The default implementation of this service uses the Angular locale and registered locale data for * weekdays and month names (as explained in the Angular i18n guide). * * It also provides a way to i18n data that depends on calendar calculations, like aria labels, day, week and year * numerals. For other static labels the datepicker uses the default Angular i18n. * * See the [i18n demo](#/components/datepicker/examples#i18n) and * [Hebrew calendar demo](#/components/datepicker/calendars#hebrew) on how to extend this class and define * a custom provider for i18n. */ class NgbDatepickerI18n { /** * Returns the text label to display above the day view. * * @since 9.1.0 */ getMonthLabel(date) { return `${this.getMonthFullName(date.month, date.year)} ${this.getYearNumerals(date.year)}`; } /** * Returns the textual representation of a day that is rendered in a day cell. * * @since 3.0.0 */ getDayNumerals(date) { return `${date.day}`; } /** * Returns the textual representation of a week number rendered by datepicker. * * @since 3.0.0 */ getWeekNumerals(weekNumber) { return `${weekNumber}`; } /** * Returns the textual representation of a year that is rendered in the datepicker year select box. * * @since 3.0.0 */ getYearNumerals(year) { return `${year}`; } /** * Returns the week label to display in the heading of the month view. * * @since 9.1.0 */ getWeekLabel() { return ''; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgbDatepickerI18n, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgbDatepickerI18n, providedIn: 'root', useFactory: () => new NgbDatepickerI18nDefault() }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgbDatepickerI18n, decorators: [{ type: Injectable, args: [{ providedIn: 'root', useFactory: () => new NgbDatepickerI18nDefault(), }] }] }); /** * A service providing default implementation for the datepicker i18n. * It can be used as a base implementation if necessary. * * @since 9.1.0 */ class NgbDatepickerI18nDefault extends NgbDatepickerI18n { constructor() { super(...arguments); this._locale = inject(LOCALE_ID); this._monthsShort = [...Array(12).keys()].map((month) => Intl.DateTimeFormat(this._locale, { month: 'short', timeZone: 'UTC' }).format(Date.UTC(2000, month))); this._monthsFull = [...Array(12).keys()].map((month) => Intl.DateTimeFormat(this._locale, { month: 'long', timeZone: 'UTC' }).format(Date.UTC(2000, month))); } getWeekdayLabel(weekday, width = 'narrow') { // 1 MAY 2000 is a Monday const weekdays = [1, 2, 3, 4, 5, 6, 7].map((day) => Intl.DateTimeFormat(this._locale, { weekday: width, timeZone: 'UTC' }).format(Date.UTC(2000, 4, day))); // `weekday` is 1 (Mon) to 7 (Sun) return weekdays[weekday - 1] || ''; } getMonthShortName(month) { return this._monthsShort[month - 1] || ''; } getMonthFullName(month) { return this._monthsFull[month - 1] || ''; } getDayAriaLabel(date) { const jsDate = new Date(date.year, date.month - 1, date.day); return formatDate(jsDate, 'fullDate', this._locale); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgbDatepickerI18nDefault, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgbDatepickerI18nDefault }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgbDatepickerI18nDefault, decorators: [{ type: Injectable }] }); class NgbDatepickerService { constructor() { this._VALIDATORS = { dayTemplateData: (dayTemplateData) => { if (this._state.dayTemplateData !== dayTemplateData) { return { dayTemplateData }; } }, displayMonths: (displayMonths) => { displayMonths = toInteger(displayMonths); if (isInteger(displayMonths) && displayMonths > 0 && this._state.displayMonths !== displayMonths) { return { displayMonths }; } }, disabled: (disabled) => { if (this._state.disabled !== disabled) { return { disabled }; } }, firstDayOfWeek: (firstDayOfWeek) => { firstDayOfWeek = toInteger(firstDayOfWeek); if (isInteger(firstDayOfWeek) && firstDayOfWeek >= 0 && this._state.firstDayOfWeek !== firstDayOfWeek) { return { firstDayOfWeek }; } }, focusVisible: (focusVisible) => { if (this._state.focusVisible !== focusVisible && !this._state.disabled) { return { focusVisible }; } }, markDisabled: (markDisabled) => { if (this._state.markDisabled !== markDisabled) { return { markDisabled }; } }, maxDate: (date) => { const maxDate = this.toValidDate(date, null); if (isChangedDate(this._state.maxDate, maxDate)) { return { maxDate }; } }, minDate: (date) => { const minDate = this.toValidDate(date, null); if (isChangedDate(this._state.minDate, minDate)) { return { minDate }; } }, navigation: (navigation) => { if (this._state.navigation !== navigation) { return { navigation }; } }, outsideDays: (outsideDays) => { if (this._state.outsideDays !== outsideDays) { return { outsideDays }; } }, weekdays: (weekdays) => { const weekdayWidth = weekdays === true || weekdays === false ? 'narrow' : weekdays; const weekdaysVisible = weekdays === true || weekdays === false ? weekdays : true; if (this._state.weekdayWidth !== weekdayWidth || this._state.weekdaysVisible !== weekdaysVisible) { return { weekdayWidth, weekdaysVisible }; } }, }; this._calendar = inject(NgbCalendar); this._i18n = inject(NgbDatepickerI18n); this._model$ = new Subject(); this._dateSelect$ = new Subject(); this._state = { dayTemplateData: null, markDisabled: null, maxDate: null, minDate: null, disabled: false, displayMonths: 1, firstDate: null, firstDayOfWeek: 1, lastDate: null, focusDate: null, focusVisible: false, months: [], navigation: 'select', outsideDays: 'visible', prevDisabled: false, nextDisabled: false, selectedDate: null, selectBoxes: { years: [], months: [] }, weekdayWidth: 'narrow', weekdaysVisible: true, }; } get model$() { return this._model$.pipe(filter((model) => model.months.length > 0)); } get dateSelect$() { return this._dateSelect$.pipe(filter((date) => date !== null)); } set(options) { let patch = Object.keys(options) .map((key) => this._VALIDATORS[key](options[key])) .reduce((obj, part) => ({ ...obj, ...part }), {}); if (Object.keys(patch).length > 0) { this._nextState(patch); } } focus(date) { const focusedDate = this.toValidDate(date, null); if (focusedDate != null && !this._state.disabled && isChangedDate(this._state.focusDate, focusedDate)) { this._nextState({ focusDate: date }); } } focusSelect() { if (isDateSelectable(this._state.focusDate, this._state)) { this.select(this._state.focusDate, { emitEvent: true }); } } open(date) { const firstDate = this.toValidDate(date, this._calendar.getToday()); if (firstDate != null && !this._state.disabled && (!this._state.firstDate || isChangedMonth(this._state.firstDate, firstDate))) { this._nextState({ firstDate }); } } select(date, options = {}) { const selectedDate = this.toValidDate(date, null); if (selectedDate != null && !this._state.disabled) { if (isChangedDate(this._state.selectedDate, selectedDate)) { this._nextState({ selectedDate }); } if (options.emitEvent && isDateSelectable(selectedDate, this._state)) { this._dateSelect$.next(selectedDate); } } } toValidDate(date, defaultValue) { const ngbDate = NgbDate.from(date); if (defaultValue === undefined) { defaultValue = this._calendar.getToday(); } return this._calendar.isValid(ngbDate) ? ngbDate : defaultValue; } getMonth(struct) { for (let month of this._state.months) { if (struct.month === month.number && struct.year === month.year) { return month; } } throw new Error(`month ${struct.month} of year ${struct.year} not found`); } _nextState(patch) { const newState = this._updateState(patch); this._patchContexts(newState); this._state = newState; this._model$.next(this._state); } _patchContexts(state) { const { months, displayMonths, selectedDate, focusDate, focusVisible, disabled, outsideDays } = state; state.months.forEach((month) => { month.weeks.forEach((week) => { week.days.forEach((day) => { // patch focus flag if (focusDate) { day.context.focused = focusDate.equals(day.date) && focusVisible; } // calculating tabindex day.tabindex = !disabled && focusDate && day.date.equals(focusDate) && focusDate.month === month.number ? 0 : -1; // override context disabled if (disabled === true) { day.context.disabled = true; } // patch selection flag if (selectedDate !== undefined) { day.context.selected = selectedDate !== null && selectedDate.equals(day.date); } // visibility if (month.number !== day.date.month) { day.hidden = outsideDays === 'hidden' || outsideDays === 'collapsed' || (displayMonths > 1 && day.date.after(months[0].firstDate) && day.date.before(months[displayMonths - 1].lastDate)); } }); }); }); } _updateState(patch) { // patching fields const state = Object.assign({}, this._state, patch); let startDate = state.firstDate; // min/max dates changed if ('minDate' in patch || 'maxDate' in patch) { checkMinBeforeMax(state.minDate, state.maxDate); state.focusDate = checkDateInRange(state.focusDate, state.minDate, state.maxDate); state.firstDate = checkDateInRange(state.firstDate, state.minDate, state.maxDate); startDate = state.focusDate; } // disabled if ('disabled' in patch) { state.focusVisible = false; } // initial rebuild via 'select()' if ('selectedDate' in patch && this._state.months.length === 0) { startDate = state.selectedDate; } // terminate early if only focus visibility was changed if ('focusVisible' in patch) { return state; } // focus date changed if ('focusDate' in patch) { state.focusDate = checkDateInRange(state.focusDate, state.minDate, state.maxDate); startDate = state.focusDate; // nothing to rebuild if only focus changed and it is still visible if (state.months.length !== 0 && state.focusDate && !state.focusDate.before(state.firstDate) && !state.focusDate.after(state.lastDate)) { return state; } } // first date changed if ('firstDate' in patch) { state.firstDate = checkDateInRange(state.firstDate, state.minDate, state.maxDate); startDate = state.firstDate; } // rebuilding months if (startDate) { const forceRebuild = 'dayTemplateData' in patch || 'firstDayOfWeek' in patch || 'markDisabled' in patch || 'minDate' in patch || 'maxDate' in patch || 'disabled' in patch || 'outsideDays' in patch || 'weekdaysVisible' in patch; const months = buildMonths(this._calendar, startDate, state, this._i18n, forceRebuild); // updating months and boundary dates state.months = months; state.firstDate = months[0].firstDate; state.lastDate = months[months.length - 1].lastDate; // reset selected date if 'markDisabled' returns true if ('selectedDate' in patch && !isDateSelectable(state.selectedDate, state)) { state.selectedDate = null; } // adjusting focus after months were built if ('firstDate' in patch) { if (!state.focusDate || state.focusDate.before(state.firstDate) || state.focusDate.after(state.lastDate)) { state.focusDate = startDate; } } // adjusting months/years for the select box navigation const yearChanged = !this._state.firstDate || this._state.firstDate.year !== state.firstDate.year; const monthChanged = !this._state.firstDate || this._state.firstDate.month !== state.firstDate.month; if (state.navigation === 'select') { // years -> boundaries (min/max were changed) if ('minDate' in patch || 'maxDate' in patch || state.selectBoxes.years.length === 0 || yearChanged) { state.selectBoxes.years = generateSelectBoxYears(state.firstDate, state.minDate, state.maxDate); } // months -> when current year or boundaries change if ('minDate' in patch || 'maxDate' in patch || state.selectBoxes.months.length === 0 || yearChanged) { state.selectBoxes.months = generateSelectBoxMonths(this._calendar, state.firstDate, state.minDate, state.maxDate); } } else { state.selectBoxes = { years: [], months: [] }; } // updating navigation arrows -> boundaries change (min/max) or month/year changes if ((state.navigation === 'arrows' || state.navigation === 'select') && (monthChanged || yearChanged || 'minDate' in patch || 'maxDate' in patch || 'disabled' in patch)) { state.prevDisabled = state.disabled || prevMonthDisabled(this._calendar, state.firstDate, state.minDate); state.nextDisabled = state.disabled || nextMonthDisabled(this._calendar, state.lastDate, state.maxDate); } } return state; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgbDatepickerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgbDatepickerService }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgbDatepickerService, decorators: [{ type: Injectable }] }); var NavigationEvent; (function (NavigationEvent) { NavigationEvent[NavigationEvent["PREV"] = 0] = "PREV"; NavigationEvent[NavigationEvent["NEXT"] = 1] = "NEXT"; })(NavigationEvent || (NavigationEvent = {})); /** * A configuration service for the [`NgbDatepicker`](#/components/datepicker/api#NgbDatepicker) component. * * You can inject this service, typically in your root component, and customize the values of its properties in * order to provide default values for all the datepickers used in the application. */ class NgbDatepickerConfig { constructor() { this.displayMonths = 1; this.firstDayOfWeek = 1; this.navigation = 'select'; this.outsideDays = 'visible'; this.showWeekNumbers = false; this.weekdays = 'narrow'; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgbDatepickerConfig, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgbDatepickerConfig, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgbDatepickerConfig, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); function NGB_DATEPICKER_DATE_ADAPTER_FACTORY() { return new NgbDateStructAdapter(); } /** * An abstract service that does the conversion between the internal datepicker `NgbDateStruct` model and * any provided user date model `D`, ex. a string, a native date, etc. * * The adapter is used **only** for conversion when binding datepicker to a form control, * ex. `[(ngModel)]="userDateModel"`. Here `userDateModel` can be of any type. * * The default datepicker implementation assumes we use `NgbDateStruct` as a user model. * * See the [date format overview](#/components/datepicker/overview#date-model) for more details * and the [custom adapter demo](#/components/datepicker/examples#adapter) for an example. */ class NgbDateAdapter { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgbDateAdapter, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgbDateAdapter, providedIn: 'root', useFactory: NGB_DATEPICKER_DATE_ADAPTER_FACTORY }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgbDateAdapter, decorators: [{ type: Injectable, args: [{ providedIn: 'root', useFactory: NGB_DATEPICKER_DATE_ADAPTER_FACTORY }] }] }); class NgbDateStructAdapter extends NgbDateAdapter { /** * Converts a NgbDateStruct value into NgbDateStruct value */ fromModel(date) { return date && isInteger(date.year) && isInteger(date.month) && isInteger(date.day) ? { year: date.year, month: date.month, day: date.day } : null; } /** * Converts a NgbDateStruct value into NgbDateStruct value */ toModel(date) { return date && isInteger(date.year) && isInteger(date.month) && isInteger(date.day) ? { year: date.year, month: date.month, day: date.day } : null; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgbDateStructAdapter, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgbDateStructAdapter }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgbDateStructAdapter, decorators: [{ type: Injectable }] }); /** * A service that represents the keyboard navigation. * * Default keyboard shortcuts [are documented in the overview](#/components/datepicker/overview#keyboard-shortcuts) * * @since 5.2.0 */ class NgbDatepickerKeyboardService { /** * Processes a keyboard event. */ processKey(event, datepicker) { const { state, calendar } = datepicker; switch (event.key) { case 'PageUp': datepicker.focusDate(calendar.getPrev(state.focusedDate, event.shiftKey ? 'y' : 'm', 1)); break; case 'PageDown': datepicker.focusDate(calendar.getNext(state.focusedDate, event.shiftKey ? 'y' : 'm', 1)); break; case 'End': datepicker.focusDate(event.shiftKey ? state.maxDate : state.lastDate); break; case 'Home': datepicker.focusDate(event.shiftKey ? state.minDate : state.firstDate); break; case 'ArrowLeft': datepicker.focusDate(calendar.getPrev(state.focusedDate, 'd', 1)); break; case 'ArrowUp': datepicker.focusDate(calendar.getPrev(state.focusedDate, 'd', calendar.getDaysPerWeek())); break; case 'ArrowRight': datepicker.focusDate(calendar.getNext(state.focusedDate, 'd', 1)); break; case 'ArrowDown': datepicker.focusDate(calendar.getNext(state.focusedDate, 'd', calendar.getDaysPerWeek())); break; case 'Enter': case ' ': datepicker.focusSelect(); break; default: return; } event.preventDefault(); event.stopPropagation(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgbDatepickerKeyboardService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgbDatepickerKeyboardService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgbDatepickerKeyboardService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); class NgbDatepickerDayView { constructor() { this.i18n = inject(NgbDatepickerI18n); } isMuted() { return !this.selected && (this.date.month !== this.currentMonth || this.disabled); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgbDatepickerDayView, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.4", type: NgbDatepickerDayView, isStandalone: true, selector: "[ngbDatepickerDayView]", inputs: { currentMonth: "currentMonth", date: "date", disabled: "disabled", focused: "focused", selected: "selected" }, host: { properties: { "class.bg-primary": "selected", "class.text-white": "selected", "class.text-muted": "isMuted()", "class.outside": "isMuted()", "class.active": "focused" }, classAttribute: "btn-light" }, ngImport: i0, template: `{{ i18n.getDayNumerals(date) }}`, isInline: true, styles: ["[ngbDatepickerDayView]{text-align:center;width:2rem;height:2rem;line-height:2rem;border-radius:.25rem;background:transparent}[ngbDatepickerDayView]:hover:not(.bg-primary),[ngbDatepickerDayView].active:not(.bg-primary){background-color:var(--bs-tertiary-bg);outline:1px solid var(--bs-border-color)}[ngbDatepickerDayView].outside{opacity:.5}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgbDatepickerDayView, decorators: [{ type: Component, args: [{ selector: '[ngbDatepickerDayView]', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: { class: 'btn-light', '[class.bg-primary]': 'selected', '[class.text-white]': 'selected', '[class.text-muted]': 'isMuted()', '[class.outside]': 'isMuted()', '[class.active]': 'focused', }, template: `{{ i18n.getDayNumerals(date) }}`, styles: ["[ngbDatepickerDayView]{text-align:center;width:2rem;height:2rem;line-height:2rem;border-radius:.25rem;background:transparent}[ngbDatepickerDayView]:hover:not(.bg-primary),[ngbDatepickerDayView].active:not(.bg-primary){background-color:var(--bs-tertiary-bg);outline:1px solid var(--bs-border-color)}[ngbDatepickerDayView].outside{opacity:.5}\n"] }] }], propDecorators: { currentMonth: [{ type: Input }], date: [{ type: Input }], disabled: [{ type: Input }], focused: [{ type: Input }], selected: [{ type: Input }] } }); class NgbDatepickerNavigationSelect { constructor() { this._month = -1; this._year = -1; this.i18n = inject(NgbDatepickerI18n); this.select = new EventEmitter(); } changeMonth(month) { this.select.emit(new NgbDate(this.date.year, toInteger(month), 1)); } changeYear(year) { this.select.emit(new NgbDate(toInteger(year), this.date.month, 1)); } ngAfterViewChecked() { if (this.date) { if (this.date.month !== this._month) { this._month = this.date.month; this.monthSelect.nativeElement.value = `${this._month}`; } if (this.date.year !== this._year) { this._year = this.date.year; this.yearSelect.nativeElement.value = `${this._year}`; } } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgbDatepickerNavigationSelect, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.4", type: NgbDatepickerNavigationSelect, isStandalone: true, selector: "ngb-datepicker-navigation-select", inputs: { date: "date", disabled: "disabled", months: "months", years: "years" }, outputs: { select: "select" }, viewQueries: [{ propertyName: "monthSelect", first: true, predicate: ["month"], descendants: true, read: ElementRef, static: true }, { propertyName: "yearSelect", first: true, predicate: ["year"], descendants: true, read: ElementRef, static: true }], ngImport: i0, template: ` <select #month [disabled]="disabled" class="form-select" i18n-aria-label="@@ngb.datepicker.select-month" aria-label="Select month" i18n-title="@@ngb.datepicker.select-month" title="Select month" (change)="changeMonth($any($event).target.value)" > @for (m of months; track m) { <option [attr.aria-label]="i18n.getMonthFullName(m, date.year)" [value]="m">{{ i18n.getMonthShortName(m, date.year) }}</option> }</select ><select #year [disabled]="disabled" class="form-select" i18n-aria-label="@@ngb.datepicker.select-year" aria-label="Select year" i18n-title="@@ngb.datepicker.select-year" title="Select year" (change)="changeYear($any($event).target.value)" > @for (y of years; track y) { <option [value]="y">{{ i18n.getYearNumerals(y) }}</option> } </select> `, isInline: true, styles: ["ngb-datepicker-navigation-select>.form-select{flex:1 1 auto;padding:0 .5rem;font-size:.875rem;height:1.85rem}ngb-datepicker-navigation-select>.form-select:focus{z-index:1}ngb-datepicker-navigation-select>.form-select::-ms-value{background-color:transparent!important}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgbDatepickerNavigationSelect, decorators: [{ type: Component, args: [{ selector: 'ngb-datepicker-navigation-select', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: ` <select #month [disabled]="disabled" class="form-select" i18n-aria-label="@@ngb.datepicker.select-month" aria-label="Select month" i18n-title="@@ngb.datepicker.select-month" title="Select month" (change)="changeMonth($any($event).target.value)" > @for (m of months; track m) { <option [attr.aria-label]="i18n.getMonthFullName(m, date.year)" [value]="m">{{ i18n.getMonthShortName(m, date.year) }}</option> }</select ><select #year [disabled]="disabled" class="form-select" i18n-aria-label="@@ngb.datepicker.select-year" aria-label="Select year" i18n-title="@@ngb.datepicker.select-year" title="Select year" (change)="changeYear($any($event).target.value)" > @for (y of years; track y) { <option [value]="y">{{ i18n.getYearNumerals(y) }}</option> } </select> `, styles: ["ngb-datepicker-navigation-select>.form-select{flex:1 1 auto;padding:0 .5rem;font-size:.875rem;height:1.85rem}ngb-datepicker-navigation-select>.form-select:focus{z-index:1}ngb-datepicker-navigation-select>.form-select::-ms-value{background-color:transparent!important}\n"] }] }], propDecorators: { date: [{ type: Input }], disabled: [{ type: Input }], months: [{ type: Input }], years: [{ type: Input }], select: [{ type: Output }], monthSelect: [{ type: ViewChild, args: ['month', { static: true, read: ElementRef }] }], yearSelect: [{ type: ViewChild, args: ['year', { static: true, read: ElementRef }] }] } }); class NgbDatepickerNavigation { constructor() { this.navigation = NavigationEvent; this.i18n = inject(NgbDatepickerI18n); this.months = []; this.navigate = new EventEmitter(); this.select = new EventEmitter(); } onClickPrev(event) { event.currentTarget.focus(); this.navigate.emit(this.navigation.PREV); } onClickNext(event) { event.currentTarget.focus(); this.navigate.emit(this.navigation.NEXT); } idMonth(month) { return month; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgbDatepickerNavigation, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.4", type: NgbDatepickerNavigation, isStandalone: true, selector: "ngb-datepicker-navigation", inputs: { date: "date", disabled: "disabled", months: "months", showSelect: "showSelect", prevDisabled: "prevDisabled", nextDisabled: "nextDisabled", selectBoxes: "selectBoxes" }, outputs: { navigate: "navigate", select: "select" }, ngImport: i0, template: ` <div class="ngb-dp-arrow ngb-dp-arrow-prev"> <button type="button" class="btn btn-link ngb-dp-arrow-btn" (click)="onClickPrev($event)" [disabled]="prevDisabled" i18n-aria-label="@@ngb.datepicker.previous-month" aria-label="Previous month" i18n-title="@@ngb.datepicker.previous-month" title="Previous month" > <span class="ngb-dp-navigation-chevron"></span> </button> </div> @if (showSelect) { <ngb-datepicker-navigation-select class="ngb-dp-navigation-select" [date]="date" [disabled]="disabled" [months]="selectBoxes.months" [years]="selectBoxes.years" (select)="select.emit($event)" /> } @if (!showSelect) { @for (month of months; track idMonth(month); let i = $index) { @if (i > 0) { <div class="ngb-dp-arrow"></div> } <div class="ngb-dp-month-name"> {{ i18n.getMonthLabel(month.firstDate) }} </div> @if (i !== months.length - 1) { <div class="ngb-dp-arrow"></div> } } } <div class="visually-hidden" aria-live="polite"> @for (month of months; track idMonth(month)) { <span>{{ i18n.getMonthLabel(month.firstDate) }}</span> } </div> <div class="ngb-dp-arrow ngb-dp-arrow-next"> <button type="button" class="btn btn-link ngb-dp-arrow-btn" (click)="onClickNext($event)" [disabled]="nextDisabled" i18n-aria-label="@@ngb.datepicker.next-month" aria-label="Next month" i18n-title="@@ngb.datepicker.next-month" title="Next month" > <span class="ngb-dp-navigation-chevron"></span> </button> </div> `, isInline: true, styles: ["ngb-datepicker-navigation{display:flex;align-items:center}.ngb-dp-navigation-chevron{border-style:solid;border-width:.2em .2em 0 0;display:inline-block;width:.75em;height:.75em;margin-left:.25em;margin-right:.15em;transform:rotate(-135deg)}.ngb-dp-arrow{display:flex;flex:1 1 auto;padding-right:0;padding-left:0;margin:0;width:2rem;height:2rem}.ngb-dp-arrow-next{justify-content:flex-end}.ngb-dp-arrow-next .ngb-dp-navigation-chevron{transform:rotate(45deg);margin-left:.15em;margin-right:.25em}.ngb-dp-arrow-btn{padding:0 .25rem;margin:0 .5rem;border:none;background-color:transparent;z-index:1}.ngb-dp-arrow-btn:focus{outline-width:1px;outline-style:auto}@media all and (-ms-high-contrast:none),(-ms-high-contrast:active){.ngb-dp-arrow-btn:focus{outline-style:solid}}.ngb-dp-month-name{font-size:larger;height:2rem;line-height:2rem;text-align:center}.ngb-dp-navigation-select{disp