@ng-bootstrap/ng-bootstrap
Version:
Angular powered Bootstrap
1,124 lines (1,110 loc) • 165 kB
JavaScript
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