UNPKG

@clr/angular

Version:

Angular components for Clarity

1 lines 208 kB
{"version":3,"file":"clr-angular-forms-datepicker.mjs","sources":["../../../projects/angular/forms/datepicker/providers/date-form-control.service.ts","../../../projects/angular/forms/datepicker/model/day.model.ts","../../../projects/angular/forms/datepicker/utils/constants.ts","../../../projects/angular/forms/datepicker/utils/date-utils.ts","../../../projects/angular/forms/datepicker/enums/weekday.enum.ts","../../../projects/angular/forms/datepicker/providers/locale-helper.service.ts","../../../projects/angular/forms/datepicker/providers/date-io.service.ts","../../../projects/angular/forms/datepicker/model/calendar.model.ts","../../../projects/angular/forms/datepicker/providers/date-navigation.service.ts","../../../projects/angular/forms/datepicker/providers/datepicker-enabled.service.ts","../../../projects/angular/forms/datepicker/providers/view-manager.service.ts","../../../projects/angular/forms/datepicker/providers/datepicker-focus.service.ts","../../../projects/angular/forms/datepicker/monthpicker.ts","../../../projects/angular/forms/datepicker/model/year-range.model.ts","../../../projects/angular/forms/datepicker/yearpicker.ts","../../../projects/angular/forms/datepicker/model/day-view.model.ts","../../../projects/angular/forms/datepicker/model/calendar-view.model.ts","../../../projects/angular/forms/datepicker/day.ts","../../../projects/angular/forms/datepicker/calendar.ts","../../../projects/angular/forms/datepicker/calendar.html","../../../projects/angular/forms/datepicker/daypicker.ts","../../../projects/angular/forms/datepicker/daypicker.html","../../../projects/angular/forms/datepicker/datepicker-action-buttons.ts","../../../projects/angular/forms/datepicker/datepicker-view-manager.ts","../../../projects/angular/forms/datepicker/datepicker-view-manager.html","../../../projects/angular/forms/datepicker/date-container.ts","../../../projects/angular/forms/datepicker/date-input.ts","../../../projects/angular/forms/datepicker/date-single-input.ts","../../../projects/angular/forms/datepicker/date-start-input.ts","../../../projects/angular/forms/datepicker/date-end-input.ts","../../../projects/angular/forms/datepicker/date-input.validator.ts","../../../projects/angular/forms/datepicker/interfaces/day-of-week.interface.ts","../../../projects/angular/forms/datepicker/datepicker.module.ts","../../../projects/angular/forms/datepicker/index.ts","../../../projects/angular/forms/datepicker/clr-angular-forms-datepicker.ts"],"sourcesContent":["/*\n * Copyright (c) 2016-2026 Broadcom. All Rights Reserved.\n * The term \"Broadcom\" refers to Broadcom Inc. and/or its subsidiaries.\n * This software is released under MIT license.\n * The full license information can be found in LICENSE in the root directory of this project.\n */\n\nimport { Injectable } from '@angular/core';\nimport { Observable, Subject } from 'rxjs';\n\n@Injectable()\nexport class DateFormControlService {\n disabled: boolean;\n\n private _touchedChange = new Subject<void>();\n private _dirtyChange = new Subject<void>();\n\n get touchedChange(): Observable<void> {\n return this._touchedChange.asObservable();\n }\n\n get dirtyChange(): Observable<void> {\n return this._dirtyChange.asObservable();\n }\n\n markAsTouched(): void {\n this._touchedChange.next();\n }\n\n markAsDirty(): void {\n this._dirtyChange.next();\n }\n\n // friendly wrapper\n setDisabled(state: boolean) {\n this.disabled = state;\n }\n}\n","/*\n * Copyright (c) 2016-2026 Broadcom. All Rights Reserved.\n * The term \"Broadcom\" refers to Broadcom Inc. and/or its subsidiaries.\n * This software is released under MIT license.\n * The full license information can be found in LICENSE in the root directory of this project.\n */\n\nexport class DayModel {\n constructor(\n readonly year: number,\n readonly month: number,\n readonly date: number\n ) {}\n\n /**\n * Checks if the passed CalendarDate is equal to itself.\n */\n isEqual(day: DayModel) {\n if (day) {\n return this.year === day.year && this.month === day.month && this.date === day.date;\n }\n return false;\n }\n\n toDate(): Date {\n return new Date(this.year, this.month, this.date);\n }\n\n /**\n * Returns a new DayModel which is incremented based on the value passed.\n */\n incrementBy(value: number): DayModel {\n // Creating new Javascript Date object to increment because\n // it will automatically take care of switching to next or previous\n // months & years without we having to worry about it.\n const date: Date = new Date(this.year, this.month, this.date + value);\n return new DayModel(date.getFullYear(), date.getMonth(), date.getDate());\n }\n\n /**\n * Clones the current day model.\n */\n clone(): DayModel {\n return new DayModel(this.year, this.month, this.date);\n }\n\n toComparisonString(): string {\n return `${this.year}${this.pad(this.month)}${this.pad(this.date)}`;\n }\n\n toDateString(): string {\n return this.toDate().toLocaleDateString(undefined, {\n weekday: 'long',\n month: 'long',\n day: 'numeric',\n year: 'numeric',\n });\n }\n\n /**\n * Compares the dates and returns boolean value based on the value passed\n */\n isBefore(day: DayModel, dayInclusive = false) {\n return dayInclusive\n ? this.toDate().getTime() <= day?.toDate().getTime()\n : this.toDate().getTime() < day?.toDate().getTime();\n }\n\n /**\n * Compares the dates and returns boolean value based on the value passed\n */\n isAfter(day: DayModel, dayInclusive = false) {\n return dayInclusive\n ? this.toDate().getTime() >= day?.toDate().getTime()\n : this.toDate().getTime() > day?.toDate().getTime();\n }\n\n private pad(num: number): string {\n return num < 10 ? `0${num}` : `${num}`;\n }\n}\n","/*\n * Copyright (c) 2016-2026 Broadcom. All Rights Reserved.\n * The term \"Broadcom\" refers to Broadcom Inc. and/or its subsidiaries.\n * This software is released under MIT license.\n * The full license information can be found in LICENSE in the root directory of this project.\n */\n\n/**\n * This is the en-001 short locale date format. Setting as default.\n */\nexport const DEFAULT_LOCALE_FORMAT = 'dd/MM/y';\n\n// https://en.wikipedia.org/wiki/Date_format_by_country\nexport const LITTLE_ENDIAN_REGEX = /d+.+m+.+y+/i;\nexport const MIDDLE_ENDIAN_REGEX = /m+.+d+.+y+/i;\n// No need for BIG_ENDIAN_REGEX because anything that doesn't satisfy the above 2\n// is automatically BIG_ENDIAN\n\nexport const DELIMITER_REGEX = /d+|m+|y+/i;\n\nexport const USER_INPUT_REGEX = /\\d+/g;\n\nexport const MOBILE_USERAGENT_REGEX = /Mobi/i;\n\nexport const RTL_REGEX = /\\u200f/g;\n\nexport const YEAR = 'YYYY';\nexport const MONTH = 'MM';\nexport const DATE = 'DD';\n\nexport type FormatType = 'LITTLE_ENDIAN' | 'MIDDLE_ENDIAN' | 'BIG_ENDIAN';\n\nexport type InputDateDisplayFormat = {\n readonly name: FormatType;\n readonly format: [string, string, string];\n};\n\nexport const LITTLE_ENDIAN: InputDateDisplayFormat = {\n name: 'LITTLE_ENDIAN',\n format: [DATE, MONTH, YEAR],\n};\n\nexport const MIDDLE_ENDIAN: InputDateDisplayFormat = {\n name: 'MIDDLE_ENDIAN',\n format: [MONTH, DATE, YEAR],\n};\n\nexport const BIG_ENDIAN: InputDateDisplayFormat = {\n name: 'BIG_ENDIAN',\n format: [YEAR, MONTH, DATE],\n};\n\nexport const NO_OF_DAYS_IN_A_WEEK = 7;\nexport const NO_OF_ROWS_IN_CALENDAR_VIEW = 6;\nexport const TOTAL_DAYS_IN_DAYS_VIEW: number = NO_OF_DAYS_IN_A_WEEK * NO_OF_ROWS_IN_CALENDAR_VIEW;\n","/*\n * Copyright (c) 2016-2026 Broadcom. All Rights Reserved.\n * The term \"Broadcom\" refers to Broadcom Inc. and/or its subsidiaries.\n * This software is released under MIT license.\n * The full license information can be found in LICENSE in the root directory of this project.\n */\n\nimport { WeekDay } from '@angular/common';\n\n/**\n * Returns the number of days in a month.\n */\nexport function getNumberOfDaysInTheMonth(year: number, month: number): number {\n // If we go to the next month, but use a day of 0, it returns the last day from the previous month\n return new Date(year, month + 1, 0).getDate();\n}\n\n/**\n * Returns the day for the corresponding date where 0 represents Sunday.\n */\nexport function getDay(year: number, month: number, date: number): WeekDay {\n return new Date(year, month, date).getDay();\n}\n\n/**\n * Takes in a year and if it is a 2 digit year, returns the corresponding 4 digit year.\n * Window of 80 years before and 20 years after the present year.\n * Credit: https://github.com/globalizejs/globalize/blob/e1b31cd6a4f1cff75b185b68b7a32220aac5196f/src/date/parse.js\n */\nexport function parseToFourDigitYear(year: number): number {\n if (year > 9999 || (year > 100 && year < 999) || year < 10) {\n return -1;\n }\n if (year > 999) {\n return year;\n }\n const currYear: number = new Date().getFullYear();\n const century: number = Math.floor(currYear / 100) * 100;\n let result: number = year + century;\n if (result > currYear + 20) {\n result = result - 100;\n }\n return result;\n}\n\nexport function datesAreEqual(date1: Date, date2: Date) {\n if (date1 instanceof Date && date2 instanceof Date) {\n return (\n date1.getFullYear() === date2.getFullYear() &&\n date1.getMonth() === date2.getMonth() &&\n date1.getDate() === date2.getDate()\n );\n } else {\n return false;\n }\n}\n","/*\n * Copyright (c) 2016-2026 Broadcom. All Rights Reserved.\n * The term \"Broadcom\" refers to Broadcom Inc. and/or its subsidiaries.\n * This software is released under MIT license.\n * The full license information can be found in LICENSE in the root directory of this project.\n */\n\n/**\n * Index of the day of the week, matching JavaScript's Date.getDay() convention.\n * Used to override the locale-derived first day of the week in the date picker.\n */\nexport enum ClrWeekday {\n Sunday = 0,\n Monday = 1,\n Tuesday = 2,\n Wednesday = 3,\n Thursday = 4,\n Friday = 5,\n Saturday = 6,\n}\n","/*\n * Copyright (c) 2016-2026 Broadcom. All Rights Reserved.\n * The term \"Broadcom\" refers to Broadcom Inc. and/or its subsidiaries.\n * This software is released under MIT license.\n * The full license information can be found in LICENSE in the root directory of this project.\n */\n\nimport {\n FormatWidth,\n FormStyle,\n getLocaleDateFormat,\n getLocaleDayNames,\n getLocaleFirstDayOfWeek,\n getLocaleMonthNames,\n TranslationWidth,\n} from '@angular/common';\nimport { Inject, Injectable, LOCALE_ID } from '@angular/core';\n\nimport { ClrWeekday } from '../enums/weekday.enum';\nimport { ClrDayOfWeek } from '../interfaces/day-of-week.interface';\n\n/**\n * This service extracts the Angular CLDR data needed by the datepicker.\n */\n@Injectable()\nexport class LocaleHelperService {\n private _firstDayOfWeek: number = ClrWeekday.Sunday;\n private _localeDays: ReadonlyArray<ClrDayOfWeek>;\n private _localeMonthsAbbreviated: ReadonlyArray<string>;\n private _localeMonthsWide: ReadonlyArray<string>;\n private _localeDateFormat: string;\n\n constructor(@Inject(LOCALE_ID) public locale: string) {\n this.initializeLocaleData();\n }\n\n get firstDayOfWeek(): number {\n return this._firstDayOfWeek;\n }\n\n get localeDays(): ReadonlyArray<ClrDayOfWeek> {\n return this._localeDays;\n }\n\n // leave for backward compatibility\n get localeDaysNarrow(): ReadonlyArray<string> {\n return this._localeDays.map(day => day.narrow);\n }\n\n get localeMonthsAbbreviated(): ReadonlyArray<string> {\n return this._localeMonthsAbbreviated;\n }\n\n get localeMonthsWide(): ReadonlyArray<string> {\n return this._localeMonthsWide;\n }\n\n get localeDateFormat(): string {\n return this._localeDateFormat;\n }\n\n /**\n * Overrides the first day of the week regardless of locale.\n * Accepts a `ClrWeekday` value (Sunday=0 through Saturday=6), or null to revert to locale default.\n * Incorrect values will revert to default value (Sunday).\n */\n updateFirstDayOfWeek(day: ClrWeekday | null): void {\n if (day === null || day < ClrWeekday.Sunday || day > ClrWeekday.Saturday) {\n this.initializeLocaleFirstDayOfWeek();\n this.initializeLocaleDays();\n\n return;\n }\n\n this._firstDayOfWeek = day;\n\n this.initializeLocaleDays();\n }\n\n /**\n * Initializes the locale data.\n */\n private initializeLocaleData(): void {\n // Order in which these functions is called is very important.\n this.initializeLocaleFirstDayOfWeek();\n this.initializeLocaleDateFormat();\n this.initializeLocaleMonthsAbbreviated();\n this.initializeLocaleMonthsWide();\n this.initializeLocaleDays();\n }\n\n /**\n * Initialize day names based on the locale.\n * eg: [{day: Sunday, narrow: S}, {day: Monday, narrow: M}...] for en-US.\n */\n private initializeLocaleDays(): void {\n // Get locale day names starting with Sunday\n const tempArr = [];\n const tempWideArr: string[] = getLocaleDayNames(this.locale, FormStyle.Standalone, TranslationWidth.Wide).slice();\n const tempNarrowArr: string[] = getLocaleDayNames(\n this.locale,\n FormStyle.Standalone,\n TranslationWidth.Narrow\n ).slice();\n\n for (let i = 0; i < 7; i++) {\n tempArr.push({ day: tempWideArr[i], narrow: tempNarrowArr[i] });\n }\n\n // Rearrange the tempArr to start with the first day of the week based on the locale (default or override).\n if (this.firstDayOfWeek > ClrWeekday.Sunday) {\n const prevDays: { day: string; narrow: string }[] = tempArr.splice(0, this.firstDayOfWeek);\n tempArr.push(...prevDays);\n }\n\n this._localeDays = tempArr;\n }\n\n /**\n * Initializes the array of month names in the TranslationWidth.Abbreviated format.\n * e.g. `[Jan, Feb, ...]` for en-US\n */\n private initializeLocaleMonthsAbbreviated(): void {\n this._localeMonthsAbbreviated = getLocaleMonthNames(\n this.locale,\n FormStyle.Standalone,\n TranslationWidth.Abbreviated\n ).slice();\n }\n\n /**\n * Initializes the array of month names in the TranslationWidth.Wide format.\n * e.g. `[January, February, ...]` for en-US\n */\n private initializeLocaleMonthsWide(): void {\n this._localeMonthsWide = getLocaleMonthNames(this.locale, FormStyle.Standalone, TranslationWidth.Wide).slice();\n }\n\n /**\n * Initializes the first day of the week based on the locale.\n */\n private initializeLocaleFirstDayOfWeek(): void {\n this._firstDayOfWeek = getLocaleFirstDayOfWeek(this.locale);\n }\n\n private initializeLocaleDateFormat(): void {\n this._localeDateFormat = getLocaleDateFormat(this.locale, FormatWidth.Short);\n }\n}\n","/*\n * Copyright (c) 2016-2026 Broadcom. All Rights Reserved.\n * The term \"Broadcom\" refers to Broadcom Inc. and/or its subsidiaries.\n * This software is released under MIT license.\n * The full license information can be found in LICENSE in the root directory of this project.\n */\n\nimport { Injectable } from '@angular/core';\nimport { Subject } from 'rxjs';\n\nimport { LocaleHelperService } from './locale-helper.service';\nimport { DateRange, DateRangeOption } from '../interfaces/date-range.interface';\nimport { DayModel } from '../model/day.model';\nimport {\n BIG_ENDIAN,\n DEFAULT_LOCALE_FORMAT,\n DELIMITER_REGEX,\n InputDateDisplayFormat,\n LITTLE_ENDIAN,\n LITTLE_ENDIAN_REGEX,\n MIDDLE_ENDIAN,\n MIDDLE_ENDIAN_REGEX,\n RTL_REGEX,\n USER_INPUT_REGEX,\n} from '../utils/constants';\nimport { getNumberOfDaysInTheMonth, parseToFourDigitYear } from '../utils/date-utils';\n\n@Injectable()\nexport class DateIOService {\n /**\n * This is the default range. It approximates the beginning of time to the end of time.\n * The disabled dates are the dates that are not allowed to be selected.\n * The min date is the earliest date that can be selected.\n * The max date is the latest date that can be selected.\n * Unless a minDate or maxDate is set with the native HTML5 api the range is all dates\n */\n disabledDates: DateRange = {\n minDate: new DayModel(0, 0, 1),\n maxDate: new DayModel(9999, 11, 31),\n };\n\n cldrLocaleDateFormat: string = DEFAULT_LOCALE_FORMAT;\n minDateChange = new Subject<DayModel>();\n maxDateChange = new Subject<DayModel>();\n\n private dateRangeOptions;\n private localeDisplayFormat: InputDateDisplayFormat = LITTLE_ENDIAN;\n private delimiters: [string, string] = ['/', '/'];\n\n constructor(localeHelperService: LocaleHelperService) {\n this.cldrLocaleDateFormat = localeHelperService.localeDateFormat;\n this.initializeLocaleDisplayFormat();\n }\n\n get placeholderText(): string {\n const format: [string, string, string] = this.localeDisplayFormat.format;\n return format[0] + this.delimiters[0] + format[1] + this.delimiters[1] + format[2];\n }\n\n setMinDate(date: string): void {\n // NOTE: I'm expecting consumers to pass one of four things here:\n // A proper date string(2019-11-11), null, undefined or empty string ('')\n if (!date) {\n // attribute binding was removed, reset back to the beginning of time\n this.disabledDates.minDate = new DayModel(0, 0, 1);\n } else {\n const [year, month, day] = date.split('-').map(n => parseInt(n, 10));\n this.disabledDates.minDate = new DayModel(year, month - 1, day);\n }\n this.minDateChange.next(this.disabledDates.minDate);\n }\n\n setMaxDate(date: string): void {\n // NOTE: I'm expecting consumers to pass one of four things here:\n // A proper date string(2019-11-11), null, undefined or empty string ('')\n if (!date) {\n // attribute binding was removed, reset forward to the end of time\n this.disabledDates.maxDate = new DayModel(9999, 11, 31);\n } else {\n const [year, month, day] = date.split('-').map(n => parseInt(n, 10));\n this.disabledDates.maxDate = new DayModel(year, month - 1, day);\n }\n this.maxDateChange.next(this.disabledDates.maxDate);\n }\n\n setRangeOptions(rangeOptions: DateRangeOption[]) {\n const validatedRangeOption = this.validateDateRangeOptions(rangeOptions);\n this.dateRangeOptions = validatedRangeOption || [];\n }\n\n getRangeOptions() {\n return this.dateRangeOptions;\n }\n\n toLocaleDisplayFormatString(date: Date): string {\n if (date) {\n if (isNaN(date.getTime())) {\n return '';\n }\n const dateNo: number = date.getDate();\n const monthNo: number = date.getMonth() + 1;\n const dateStr: string = dateNo > 9 ? dateNo.toString() : '0' + dateNo;\n const monthStr: string = monthNo > 9 ? monthNo.toString() : '0' + monthNo;\n if (this.localeDisplayFormat === LITTLE_ENDIAN) {\n return dateStr + this.delimiters[0] + monthStr + this.delimiters[1] + date.getFullYear();\n } else if (this.localeDisplayFormat === MIDDLE_ENDIAN) {\n return monthStr + this.delimiters[0] + dateStr + this.delimiters[1] + date.getFullYear();\n } else {\n return date.getFullYear() + this.delimiters[0] + monthStr + this.delimiters[1] + dateStr;\n }\n }\n return '';\n }\n\n getDateValueFromDateString(date: string): Date {\n if (!date || typeof date !== 'string') {\n return null;\n }\n const dateParts: string[] = date.match(USER_INPUT_REGEX);\n if (!dateParts || dateParts.length !== 3) {\n return null;\n }\n const [firstPart, secondPart, thirdPart] = dateParts;\n if (this.localeDisplayFormat === LITTLE_ENDIAN) {\n // secondPart is month && firstPart is date\n return this.validateAndGetDate(thirdPart, secondPart, firstPart);\n } else if (this.localeDisplayFormat === MIDDLE_ENDIAN) {\n // firstPart is month && secondPart is date\n return this.validateAndGetDate(thirdPart, firstPart, secondPart);\n } else {\n // secondPart is month && thirdPart is date\n return this.validateAndGetDate(firstPart, secondPart, thirdPart);\n }\n }\n\n private validateDateRangeOptions(rangeOptions: DateRangeOption[]): DateRangeOption[] {\n const validOptions = [];\n rangeOptions?.forEach((rangeOption: DateRangeOption) => {\n if (\n rangeOption?.value?.length !== 2 ||\n Object.prototype.toString.call(rangeOption?.value[0]) !== '[object Date]' ||\n Object.prototype.toString.call(rangeOption?.value[1]) !== '[object Date]'\n ) {\n return;\n }\n validOptions.push(rangeOption);\n });\n return validOptions;\n }\n\n private initializeLocaleDisplayFormat(): void {\n const format: string = this.cldrLocaleDateFormat.toLocaleLowerCase();\n if (LITTLE_ENDIAN_REGEX.test(format)) {\n this.localeDisplayFormat = LITTLE_ENDIAN;\n } else if (MIDDLE_ENDIAN_REGEX.test(format)) {\n this.localeDisplayFormat = MIDDLE_ENDIAN;\n } else {\n // everything else is set to BIG-ENDIAN FORMAT\n this.localeDisplayFormat = BIG_ENDIAN;\n }\n this.extractDelimiters();\n }\n\n private extractDelimiters(): void {\n if (this.cldrLocaleDateFormat) {\n // Sanitize Date Format. Remove RTL characters.\n // FIXME: When we support RTL, remove this and handle it correctly.\n const localeFormat: string = this.cldrLocaleDateFormat.replace(RTL_REGEX, '');\n const delimiters: string[] = localeFormat.split(DELIMITER_REGEX);\n\n // NOTE: The split from the CLDR date format should always result\n // in an arary with 4 elements. The 1st and the 2nd values are the delimiters\n // we will use in order.\n // Eg: \"dd/MM/y\".split(/d+|m+|y+/i) results in [\"\", \"/\", \"/\", \"\"]\n if (delimiters && delimiters.length === 4) {\n this.delimiters = [delimiters[1], delimiters[2]];\n } else {\n console.error('Unexpected date format received. Delimiters extracted: ', delimiters);\n }\n }\n }\n\n /**\n * Checks if the month entered by the user is valid or not.\n * Note: Month is 0 based.\n */\n private isValidMonth(month: number): boolean {\n return month > -1 && month < 12;\n }\n\n /**\n * Checks if the date is valid depending on the year and month provided.\n */\n private isValidDate(year: number, month: number, date: number): boolean {\n return date > 0 && date <= getNumberOfDaysInTheMonth(year, month);\n }\n\n /**\n * Validates the parameters provided and returns the date.\n * If the parameters are not\n * valid then return null.\n * NOTE: (Month here is 1 based since the user has provided that as an input)\n */\n private validateAndGetDate(year: string, month: string, date: string): Date {\n // I don't know whats wrong with the TS compiler. It throws an error if I write\n // the below if statement. The error is:\n // Operator '!==' cannot be applied to types '2' and '4'\n // More info here: https://github.com/Microsoft/TypeScript/issues/12794#issuecomment-270342936\n /*\n if (year.length !== 2 || year.length !== 4) {\n return null;\n }\n */\n\n // Instead I have to write the logic like this x-(\n const y: number = +year;\n const m: number = +month - 1; // month is 0 based\n const d: number = +date;\n if (!this.isValidMonth(m) || !this.isValidDate(y, m, d)) {\n return null;\n }\n const result: number = parseToFourDigitYear(y);\n return result !== -1 ? new Date(result, m, d) : null;\n }\n}\n","/*\n * Copyright (c) 2016-2026 Broadcom. All Rights Reserved.\n * The term \"Broadcom\" refers to Broadcom Inc. and/or its subsidiaries.\n * This software is released under MIT license.\n * The full license information can be found in LICENSE in the root directory of this project.\n */\n\nimport { DayModel } from './day.model';\nimport { getNumberOfDaysInTheMonth } from '../utils/date-utils';\n\nexport class CalendarModel {\n days: DayModel[];\n\n constructor(\n readonly year: number,\n readonly month: number\n ) {\n this.initializeDaysInCalendar();\n }\n\n /**\n * Checks if the calendar passed is equal to the current calendar.\n */\n isEqual(calendar: CalendarModel) {\n if (calendar) {\n return this.year === calendar.year && this.month === calendar.month;\n }\n return false;\n }\n\n /**\n * Checks if a DayModel is in the Calendar\n */\n isDayInCalendar(day: DayModel): boolean {\n if (day) {\n return this.year === day.year && this.month === day.month;\n }\n return false;\n }\n\n /**\n * Returns CalendarModel of the previous month.\n */\n previousMonth(): CalendarModel {\n if (this.month === 0) {\n return new CalendarModel(this.year - 1, 11);\n } else {\n return new CalendarModel(this.year, this.month - 1);\n }\n }\n\n /**\n * Returns CalendarModel of the next month.\n */\n nextMonth(): CalendarModel {\n if (this.month === 11) {\n return new CalendarModel(this.year + 1, 0);\n } else {\n return new CalendarModel(this.year, this.month + 1);\n }\n }\n\n /**\n * Returns CalendarModel of the previous year.\n */\n previousYear(): CalendarModel {\n return new CalendarModel(this.year - 1, this.month);\n }\n\n /**\n * Returns CalendarModel of the next year.\n */\n nextYear(): CalendarModel {\n return new CalendarModel(this.year + 1, this.month);\n }\n\n /**\n * Populates the days array with the DayModels in the current Calendar.\n */\n private initializeDaysInCalendar(): void {\n const noOfDaysInCalendar: number = getNumberOfDaysInTheMonth(this.year, this.month);\n this.days = Array(noOfDaysInCalendar)\n .fill(null)\n .map((_date, index) => {\n return new DayModel(this.year, this.month, index + 1);\n });\n }\n}\n","/*\n * Copyright (c) 2016-2026 Broadcom. All Rights Reserved.\n * The term \"Broadcom\" refers to Broadcom Inc. and/or its subsidiaries.\n * This software is released under MIT license.\n * The full license information can be found in LICENSE in the root directory of this project.\n */\n\nimport { Injectable } from '@angular/core';\nimport { Observable, Subject, tap } from 'rxjs';\n\nimport { DateRangeInput } from '../interfaces/date-range.interface';\nimport { CalendarModel } from '../model/calendar.model';\nimport { DayModel } from '../model/day.model';\n\n/**\n * This service is responsible for:\n * 1. Initializing the displayed calendar.\n * 2. Moving the calendar to the next, previous or current months\n * 3. Managing the focused and selected day models.\n */\n@Injectable()\nexport class DateNavigationService {\n persistedDate: DayModel;\n persistedEndDate: DayModel;\n selectedDay: DayModel;\n selectedEndDay: DayModel;\n focusedDay: DayModel;\n hoveredDay: DayModel;\n hoveredMonth: number;\n hoveredYear: number;\n isRangePicker = false;\n hasActionButtons = false;\n\n private _displayedCalendar: CalendarModel;\n private _todaysFullDate: Date = new Date();\n private _today: DayModel;\n private _selectedDayChange = new Subject<DayModel>();\n private _selectedEndDayChange = new Subject<DayModel>();\n private _displayedCalendarChange = new Subject<void>();\n private _focusOnCalendarChange = new Subject<void>();\n private _refreshCalendarView = new Subject<void>();\n private _focusedDayChange = new Subject<DayModel>();\n\n get today(): DayModel {\n return this._today;\n }\n\n get displayedCalendar(): CalendarModel {\n return this._displayedCalendar;\n }\n\n get selectedDayChange(): Observable<DayModel> {\n return this._selectedDayChange.asObservable();\n }\n\n get selectedEndDayChange(): Observable<DayModel> {\n return this._selectedEndDayChange.asObservable();\n }\n\n /**\n * This observable lets the subscriber know that the displayed calendar has changed.\n */\n get displayedCalendarChange(): Observable<void> {\n return this._displayedCalendarChange.asObservable();\n }\n\n /**\n * This observable lets the subscriber know that the focus should be applied on the calendar.\n */\n get focusOnCalendarChange(): Observable<void> {\n return this._focusOnCalendarChange.asObservable();\n }\n\n /**\n * This observable lets the subscriber know that the focused day in the displayed calendar has changed.\n */\n get focusedDayChange(): Observable<DayModel> {\n return this._focusedDayChange.asObservable().pipe(tap((day: DayModel) => (this.focusedDay = day)));\n }\n\n /**\n * This observable lets the subscriber know that the displayed calendar has changed.\n */\n get refreshCalendarView(): Observable<void> {\n return this._refreshCalendarView.asObservable();\n }\n\n /**\n * Notifies that the selected day has changed so that the date can be emitted to the user.\n */\n notifySelectedDayChanged(dayObject: DayModel | DateRangeInput, { emitEvent } = { emitEvent: true }): void {\n if (this.isRangePicker) {\n const { startDate, endDate } = dayObject as DateRangeInput;\n if (startDate && endDate) {\n this.setSelectedDay(startDate, emitEvent);\n this.setSelectedEndDay(endDate, emitEvent);\n } else {\n if (endDate !== null) {\n this.setSelectedEndDay(endDate, emitEvent);\n }\n if (startDate !== null) {\n this.setSelectedDay(startDate, emitEvent);\n }\n }\n } else {\n const day = dayObject as DayModel;\n this.setSelectedDay(day, emitEvent);\n }\n this._refreshCalendarView.next();\n }\n\n /**\n * Initializes the calendar based on the selected day.\n */\n initializeCalendar(): void {\n this.focusedDay = null; // Can be removed later on the store focus\n this.initializeTodaysDate();\n if (this.selectedDay) {\n this._displayedCalendar = new CalendarModel(this.selectedDay.year, this.selectedDay.month);\n } else {\n this._displayedCalendar = new CalendarModel(this.today.year, this.today.month);\n }\n }\n\n changeMonth(month: number): void {\n this.setDisplayedCalendar(new CalendarModel(this._displayedCalendar.year, month));\n }\n\n changeYear(year: number): void {\n this.setDisplayedCalendar(new CalendarModel(year, this._displayedCalendar.month));\n }\n\n /**\n * Moves the displayed calendar to the next month.\n */\n moveToNextMonth(): void {\n this.setDisplayedCalendar(this._displayedCalendar.nextMonth());\n }\n\n /**\n * Moves the displayed calendar to the previous month.\n */\n moveToPreviousMonth(): void {\n this.setDisplayedCalendar(this._displayedCalendar.previousMonth());\n }\n\n /**\n * Moves the displayed calendar to the next year.\n */\n moveToNextYear(): void {\n this.setDisplayedCalendar(this._displayedCalendar.nextYear());\n }\n\n /**\n * Moves the displayed calendar to the previous year.\n */\n moveToPreviousYear(): void {\n this.setDisplayedCalendar(this._displayedCalendar.previousYear());\n }\n\n /**\n * Moves the displayed calendar to the current month and year.\n */\n moveToCurrentMonth(): void {\n if (!this.displayedCalendar.isDayInCalendar(this.today)) {\n this.setDisplayedCalendar(new CalendarModel(this.today.year, this.today.month));\n }\n this._focusOnCalendarChange.next();\n }\n\n moveToSpecificMonth(day: DayModel) {\n if (!this.displayedCalendar.isDayInCalendar(day)) {\n this.setDisplayedCalendar(new CalendarModel(day.year, day.month));\n }\n }\n\n incrementFocusDay(value: number): void {\n this.hoveredDay = this.focusedDay = this.focusedDay.incrementBy(value);\n if (this._displayedCalendar.isDayInCalendar(this.focusedDay)) {\n this._focusedDayChange.next(this.focusedDay);\n } else {\n this.setDisplayedCalendar(new CalendarModel(this.focusedDay.year, this.focusedDay.month));\n }\n this._focusOnCalendarChange.next();\n }\n\n resetSelectedDay() {\n this.selectedDay = this.persistedDate;\n this.selectedEndDay = this.persistedEndDate;\n }\n\n convertDateToDayModel(date: Date): DayModel {\n return new DayModel(date.getFullYear(), date.getMonth(), date.getDate());\n }\n\n private setSelectedDay(dayModel: DayModel | undefined, emitEvent): void {\n this.selectedDay = dayModel;\n if (emitEvent) {\n this._selectedDayChange.next(dayModel);\n }\n }\n\n private setSelectedEndDay(dayModel: DayModel | undefined, emitEvent): void {\n this.selectedEndDay = dayModel;\n if (emitEvent) {\n this._selectedEndDayChange.next(dayModel);\n }\n }\n\n // not a setter because i want this to remain private\n private setDisplayedCalendar(value: CalendarModel) {\n if (!this._displayedCalendar.isEqual(value)) {\n this._displayedCalendar = value;\n this._displayedCalendarChange.next();\n }\n }\n\n private initializeTodaysDate(): void {\n this._todaysFullDate = new Date();\n this._today = new DayModel(\n this._todaysFullDate.getFullYear(),\n this._todaysFullDate.getMonth(),\n this._todaysFullDate.getDate()\n );\n }\n}\n","/*\n * Copyright (c) 2016-2026 Broadcom. All Rights Reserved.\n * The term \"Broadcom\" refers to Broadcom Inc. and/or its subsidiaries.\n * This software is released under MIT license.\n * The full license information can be found in LICENSE in the root directory of this project.\n */\n\nimport { DOCUMENT, Inject, Injectable } from '@angular/core';\nimport { DATEPICKER_ENABLE_BREAKPOINT } from '@clr/angular/utils';\n\nimport { MOBILE_USERAGENT_REGEX } from '../utils/constants';\n\n@Injectable()\nexport class DatepickerEnabledService {\n private _isUserAgentMobile = false;\n private _innerWidth: number;\n\n constructor(@Inject(DOCUMENT) private _document: any) {\n if (_document) {\n this._isUserAgentMobile = MOBILE_USERAGENT_REGEX.test(_document.defaultView.navigator.userAgent);\n this._innerWidth = _document.defaultView.innerWidth;\n }\n }\n\n /**\n * Returns if the calendar should be active or not.\n * If the user agent is mobile and the screen width is less than DATEPICKER_ACTIVE_BREAKPOINT\n * then the calendar is inactive.\n */\n get isEnabled(): boolean {\n // https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent\n // What they recommend is:\n //\"In summary, we recommend looking for the string 'Mobi'\n // anywhere in the User Agent to detect a mobile device.\"\n if (this._document) {\n if (this._innerWidth < DATEPICKER_ENABLE_BREAKPOINT && this._isUserAgentMobile) {\n return false;\n }\n }\n return true;\n }\n}\n","/*\n * Copyright (c) 2016-2026 Broadcom. All Rights Reserved.\n * The term \"Broadcom\" refers to Broadcom Inc. and/or its subsidiaries.\n * This software is released under MIT license.\n * The full license information can be found in LICENSE in the root directory of this project.\n */\n\nimport { Injectable } from '@angular/core';\nimport { ClrPopoverPosition } from '@clr/angular/popover/common';\n\nconst enum DatepickerViewEnum {\n MONTHVIEW = 'MONTHVIEW',\n YEARVIEW = 'YEARVIEW',\n DAYVIEW = 'DAYVIEW',\n}\n\n/**\n * This service manages which view is visible in the datepicker popover.\n */\n@Injectable()\nexport class ViewManagerService {\n position = ClrPopoverPosition.BOTTOM_LEFT;\n\n private _currentView: DatepickerViewEnum = DatepickerViewEnum.DAYVIEW;\n\n get isDayView(): boolean {\n return this._currentView === DatepickerViewEnum.DAYVIEW;\n }\n\n get isYearView(): boolean {\n return this._currentView === DatepickerViewEnum.YEARVIEW;\n }\n\n get isMonthView(): boolean {\n return this._currentView === DatepickerViewEnum.MONTHVIEW;\n }\n\n changeToMonthView(): void {\n this._currentView = DatepickerViewEnum.MONTHVIEW;\n }\n\n changeToYearView(): void {\n this._currentView = DatepickerViewEnum.YEARVIEW;\n }\n\n changeToDayView(): void {\n this._currentView = DatepickerViewEnum.DAYVIEW;\n }\n}\n","/*\n * Copyright (c) 2016-2026 Broadcom. All Rights Reserved.\n * The term \"Broadcom\" refers to Broadcom Inc. and/or its subsidiaries.\n * This software is released under MIT license.\n * The full license information can be found in LICENSE in the root directory of this project.\n */\n\nimport { isPlatformBrowser } from '@angular/common';\nimport { ElementRef, Inject, Injectable, NgZone, PLATFORM_ID } from '@angular/core';\nimport { filter, first } from 'rxjs/operators';\n\n/**\n * This service focuses the day that is focusable in the calendar.\n */\n@Injectable()\nexport class DatepickerFocusService {\n constructor(\n private _ngZone: NgZone,\n @Inject(PLATFORM_ID) private platformId: any\n ) {}\n\n focusCell(elRef: ElementRef<HTMLElement>): void {\n this._ngZone.runOutsideAngular(() => {\n this.ngZoneIsStableInBrowser().subscribe(() => {\n const focusEl = elRef.nativeElement.querySelector<HTMLElement>('[tabindex=\"0\"]');\n if (focusEl) {\n focusEl.focus();\n }\n });\n });\n }\n\n focusInput(element: HTMLInputElement): void {\n this._ngZone.runOutsideAngular(() => this.ngZoneIsStableInBrowser().subscribe(() => element.focus()));\n }\n\n elementIsFocused(element: HTMLInputElement) {\n return isPlatformBrowser(this.platformId) && document.activeElement === element;\n }\n\n private ngZoneIsStableInBrowser() {\n // Credit: Material: https://github.com/angular/material2/blob/master/src/lib/datepicker/calendar.ts\n return this._ngZone.onStable.asObservable().pipe(\n first(),\n filter(() => isPlatformBrowser(this.platformId))\n );\n }\n}\n","/*\n * Copyright (c) 2016-2026 Broadcom. All Rights Reserved.\n * The term \"Broadcom\" refers to Broadcom Inc. and/or its subsidiaries.\n * This software is released under MIT license.\n * The full license information can be found in LICENSE in the root directory of this project.\n */\n\nimport { AfterViewInit, Component, ElementRef, HostListener } from '@angular/core';\nimport { ClrCommonStringsService, Keys } from '@clr/angular/utils';\n\nimport { DateNavigationService } from './providers/date-navigation.service';\nimport { DatepickerFocusService } from './providers/datepicker-focus.service';\nimport { LocaleHelperService } from './providers/locale-helper.service';\nimport { ViewManagerService } from './providers/view-manager.service';\n@Component({\n selector: 'clr-monthpicker',\n template: `\n <div class=\"calendar-header in-monthpicker\">\n <div class=\"year-view-switcher\">\n <button\n class=\"calendar-btn yearpicker-trigger\"\n type=\"button\"\n (click)=\"changeToYearView()\"\n [attr.aria-label]=\"yearAttrString\"\n [attr.title]=\"yearAttrString\"\n >\n {{ calendarYear }}\n </button>\n </div>\n <div class=\"calendar-switchers\">\n <button\n class=\"calendar-btn switcher\"\n type=\"button\"\n (click)=\"previousYear()\"\n [attr.aria-label]=\"commonStrings.keys.datepickerPreviousMonth\"\n >\n <cds-icon shape=\"angle\" direction=\"left\" [attr.title]=\"commonStrings.keys.datepickerPreviousMonth\"></cds-icon>\n </button>\n <button\n class=\"calendar-btn switcher\"\n type=\"button\"\n (click)=\"currentYear()\"\n [attr.aria-label]=\"commonStrings.keys.datepickerCurrentMonth\"\n >\n <cds-icon shape=\"event\" [attr.title]=\"commonStrings.keys.datepickerCurrentMonth\"></cds-icon>\n </button>\n <button\n class=\"calendar-btn switcher\"\n type=\"button\"\n (click)=\"nextYear()\"\n [attr.aria-label]=\"commonStrings.keys.datepickerNextMonth\"\n >\n <cds-icon shape=\"angle\" direction=\"right\" [attr.title]=\"commonStrings.keys.datepickerNextMonth\"></cds-icon>\n </button>\n </div>\n </div>\n <div class=\"months\">\n @for (month of monthNames; track month; let monthIndex = $index) {\n <button\n type=\"button\"\n class=\"calendar-btn month\"\n (click)=\"changeMonth(monthIndex)\"\n [class.is-selected]=\"isSelected(monthIndex)\"\n [class.is-start-range]=\"getIsRangeStartMonth(monthIndex)\"\n [class.is-end-range]=\"getIsRangeEndMonth(monthIndex)\"\n [class.in-range]=\"isInRange(monthIndex)\"\n [attr.tabindex]=\"getTabIndex(monthIndex)\"\n [class.is-today]=\"calendarYear === currentCalendarYear && monthIndex === currentCalendarMonth\"\n (mouseenter)=\"onHover(monthIndex)\"\n >\n {{ month }}\n </button>\n }\n </div>\n `,\n host: {\n '[class.monthpicker]': 'true',\n role: 'application',\n },\n standalone: false,\n})\nexport class ClrMonthpicker implements AfterViewInit {\n /**\n * Keeps track of the current focused month.\n */\n private _focusedMonthIndex: number;\n\n constructor(\n private _localeHelperService: LocaleHelperService,\n private _dateNavigationService: DateNavigationService,\n private _datepickerFocusService: DatepickerFocusService,\n private _elRef: ElementRef,\n private _viewManagerService: ViewManagerService,\n public commonStrings: ClrCommonStringsService\n ) {\n this._focusedMonthIndex = this.calendarMonthIndex;\n }\n /**\n * Gets the months array which is used to rendered the monthpicker view.\n * Months are in the TranslationWidth.Wide format.\n */\n get monthNames(): ReadonlyArray<string> {\n return this._localeHelperService.localeMonthsWide;\n }\n /**\n * Gets the month value of the Calendar.\n */\n get calendarMonthIndex(): number {\n return this._dateNavigationService.displayedCalendar.month;\n }\n\n /**\n * Gets the year which the user is currently on.\n */\n get calendarEndMonthIndex(): number {\n return this._dateNavigationService.selectedEndDay?.month;\n }\n\n get yearAttrString(): string {\n return this.commonStrings.parse(this.commonStrings.keys.datepickerSelectYearText, {\n CALENDAR_YEAR: this.calendarYear.toString(),\n });\n }\n\n /**\n * Returns the year value of the calendar.\n */\n get calendarYear(): number {\n return this._dateNavigationService.displayedCalendar.year;\n }\n\n get currentCalendarYear(): number {\n return new Date().getFullYear();\n }\n\n get currentCalendarMonth(): number {\n return new Date().getMonth();\n }\n\n getIsRangeStartMonth(monthIndex: number): boolean {\n return (\n this._dateNavigationService.isRangePicker &&\n this.calendarYear === this._dateNavigationService.selectedDay?.year &&\n monthIndex === this._dateNavigationService.selectedDay?.month\n );\n }\n\n getIsRangeEndMonth(monthIndex: number): boolean {\n return (\n this._dateNavigationService.isRangePicker &&\n this.calendarYear === this._dateNavigationService.selectedEndDay?.year &&\n monthIndex === this._dateNavigationService.selectedEndDay?.month\n );\n }\n\n /**\n * Calls the ViewManagerService to change to the yearpicker view.\n */\n changeToYearView(): void {\n this._viewManagerService.changeToYearView();\n }\n\n /**\n * Focuses on the current calendar month when the View is initialized.\n */\n ngAfterViewInit() {\n this._datepickerFocusService.focusCell(this._elRef);\n }\n\n /**\n * Handles the Keyboard arrow navigation for the monthpicker.\n */\n @HostListener('keydown', ['$event'])\n onKeyDown(event: KeyboardEvent) {\n // NOTE: Didn't move this to the date navigation service because\n // the logic is fairly simple and it didn't make sense for me\n // to create extra observables just to move this logic to the service.\n if (event) {\n const key = event.key;\n if (key === Keys.ArrowUp && this._focusedMonthIndex > 1) {\n event.preventDefault();\n this._focusedMonthIndex -= 2;\n this._datepickerFocusService.focusCell(this._elRef);\n } else if (key === Keys.ArrowDown && this._focusedMonthIndex < 10) {\n event.preventDefault();\n this._focusedMonthIndex += 2;\n this._datepickerFocusService.focusCell(this._elRef);\n } else if (key === Keys.ArrowRight && this._focusedMonthIndex < 11) {\n event.preventDefault();\n this._focusedMonthIndex++;\n this._datepickerFocusService.focusCell(this._elRef);\n } else if (key === Keys.ArrowLeft && this._focusedMonthIndex > 0) {\n event.preventDefault();\n this._focusedMonthIndex--;\n this._datepickerFocusService.focusCell(this._elRef);\n }\n }\n }\n\n isSelected(monthIndex: number): boolean {\n return (\n (this._dateNavigationService.selectedDay?.year === this.calendarYear &&\n monthIndex === this._dateNavigationService.selectedDay?.month) ||\n (this._dateNavigationService.selectedEndDay?.year === this.calendarYear &&\n monthIndex === this.calendarEndMonthIndex)\n );\n }\n\n /**\n * Calls the DateNavigationService to update the hovered month value of the calendar\n */\n onHover(monthIndex: number): void {\n this._dateNavigationService.hoveredMonth = monthIndex;\n }\n\n /**\n * Calls the DateNavigationService to update the month value of the calendar.\n * Also changes the view to the daypicker.\n */\n changeMonth(monthIndex: number) {\n this._dateNavigationService.changeMonth(monthIndex);\n this._viewManagerService.changeToDayView();\n }\n\n /**\n * Compares the month passed to the focused month and returns the tab index.\n */\n getTabIndex(monthIndex: number): number {\n return monthIndex === this._focusedMonthIndex ? 0 : -1;\n }\n\n /**\n * Calls the DateNavigationService to move to the next month.\n */\n nextYear(): void {\n this._dateNavigationService.moveToNextYear();\n }\n\n /**\n * Calls the DateNavigationService to move to the previous month.\n */\n previousYear(): void {\n this._dateNavigationService.moveToPreviousYear();\n }\n\n /**\n * Calls the DateNavigationService to move to the current month.\n */\n currentYear(): void {\n this._dateNavigationService.moveToCurrentMonth();\n }\n\n /**\n * Applicable only to date range picker\n * Compares the month passed is in between the start and end date range\n */\n isInRange(monthIndex: number): boolean {\n if (!this._dateNavigationService.isRangePicker) {\n return false;\n }\n if (this._dateNavigationService.selectedDay && this._dateNavigationService.selectedEndDay) {\n return (\n (this.calendarYear === this._dateNavigationService.selectedDay.year &&\n monthIndex > this._dateNavigationService.selectedDay.month &&\n this.calendarYear === this._dateNavigationService.selectedEndDay.year &&\n monthIndex < this._dateNavigationService.selectedEndDay.month) ||\n (this._dateNavigationService.selectedDay.year !== this._dateNavigationService.selectedEndDay.year &&\n this.calendarYear === this._dateNavigationService.selectedDay.year &&\n monthIndex > this._dateNavigationService.selectedDay.month) ||\n (this._dateNavigationService.selectedDay.year !== this._dateNavigationService.selectedEndDay.year &&\n this.calendarYear === this._dateNavigationService.selectedEndDay.year &&\n monthIndex < this._dateNavigationService.selectedEndDay.month) ||\n (this.calendarYear > this._dateNavigationService.selectedDay.year &&\n this.calendarYear < this._dateNavigationService.selectedEndDay.year)\n );\n } else if (this._dateNavigationService.selectedDay && !this._dateNavigationService.selectedEndDay) {\n return (\n (this.calendarYear === this._dateNavigationService.selectedDay.year &&\n monthIndex > this._dateNavigationService.selectedDay.month &&\n monthIndex < this._dateNavigationService.hoveredMonth) ||\n (this.calendarYear > this._dateNavigationService.selectedDay.year &&\n monthIndex < this._dateNavigationService.hoveredMonth)\n );\n } else {\n return false;\n }\n }\n}\n","/*\n * Copyright (c) 2016-2026 Broadcom. All Rights Reserved.\n * The term \"Broadcom\" refers to Broadcom Inc. and/or its subsidiaries.\n * This software is released under MIT license.\n * The full license information can be found in LICENSE in the root directory of this project.\n */\n\nconst YEARS_TO_DISPLAY = 10;\n\nexport class YearRangeModel {\n yearRange: number[] = [];\n\n constructor(private readonly year: number) {\n this.generateYearRange();\n }\n\n /**\n * Gets the number in the middle of the range.\n */\n get middleYear(): number {\n return this.yearRange[Math.floor(this.yearRange.length / 2)];\n }\n\n /**\n * Generates the YearRangeModel for the next decade.\n */\n nextDecade(): YearRangeModel {\n return new YearRangeModel(this.year + 10);\n }\n\n /**\n * Generates the YearRangeModel for the previous decade.\n */\n previousDecade(): YearRangeModel {\n return new YearRangeModel(this.year - 10);\n }\n\n /**\n * Generates the YearRangeModel for the current decade.\n */\n currentDecade(): YearRangeModel {\n return new YearRangeModel(new Date().getFullYear());\n }\n\n /**\n * Checks if the value is in the YearRangeModel.\n */\n inRange(value: number): boolean {\n return this.yearRange.indexOf(value) > -1;\n }\n\n /**\n * Generates the year range based on the year parameter.\n * eg: If 2018 is passed the output will be [2010, 2011, ..., 2019]\n */\n private generateYearRange() {\n const remainder: number = this.year % YEARS_TO_DISPLAY;\n const floor: number = this.year - remainder;\n const ceil: number = floor + YEARS_TO_DISPLAY;\n this.yearRange = this.generateRange(floor, ceil);\n }\n\n /**\n * Function which generate a range of numbers from floor to ceil.\n */\n private generateRange(floor: number, ceil: number): number[] {\n return Array.from({ length: