UNPKG

@ng-matero/extensions

Version:
1 lines 271 kB
{"version":3,"file":"mtxDatetimepicker.mjs","sources":["../../../projects/extensions/datetimepicker/datetimepicker-errors.ts","../../../projects/extensions/datetimepicker/datetimepicker-filtertype.ts","../../../projects/extensions/datetimepicker/datetimepicker-intl.ts","../../../projects/extensions/datetimepicker/calendar-body.ts","../../../projects/extensions/datetimepicker/calendar-body.html","../../../projects/extensions/datetimepicker/month-view.ts","../../../projects/extensions/datetimepicker/month-view.html","../../../projects/extensions/datetimepicker/multi-year-view.ts","../../../projects/extensions/datetimepicker/multi-year-view.html","../../../projects/extensions/datetimepicker/clock.ts","../../../projects/extensions/datetimepicker/clock.html","../../../projects/extensions/datetimepicker/time-view.ts","../../../projects/extensions/datetimepicker/time-view.html","../../../projects/extensions/datetimepicker/year-view.ts","../../../projects/extensions/datetimepicker/year-view.html","../../../projects/extensions/datetimepicker/calendar.ts","../../../projects/extensions/datetimepicker/calendar.html","../../../projects/extensions/datetimepicker/datetimepicker.ts","../../../projects/extensions/datetimepicker/datetimepicker-content.html","../../../projects/extensions/datetimepicker/datetimepicker-input.ts","../../../projects/extensions/datetimepicker/datetimepicker-toggle.ts","../../../projects/extensions/datetimepicker/datetimepicker-toggle.html","../../../projects/extensions/datetimepicker/datetimepicker-actions.ts","../../../projects/extensions/datetimepicker/datetimepicker-module.ts","../../../projects/extensions/datetimepicker/mtxDatetimepicker.ts"],"sourcesContent":["/** @docs-private */\nexport function createMissingDateImplError(provider: string) {\n return Error(\n `MtxDatetimepicker: No provider found for ${provider}. You must add one of the following ` +\n `to your app config: provideNativeDatetimeAdapter, provideDateFnsDatetimeAdapter,` +\n `provideLuxonDatetimeAdapter, provideMomentDatetimeAdapter, or provide a ` +\n `custom implementation.`\n );\n}\n","export enum MtxDatetimepickerFilterType {\n DATE,\n HOUR,\n MINUTE,\n}\n","import { Injectable } from '@angular/core';\nimport { Subject } from 'rxjs';\n\n@Injectable({ providedIn: 'root' })\nexport class MtxDatetimepickerIntl {\n /**\n * Stream to emit from when labels are changed. Use this to notify components when the labels have\n * changed after initialization.\n */\n readonly changes = new Subject<void>();\n\n /** A label for the calendar popup (used by screen readers). */\n calendarLabel = 'Calendar';\n\n /** A label for the button used to open the calendar popup (used by screen readers). */\n openCalendarLabel = 'Open calendar';\n\n /** Label for the button used to close the calendar popup. */\n closeCalendarLabel = 'Close calendar';\n\n /** A label for the previous month button (used by screen readers). */\n prevMonthLabel = 'Previous month';\n\n /** A label for the next month button (used by screen readers). */\n nextMonthLabel = 'Next month';\n\n /** A label for the previous year button (used by screen readers). */\n prevYearLabel = 'Previous year';\n\n /** A label for the next year button (used by screen readers). */\n nextYearLabel = 'Next year';\n\n /** A label for the previous multi-year button (used by screen readers). */\n prevMultiYearLabel = 'Previous 24 years';\n\n /** A label for the next multi-year button (used by screen readers). */\n nextMultiYearLabel = 'Next 24 years';\n\n /** A label for the 'switch to month view' button (used by screen readers). */\n switchToMonthViewLabel = 'Choose date';\n\n /** A label for the 'switch to year view' button (used by screen readers). */\n switchToYearViewLabel = 'Choose month';\n\n /** A label for the 'switch to multi-year view' button (used by screen readers). */\n switchToMultiYearViewLabel = 'Choose month and year';\n\n /** A label for the first date of a range of dates (used by screen readers). */\n startDateLabel = 'Start date';\n\n /** A label for the last date of a range of dates (used by screen readers). */\n endDateLabel = 'End date';\n\n /** A label for the week numbers (used by screen readers). */\n weekNumberLabel(number: number): string {\n return `Week ${number}`;\n }\n\n /** Formats a range of years (used for visuals). */\n formatYearRange(start: string, end: string): string {\n return `${start} \\u2013 ${end}`;\n }\n\n /** Formats a label for a range of years (used by screen readers). */\n formatYearRangeLabel(start: string, end: string): string {\n return `${start} to ${end}`;\n }\n\n /** A label for the 'switch to clock hour view' button (used by screen readers). */\n switchToClockHourViewLabel = 'Choose hour';\n\n /** A label for the 'switch to clock minute view' button (used by screen readers). */\n switchToClockMinuteViewLabel = 'Choose minute';\n\n /** Label used for ok button within the manual time input. */\n okLabel = 'OK';\n\n /** Label used for cancel button within the manual time input. */\n cancelLabel = 'Cancel';\n}\n","import {\n afterNextRender,\n booleanAttribute,\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n EventEmitter,\n inject,\n Injector,\n Input,\n NgZone,\n OnChanges,\n Output,\n SimpleChanges,\n ViewEncapsulation,\n} from '@angular/core';\n\nlet uniqueIdCounter = 0;\n\n/**\n * An internal class that represents the data corresponding to a single calendar cell.\n * @docs-private\n */\nexport class MtxCalendarCell {\n readonly id = uniqueIdCounter++;\n\n constructor(\n public value: number,\n public displayValue: string,\n public ariaLabel: string,\n public enabled: boolean,\n public isWeekNumber?: boolean\n ) {}\n}\n\n/**\n * An internal component used to display calendar data in a table.\n * @docs-private\n */\n@Component({\n selector: '[mtx-calendar-body]',\n templateUrl: 'calendar-body.html',\n styleUrl: 'calendar-body.scss',\n host: {\n 'class': 'mtx-calendar-body',\n 'role': 'grid',\n 'aria-readonly': 'true',\n },\n exportAs: 'mtxCalendarBody',\n encapsulation: ViewEncapsulation.None,\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class MtxCalendarBody implements OnChanges {\n private _elementRef = inject<ElementRef<HTMLElement>>(ElementRef);\n private _ngZone = inject(NgZone);\n\n private _injector = inject(Injector);\n\n /** The label for the table. (e.g. \"Jan 2017\"). */\n @Input() label!: string;\n\n /** The cells to display in the table. */\n @Input() rows!: MtxCalendarCell[][];\n\n /** Whether to show week numbers */\n @Input({ transform: booleanAttribute }) showWeekNumbers = false;\n\n /**\n * The aspect ratio (width / height) to use for the cells in the table. This aspect ratio will be\n * maintained even as the table resizes.\n */\n @Input() cellAspectRatio: number = 1;\n\n /** The value in the table that corresponds to today. */\n @Input() todayValue!: number;\n\n /** The value in the table that is currently selected. */\n @Input() selectedValue!: number;\n\n /** The minimum number of free cells needed to fit the label in the first row. */\n @Input() labelMinRequiredCells!: number;\n\n /** The number of columns in the table. */\n @Input() numCols = 7;\n\n /** Whether to allow selection of disabled cells. */\n @Input() allowDisabledSelection = false;\n\n /** The cell number of the active cell in the table. */\n @Input() activeCell = 0;\n\n /** Emits when a new value is selected. */\n @Output() selectedValueChange = new EventEmitter<number>();\n\n /** The number of blank cells to put at the beginning for the first row. */\n _firstRowOffset!: number;\n\n /** Padding for the individual date cells. */\n _cellPadding!: string;\n\n /** Width of an individual cell. */\n _cellWidth!: string;\n\n _cellClicked(cell: MtxCalendarCell): void {\n if (!this.allowDisabledSelection && !cell.enabled) {\n return;\n }\n this.selectedValueChange.emit(cell.value);\n }\n\n _emitActiveDateChange(cell: MtxCalendarCell, event: FocusEvent): void {\n if (cell.enabled) {\n // this.activeDateChange.emit({ value: cell.value, event });\n }\n }\n\n _isActiveCell(rowIndex: number, colIndex: number): boolean {\n const week = this.rows[rowIndex];\n const cell = week[colIndex];\n\n if (cell.isWeekNumber) {\n return false;\n }\n\n return cell.value === this.activeCell + 1;\n }\n\n /**\n * Tracking function for rows based on their identity. Ideally we would use some sort of\n * key on the row, but that would require a breaking change for the `rows` input. We don't\n * use the built-in identity tracking, because it logs warnings.\n */\n _trackRow = (row: MtxCalendarCell[]) => row;\n\n ngOnChanges(changes: SimpleChanges) {\n const columnChanges = changes['numCols'];\n const { rows, numCols } = this;\n\n if (changes['rows'] || columnChanges) {\n this._firstRowOffset = rows && rows.length && rows[0].length ? numCols - rows[0].length : 0;\n }\n\n if (changes['cellAspectRatio'] || columnChanges || !this._cellPadding) {\n this._cellPadding = `${(50 * this.cellAspectRatio) / numCols}%`;\n }\n\n if (columnChanges || !this._cellWidth) {\n this._cellWidth = `${100 / numCols}%`;\n }\n }\n\n _focusActiveCell(movePreview = true) {\n afterNextRender(\n () => {\n setTimeout(() => {\n const activeCell: HTMLElement | null = this._elementRef.nativeElement.querySelector(\n '.mtx-calendar-body-active'\n );\n\n if (activeCell) {\n activeCell.focus();\n }\n });\n },\n { injector: this._injector }\n );\n }\n}\n","<!--\n If there's not enough space in the first row, create a separate label row. We mark this row as\n aria-hidden because we don't want it to be read out as one of the weeks in the month.\n-->\n@if (_firstRowOffset < labelMinRequiredCells) {\n <tr aria-hidden=\"true\">\n <td\n class=\"mtx-calendar-body-label\"\n [attr.colspan]=\"numCols\"\n [style.paddingTop]=\"_cellPadding\"\n [style.paddingBottom]=\"_cellPadding\"\n >\n {{ label }}\n </td>\n </tr>\n}\n\n<!-- Create the first row separately so we can include a special spacer cell. -->\n@for (row of rows; track _trackRow(row); let rowIndex = $index) {\n <tr role=\"row\">\n @if (row[0].isWeekNumber) {\n <th\n class=\"mtx-calendar-body-week-number\"\n [style.paddingTop]=\"_cellPadding\"\n [style.paddingBottom]=\"_cellPadding\"\n [attr.aria-label]=\"row[0].ariaLabel\"\n >\n {{ row[0].displayValue }}\n </th>\n }\n\n <!--\n This cell is purely decorative, but we can't put `aria-hidden` or `role=\"presentation\"` on it,\n because it throws off the week days for the rest of the row on NVDA. The aspect ratio of the\n table cells is maintained by setting the top and bottom padding as a percentage of the width\n (a variant of the trick described here: https://www.w3schools.com/howto/howto_css_aspect_ratio.asp).\n -->\n @if (rowIndex === 0 && _firstRowOffset) {\n <td\n class=\"mtx-calendar-body-label\"\n [attr.colspan]=\"_firstRowOffset\"\n [style.paddingTop]=\"_cellPadding\"\n [style.paddingBottom]=\"_cellPadding\"\n >\n {{ _firstRowOffset >= labelMinRequiredCells ? label : '' }}\n </td>\n }\n <!--\n Each gridcell in the calendar contains a button, which signals to assistive technology that the\n cell is interactable, as well as the selection state via `aria-pressed`. See #23476 for\n background.\n -->\n @for (item of row; track item.id; let colIndex = $index) {\n @if (!item.isWeekNumber) {\n <td\n role=\"gridcell\"\n class=\"mtx-calendar-body-cell-container\"\n [style.width]=\"_cellWidth\"\n [style.paddingTop]=\"_cellPadding\"\n [style.paddingBottom]=\"_cellPadding\"\n [attr.data-mat-row]=\"rowIndex\"\n [attr.data-mat-col]=\"colIndex\"\n >\n <button\n type=\"button\"\n class=\"mtx-calendar-body-cell\"\n [tabindex]=\"_isActiveCell(rowIndex, colIndex) ? 0 : -1\"\n [class.mtx-calendar-body-disabled]=\"!item.enabled\"\n [class.mtx-calendar-body-active]=\"_isActiveCell(rowIndex, colIndex)\"\n [attr.aria-label]=\"item.ariaLabel\"\n [attr.aria-disabled]=\"!item.enabled || null\"\n (click)=\"_cellClicked(item)\"\n >\n <span\n class=\"mtx-calendar-body-cell-content mat-focus-indicator\"\n [class.mtx-calendar-body-selected]=\"selectedValue === item.value\"\n [class.mtx-calendar-body-today]=\"todayValue === item.value\"\n >\n {{ item.displayValue }}\n </span>\n </button>\n </td>\n }\n }\n </tr>\n}\n","import { Directionality } from '@angular/cdk/bidi';\nimport {\n DOWN_ARROW,\n END,\n ENTER,\n HOME,\n LEFT_ARROW,\n PAGE_DOWN,\n PAGE_UP,\n RIGHT_ARROW,\n SPACE,\n UP_ARROW,\n} from '@angular/cdk/keycodes';\nimport {\n AfterContentInit,\n ChangeDetectionStrategy,\n Component,\n EventEmitter,\n Input,\n Output,\n ViewChild,\n ViewEncapsulation,\n booleanAttribute,\n inject,\n} from '@angular/core';\nimport {\n DatetimeAdapter,\n MTX_DATETIME_FORMATS,\n MtxDatetimeFormats,\n} from '@ng-matero/extensions/core';\nimport { MtxCalendarBody, MtxCalendarCell } from './calendar-body';\nimport { createMissingDateImplError } from './datetimepicker-errors';\nimport { MtxDatetimepickerIntl } from './datetimepicker-intl';\nimport { MtxDatetimepickerType } from './datetimepicker-types';\n\nconst DAYS_PER_WEEK = 7;\n\nlet uniqueIdCounter = 0;\n\n/**\n * An internal component used to display a single month in the datetimepicker.\n * @docs-private\n */\n@Component({\n selector: 'mtx-month-view',\n templateUrl: 'month-view.html',\n exportAs: 'mtxMonthView',\n encapsulation: ViewEncapsulation.None,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [MtxCalendarBody],\n})\nexport class MtxMonthView<D> implements AfterContentInit {\n _adapter = inject<DatetimeAdapter<D>>(DatetimeAdapter, { optional: true })!;\n private _dir = inject(Directionality, { optional: true });\n private _dateFormats = inject<MtxDatetimeFormats>(MTX_DATETIME_FORMATS, { optional: true })!;\n private _intl = inject(MtxDatetimepickerIntl);\n\n @Input() type: MtxDatetimepickerType = 'date';\n\n /** A function used to filter which dates are selectable. */\n @Input() dateFilter!: (date: D) => boolean;\n\n /** Whether to show week numbers */\n @Input({ transform: booleanAttribute }) showWeekNumbers = false;\n\n /** Emits when a new date is selected. */\n @Output() selectedChange = new EventEmitter<D>();\n\n /** Emits when any date is selected. */\n @Output() readonly _userSelection = new EventEmitter<void>();\n\n /** Emits when any date is activated. */\n @Output() readonly activeDateChange: EventEmitter<D> = new EventEmitter<D>();\n\n /** The body of calendar table */\n @ViewChild(MtxCalendarBody) _mtxCalendarBody!: MtxCalendarBody;\n\n /** Grid of calendar cells representing the dates of the month. */\n _weeks!: MtxCalendarCell[][];\n\n /** The number of blank cells in the first row before the 1st of the month. */\n _firstWeekOffset!: number;\n\n /**\n * The date of the month that the currently selected Date falls on.\n * Null if the currently selected Date is in another month.\n */\n _selectedDate!: number | null;\n\n /** The date of the month that today falls on. Null if today is in another month. */\n _todayDate!: number | null;\n\n /** The names of the weekdays. */\n _weekdays: { long: string; narrow: string; id: number }[] = [];\n\n constructor() {\n if (!this._adapter) {\n throw createMissingDateImplError('DatetimeAdapter');\n }\n\n if (!this._dateFormats) {\n throw createMissingDateImplError('MTX_DATETIME_FORMATS');\n }\n\n this._activeDate = this._adapter.today();\n }\n\n private _activeDate: D;\n\n /**\n * The date to display in this month view (everything other than the month and year is ignored).\n */\n @Input()\n get activeDate(): D {\n return this._activeDate;\n }\n set activeDate(value: D) {\n const oldActiveDate = this._activeDate;\n this._activeDate = value || this._adapter.today();\n if (\n oldActiveDate &&\n this._activeDate &&\n !this._adapter.sameMonthAndYear(oldActiveDate, this._activeDate)\n ) {\n this._init();\n }\n }\n\n /** The currently selected date. */\n @Input()\n get selected(): D | null {\n return this._selected;\n }\n set selected(value: D | null) {\n this._selected = value;\n this._selectedDate = this._getDateInCurrentMonth(this.selected);\n }\n private _selected: D | null = null;\n\n ngAfterContentInit(): void {\n this._init();\n }\n\n /** Handles when a new date is selected. */\n _dateSelected(date: number) {\n const dateObject = this._adapter.createDatetime(\n this._adapter.getYear(this.activeDate),\n this._adapter.getMonth(this.activeDate),\n date,\n this._adapter.getHour(this.activeDate),\n this._adapter.getMinute(this.activeDate)\n );\n this.selectedChange.emit(dateObject);\n this._activeDate = dateObject;\n\n if (this.type === 'date') {\n this._userSelection.emit();\n }\n }\n\n /** Initializes this month view. */\n private _init() {\n this._selectedDate = this._getDateInCurrentMonth(this.selected);\n this._todayDate = this._getDateInCurrentMonth(this._adapter.today());\n\n const firstOfMonth = this._adapter.createDatetime(\n this._adapter.getYear(this.activeDate),\n this._adapter.getMonth(this.activeDate),\n 1,\n this._adapter.getHour(this.activeDate),\n this._adapter.getMinute(this.activeDate)\n );\n this._firstWeekOffset =\n (DAYS_PER_WEEK +\n this._adapter.getDayOfWeek(firstOfMonth) -\n this._adapter.getFirstDayOfWeek()) %\n DAYS_PER_WEEK;\n\n this._initWeekdays();\n this._createWeekCells();\n }\n\n /** Initializes the weekdays. */\n private _initWeekdays() {\n const firstDayOfWeek = this._adapter.getFirstDayOfWeek();\n const narrowWeekdays = this._adapter.getDayOfWeekNames('narrow');\n const longWeekdays = this._adapter.getDayOfWeekNames('long');\n\n // Rotate the labels for days of the week based on the configured first day of the week.\n const weekdays = longWeekdays.map((long, i) => {\n return { long, narrow: narrowWeekdays[i], id: uniqueIdCounter++ };\n });\n this._weekdays = weekdays.slice(firstDayOfWeek).concat(weekdays.slice(0, firstDayOfWeek));\n }\n\n /** Creates MdCalendarCells for the dates in this month. */\n private _createWeekCells() {\n const daysInMonth = this._adapter.getNumDaysInMonth(this.activeDate);\n const dateNames = this._adapter.getDateNames();\n this._weeks = [[]];\n for (let i = 0, cell = this._firstWeekOffset; i < daysInMonth; i++, cell++) {\n if (cell === DAYS_PER_WEEK) {\n this._weeks.push([]);\n cell = 0;\n }\n const date = this._adapter.createDatetime(\n this._adapter.getYear(this.activeDate),\n this._adapter.getMonth(this.activeDate),\n i + 1,\n this._adapter.getHour(this.activeDate),\n this._adapter.getMinute(this.activeDate)\n );\n\n if (this.showWeekNumbers && (cell === 0 || i === 0)) {\n const firstDayOfWeek = this._adapter.getFirstDayOfWeek();\n const weekNumber = this._adapter.getWeek(date, firstDayOfWeek);\n const ariaLabel = this._intl.weekNumberLabel(weekNumber);\n this._weeks[this._weeks.length - 1].push(\n new MtxCalendarCell(weekNumber, `${weekNumber}`, ariaLabel, false, true)\n );\n }\n\n const enabled = !this.dateFilter || this.dateFilter(date);\n const ariaLabel = this._adapter.format(date, this._dateFormats.display.dateA11yLabel);\n this._weeks[this._weeks.length - 1].push(\n new MtxCalendarCell(i + 1, dateNames[i], ariaLabel, enabled)\n );\n }\n }\n\n /**\n * Gets the date in this month that the given Date falls on.\n * Returns null if the given Date is in another month.\n */\n private _getDateInCurrentMonth(date: D | null) {\n return date && this._adapter.sameMonthAndYear(date, this.activeDate)\n ? this._adapter.getDate(date)\n : null;\n }\n\n /** Handles keydown events on the calendar body when calendar is in month view. */\n _handleCalendarBodyKeydown(event: KeyboardEvent): void {\n // TODO(mmalerba): We currently allow keyboard navigation to disabled dates, but just prevent\n // disabled ones from being selected. This may not be ideal, we should look into whether\n // navigation should skip over disabled dates, and if so, how to implement that efficiently.\n\n const oldActiveDate = this._activeDate;\n const isRtl = this._isRtl();\n\n switch (event.keyCode) {\n case LEFT_ARROW:\n this.activeDate = this._adapter.addCalendarDays(this._activeDate, isRtl ? 1 : -1);\n break;\n case RIGHT_ARROW:\n this.activeDate = this._adapter.addCalendarDays(this._activeDate, isRtl ? -1 : 1);\n break;\n case UP_ARROW:\n this.activeDate = this._adapter.addCalendarDays(this._activeDate, -7);\n break;\n case DOWN_ARROW:\n this.activeDate = this._adapter.addCalendarDays(this._activeDate, 7);\n break;\n case HOME:\n this.activeDate = this._adapter.addCalendarDays(\n this._activeDate,\n 1 - this._adapter.getDate(this._activeDate)\n );\n break;\n case END:\n this.activeDate = this._adapter.addCalendarDays(\n this._activeDate,\n this._adapter.getNumDaysInMonth(this._activeDate) -\n this._adapter.getDate(this._activeDate)\n );\n break;\n case PAGE_UP:\n this.activeDate = event.altKey\n ? this._adapter.addCalendarYears(this._activeDate, -1)\n : this._adapter.addCalendarMonths(this._activeDate, -1);\n break;\n case PAGE_DOWN:\n this.activeDate = event.altKey\n ? this._adapter.addCalendarYears(this._activeDate, 1)\n : this._adapter.addCalendarMonths(this._activeDate, 1);\n break;\n case ENTER:\n case SPACE:\n if (!this.dateFilter || this.dateFilter(this._activeDate)) {\n this._dateSelected(this._adapter.getDate(this._activeDate));\n // this._userSelection.emit();\n // Prevent unexpected default actions such as form submission.\n event.preventDefault();\n }\n return;\n default:\n // Don't prevent default or focus active cell on keys that we don't explicitly handle.\n return;\n }\n\n if (this._adapter.compareDate(oldActiveDate, this.activeDate)) {\n this.activeDateChange.emit(this.activeDate);\n }\n\n this._focusActiveCell();\n // Prevent unexpected default actions such as form submission.\n event.preventDefault();\n }\n\n /** Focuses the active cell after the microtask queue is empty. */\n _focusActiveCell(movePreview?: boolean) {\n this._mtxCalendarBody._focusActiveCell(movePreview);\n }\n\n /** Determines whether the user has the RTL layout direction. */\n private _isRtl() {\n return this._dir && this._dir.value === 'rtl';\n }\n}\n","<!-- eslint-disable @angular-eslint/template/interactive-supports-focus -->\n<table class=\"mtx-calendar-table\" role=\"grid\">\n <thead class=\"mtx-calendar-table-header\">\n <tr>\n @if (showWeekNumbers) {\n <th></th>\n }\n @for (day of _weekdays; track day.id) {\n <th [attr.aria-label]=\"day.long\">{{ day.narrow }}</th>\n }\n </tr>\n </thead>\n <tbody\n mtx-calendar-body\n [rows]=\"_weeks\"\n [numCols]=\"showWeekNumbers ? 8 : 7\"\n [showWeekNumbers]=\"showWeekNumbers\"\n [todayValue]=\"_todayDate!\"\n [activeCell]=\"_adapter.getDate(activeDate) - 1\"\n [selectedValue]=\"_selectedDate!\"\n (selectedValueChange)=\"_dateSelected($event)\"\n (keydown)=\"_handleCalendarBodyKeydown($event)\"\n ></tbody>\n</table>\n","import {\n DOWN_ARROW,\n END,\n ENTER,\n HOME,\n LEFT_ARROW,\n PAGE_DOWN,\n PAGE_UP,\n RIGHT_ARROW,\n SPACE,\n UP_ARROW,\n} from '@angular/cdk/keycodes';\nimport {\n AfterContentInit,\n ChangeDetectionStrategy,\n Component,\n EventEmitter,\n Input,\n Output,\n ViewChild,\n ViewEncapsulation,\n inject,\n} from '@angular/core';\nimport {\n DatetimeAdapter,\n MTX_DATETIME_FORMATS,\n MtxDatetimeFormats,\n} from '@ng-matero/extensions/core';\nimport { MtxCalendarBody, MtxCalendarCell } from './calendar-body';\nimport { createMissingDateImplError } from './datetimepicker-errors';\nimport { MtxDatetimepickerType } from './datetimepicker-types';\nimport { Directionality } from '@angular/cdk/bidi';\n\nexport const yearsPerPage = 24;\n\nexport const yearsPerRow = 4;\n\n/**\n * An internal component used to display multiple years in the datetimepicker.\n * @docs-private\n */\n@Component({\n selector: 'mtx-multi-year-view',\n templateUrl: 'multi-year-view.html',\n exportAs: 'mtxMultiYearView',\n encapsulation: ViewEncapsulation.None,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [MtxCalendarBody],\n})\nexport class MtxMultiYearView<D> implements AfterContentInit {\n _adapter = inject<DatetimeAdapter<D>>(DatetimeAdapter, { optional: true })!;\n private _dir = inject(Directionality, { optional: true });\n private _dateFormats = inject<MtxDatetimeFormats>(MTX_DATETIME_FORMATS, { optional: true })!;\n\n @Input() type: MtxDatetimepickerType = 'date';\n\n /** A function used to filter which dates are selectable. */\n @Input() dateFilter!: (date: D) => boolean;\n\n /** Emits when a new month is selected. */\n @Output() selectedChange = new EventEmitter<D>();\n\n /** Emits when any date is selected. */\n @Output() readonly _userSelection = new EventEmitter<void>();\n\n /** Emits when any date is activated. */\n @Output() readonly activeDateChange: EventEmitter<D> = new EventEmitter<D>();\n\n /** The body of calendar table */\n @ViewChild(MtxCalendarBody) _mtxCalendarBody!: MtxCalendarBody;\n\n /** Grid of calendar cells representing the years in the range. */\n _years!: MtxCalendarCell[][];\n\n /** The label for this year range (e.g. \"2000-2020\"). */\n _yearLabel!: string;\n\n /** The year in this range that today falls on. Null if today is in a different range. */\n _todayYear!: number;\n\n /**\n * The year in this range that the selected Date falls on.\n * Null if the selected Date is in a different range.\n */\n _selectedYear!: number | null;\n\n constructor() {\n if (!this._adapter) {\n throw createMissingDateImplError('DatetimeAdapter');\n }\n\n if (!this._dateFormats) {\n throw createMissingDateImplError('MTX_DATETIME_FORMATS');\n }\n\n this._activeDate = this._adapter.today();\n }\n\n /** The date to display in this multi year view */\n @Input()\n get activeDate(): D {\n return this._activeDate;\n }\n set activeDate(value: D) {\n const oldActiveDate = this._activeDate;\n this._activeDate = value || this._adapter.today();\n if (\n oldActiveDate &&\n this._activeDate &&\n !isSameMultiYearView(\n this._adapter,\n oldActiveDate,\n this._activeDate,\n this.minDate,\n this.maxDate\n )\n ) {\n this._init();\n }\n }\n private _activeDate: D;\n\n /** The currently selected date. */\n @Input()\n get selected(): D | null {\n return this._selected;\n }\n set selected(value: D | null) {\n this._selected = value;\n this._selectedYear = this._selected && this._adapter.getYear(this._selected);\n }\n private _selected: D | null = null;\n\n /** The minimum selectable date. */\n @Input()\n get minDate(): D | null {\n return this._minDate;\n }\n set minDate(value: D | null) {\n this._minDate = this._getValidDateOrNull(this._adapter.deserialize(value));\n }\n private _minDate!: D | null;\n\n /** The maximum selectable date. */\n @Input()\n get maxDate(): D | null {\n return this._maxDate;\n }\n set maxDate(value: D | null) {\n this._maxDate = this._getValidDateOrNull(this._adapter.deserialize(value));\n }\n private _maxDate!: D | null;\n\n ngAfterContentInit() {\n this._init();\n }\n\n /** Handles when a new year is selected. */\n _yearSelected(year: number) {\n const month = this._adapter.getMonth(this.activeDate);\n const normalizedDate = this._adapter.createDatetime(year, month, 1, 0, 0);\n\n const dateObject = this._adapter.createDatetime(\n year,\n month,\n Math.min(\n this._adapter.getDate(this.activeDate),\n this._adapter.getNumDaysInMonth(normalizedDate)\n ),\n this._adapter.getHour(this.activeDate),\n this._adapter.getMinute(this.activeDate)\n );\n\n this.selectedChange.emit(dateObject);\n this._activeDate = dateObject;\n\n if (this.type === 'year') {\n this._userSelection.emit();\n }\n }\n\n _getActiveCell(): number {\n return getActiveOffset(this._adapter, this.activeDate, this.minDate, this.maxDate);\n }\n\n /** Initializes this year view. */\n private _init() {\n this._todayYear = this._adapter.getYear(this._adapter.today());\n this._yearLabel = this._adapter.getYearName(this.activeDate);\n\n const activeYear = this._adapter.getYear(this.activeDate);\n\n const minYearOfPage =\n activeYear - getActiveOffset(this._adapter, this.activeDate, this.minDate, this.maxDate);\n\n this._years = [];\n for (let i = 0, row: number[] = []; i < yearsPerPage; i++) {\n row.push(minYearOfPage + i);\n if (row.length === yearsPerRow) {\n this._years.push(row.map(year => this._createCellForYear(year)));\n row = [];\n }\n }\n }\n\n /** Creates an MtxCalendarCell for the given year. */\n private _createCellForYear(year: number) {\n const yearName = this._adapter.getYearName(this._adapter.createDate(year, 0, 1));\n return new MtxCalendarCell(year, yearName, yearName, this._shouldEnableYear(year));\n }\n\n /** Whether the given year is enabled. */\n private _shouldEnableYear(year: number) {\n // disable if the year is greater than maxDate lower than minDate\n if (\n year === undefined ||\n year === null ||\n (this.maxDate && year > this._adapter.getYear(this.maxDate)) ||\n (this.minDate && year < this._adapter.getYear(this.minDate))\n ) {\n return false;\n }\n\n // enable if it reaches here and there's no filter defined\n if (!this.dateFilter) {\n return true;\n }\n\n const firstOfYear = this._adapter.createDate(year, 0, 1);\n\n // If any date in the year is enabled count the year as enabled.\n for (\n let date = firstOfYear;\n this._adapter.getYear(date) === year;\n date = this._adapter.addCalendarDays(date, 1)\n ) {\n if (this.dateFilter(date)) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * @param obj The object to check.\n * @returns The given object if it is both a date instance and valid, otherwise null.\n */\n _getValidDateOrNull(obj: any): D | null {\n return this._adapter.isDateInstance(obj) && this._adapter.isValid(obj) ? obj : null;\n }\n\n /** Handles keydown events on the calendar body when calendar is in multi-year view. */\n _handleCalendarBodyKeydown(event: KeyboardEvent): void {\n const oldActiveDate = this._activeDate;\n const isRtl = this._isRtl();\n\n switch (event.keyCode) {\n case LEFT_ARROW:\n this.activeDate = this._adapter.addCalendarYears(this._activeDate, isRtl ? 1 : -1);\n break;\n case RIGHT_ARROW:\n this.activeDate = this._adapter.addCalendarYears(this._activeDate, isRtl ? -1 : 1);\n break;\n case UP_ARROW:\n this.activeDate = this._adapter.addCalendarYears(this._activeDate, -yearsPerRow);\n break;\n case DOWN_ARROW:\n this.activeDate = this._adapter.addCalendarYears(this._activeDate, yearsPerRow);\n break;\n case HOME:\n this.activeDate = this._adapter.addCalendarYears(\n this._activeDate,\n -getActiveOffset(this._adapter, this._activeDate, this.minDate, this.maxDate)\n );\n break;\n case END:\n this.activeDate = this._adapter.addCalendarYears(\n this._activeDate,\n yearsPerPage -\n getActiveOffset(this._adapter, this._activeDate, this.minDate, this.maxDate) -\n 1\n );\n break;\n case PAGE_UP:\n this.activeDate = this._adapter.addCalendarYears(\n this._activeDate,\n event.altKey ? -yearsPerPage * 10 : -yearsPerPage\n );\n break;\n case PAGE_DOWN:\n this.activeDate = this._adapter.addCalendarYears(\n this._activeDate,\n event.altKey ? yearsPerPage * 10 : yearsPerPage\n );\n break;\n case ENTER:\n case SPACE:\n this._yearSelected(this._adapter.getYear(this._activeDate));\n break;\n default:\n // Don't prevent default or focus active cell on keys that we don't explicitly handle.\n return;\n }\n\n if (this._adapter.compareDate(oldActiveDate, this.activeDate)) {\n this.activeDateChange.emit(this.activeDate);\n }\n\n this._focusActiveCell();\n // Prevent unexpected default actions such as form submission.\n event.preventDefault();\n }\n\n /** Focuses the active cell after the microtask queue is empty. */\n _focusActiveCell() {\n this._mtxCalendarBody._focusActiveCell();\n }\n\n /** Determines whether the user has the RTL layout direction. */\n private _isRtl() {\n return this._dir && this._dir.value === 'rtl';\n }\n}\n\nexport function isSameMultiYearView<D>(\n dateAdapter: DatetimeAdapter<D>,\n date1: D,\n date2: D,\n minDate: D | null,\n maxDate: D | null\n): boolean {\n const year1 = dateAdapter.getYear(date1);\n const year2 = dateAdapter.getYear(date2);\n const startingYear = getStartingYear(dateAdapter, minDate, maxDate);\n return (\n Math.floor((year1 - startingYear) / yearsPerPage) ===\n Math.floor((year2 - startingYear) / yearsPerPage)\n );\n}\n\n/**\n * When the multi-year view is first opened, the active year will be in view.\n * So we compute how many years are between the active year and the *slot* where our\n * \"startingYear\" will render when paged into view.\n */\nexport function getActiveOffset<D>(\n dateAdapter: DatetimeAdapter<D>,\n activeDate: D,\n minDate: D | null,\n maxDate: D | null\n): number {\n const activeYear = dateAdapter.getYear(activeDate);\n return euclideanModulo(activeYear - getStartingYear(dateAdapter, minDate, maxDate), yearsPerPage);\n}\n\n/**\n * We pick a \"starting\" year such that either the maximum year would be at the end\n * or the minimum year would be at the beginning of a page.\n */\nfunction getStartingYear<D>(\n dateAdapter: DatetimeAdapter<D>,\n minDate: D | null,\n maxDate: D | null\n): number {\n let startingYear = 0;\n if (maxDate) {\n const maxYear = dateAdapter.getYear(maxDate);\n startingYear = maxYear - yearsPerPage + 1;\n } else if (minDate) {\n startingYear = dateAdapter.getYear(minDate);\n }\n return startingYear;\n}\n\n/** Gets remainder that is non-negative, even if first number is negative */\nfunction euclideanModulo(a: number, b: number): number {\n return ((a % b) + b) % b;\n}\n","<!-- eslint-disable @angular-eslint/template/interactive-supports-focus -->\n<table class=\"mtx-calendar-table\" role=\"grid\">\n <thead class=\"mtx-calendar-table-header\"></thead>\n <tbody mtx-calendar-body\n [rows]=\"_years\"\n [todayValue]=\"_todayYear\"\n [numCols]=\"4\"\n [cellAspectRatio]=\"4 / 7\"\n [activeCell]=\"_getActiveCell()\"\n [allowDisabledSelection]=\"true\"\n [selectedValue]=\"_selectedYear!\"\n (selectedValueChange)=\"_yearSelected($event)\"\n (keydown)=\"_handleCalendarBodyKeydown($event)\"></tbody>\n</table>\n","import { normalizePassiveListenerOptions } from '@angular/cdk/platform';\nimport {\n AfterContentInit,\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n Component,\n DOCUMENT,\n ElementRef,\n EventEmitter,\n Input,\n OnChanges,\n OnDestroy,\n Output,\n ViewEncapsulation,\n booleanAttribute,\n inject,\n} from '@angular/core';\n\nimport { DatetimeAdapter } from '@ng-matero/extensions/core';\nimport { MtxDatetimepickerFilterType } from './datetimepicker-filtertype';\nimport { MtxAMPM } from './datetimepicker-types';\n\nconst activeEventOptions = normalizePassiveListenerOptions({ passive: false });\n\nexport const CLOCK_RADIUS = 50;\nexport const CLOCK_INNER_RADIUS = 27.5;\nexport const CLOCK_OUTER_RADIUS = 41.25;\nexport const CLOCK_TICK_RADIUS = 7.0833;\n\n/** Possible views for datetimepicker clock. */\nexport type MtxClockView = 'hour' | 'minute';\n\nexport interface ClockCell {\n value: number;\n displayValue: string;\n enabled: boolean;\n top: number;\n left: number;\n fontSize?: string;\n}\n\n/**\n * A clock that is used as part of the datetimepicker.\n * @docs-private\n */\n@Component({\n selector: 'mtx-clock',\n templateUrl: 'clock.html',\n styleUrl: 'clock.scss',\n host: {\n 'role': 'clock',\n 'class': 'mtx-clock',\n '(mousedown)': '_pointerDown($event)',\n '(touchstart)': '_pointerDown($event)',\n },\n exportAs: 'mtxClock',\n encapsulation: ViewEncapsulation.None,\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class MtxClock<D> implements AfterContentInit, OnDestroy, OnChanges {\n private _elementRef = inject(ElementRef);\n private _adapter = inject<DatetimeAdapter<D>>(DatetimeAdapter);\n private _changeDetectorRef = inject(ChangeDetectorRef);\n private _document = inject(DOCUMENT);\n\n /** A function used to filter which dates are selectable. */\n @Input() dateFilter!: (date: D, type: MtxDatetimepickerFilterType) => boolean;\n\n /** Step over minutes. */\n @Input() interval: number = 1;\n\n @Input() actionButtons: boolean = false;\n\n /** Whether the clock uses 12 hour format. */\n @Input({ transform: booleanAttribute }) twelvehour: boolean = false;\n\n /** Whether the time is now in AM or PM. */\n @Input() AMPM: MtxAMPM = 'AM';\n\n /** Emits when the currently selected date changes. */\n @Output() selectedChange = new EventEmitter<D>();\n\n /** Emits when any date is activated. */\n @Output() activeDateChange = new EventEmitter<D>();\n\n /** Emits when any date is selected. */\n @Output() readonly _userSelection = new EventEmitter<void>();\n\n /** Whether the clock is in hour view. */\n _hourView: boolean = true;\n\n _hours: ClockCell[] = [];\n\n _minutes: ClockCell[] = [];\n\n _selectedHour!: number;\n\n _selectedMinute!: number;\n\n private _timeChanged = false;\n\n /**\n * The date to display in this clock view.\n */\n @Input()\n get activeDate(): D {\n return this._activeDate;\n }\n set activeDate(value: D) {\n const oldActiveDate = this._activeDate;\n this._activeDate = this._adapter.clampDate(value, this.minDate, this.maxDate);\n if (!this._adapter.sameMinute(oldActiveDate, this._activeDate)) {\n this._init();\n }\n }\n private _activeDate!: D;\n\n /** The currently selected date. */\n @Input()\n get selected(): D | null {\n return this._selected;\n }\n set selected(value: D | null) {\n this._selected = this._adapter.getValidDateOrNull(this._adapter.deserialize(value));\n if (this._selected) {\n this.activeDate = this._selected;\n }\n }\n private _selected!: D | null;\n\n /** The minimum selectable date. */\n @Input()\n get minDate(): D | null {\n return this._minDate;\n }\n set minDate(value: D | null) {\n this._minDate = this._adapter.getValidDateOrNull(this._adapter.deserialize(value));\n }\n private _minDate!: D | null;\n\n /** The maximum selectable date. */\n @Input()\n get maxDate(): D | null {\n return this._maxDate;\n }\n set maxDate(value: D | null) {\n this._maxDate = this._adapter.getValidDateOrNull(this._adapter.deserialize(value));\n }\n private _maxDate!: D | null;\n\n /** Whether the clock should be started in hour or minute view. */\n @Input()\n set startView(value: MtxClockView) {\n this._hourView = value !== 'minute';\n }\n\n get _hand() {\n const hour = this._adapter.getHour(this.activeDate);\n this._selectedHour = hour;\n this._selectedMinute = this._adapter.getMinute(this.activeDate);\n let deg = 0;\n let radius = CLOCK_OUTER_RADIUS;\n if (this._hourView) {\n const outer = this._selectedHour > 0 && this._selectedHour < 13;\n radius = outer ? CLOCK_OUTER_RADIUS : CLOCK_INNER_RADIUS;\n if (this.twelvehour) {\n radius = CLOCK_OUTER_RADIUS;\n }\n deg = Math.round(this._selectedHour * (360 / (24 / 2)));\n } else {\n deg = Math.round(this._selectedMinute * (360 / 60));\n }\n return {\n height: `${radius}%`,\n marginTop: `${50 - radius}%`,\n transform: `rotate(${deg}deg)`,\n };\n }\n\n ngAfterContentInit() {\n this.activeDate = this._activeDate || this._adapter.today();\n this._init();\n }\n\n ngOnDestroy() {\n this._removeGlobalEvents();\n }\n\n ngOnChanges(): void {\n this._init();\n }\n\n /** Called when the user has put their pointer down on the clock. */\n _pointerDown = (event: TouchEvent | MouseEvent) => {\n this._timeChanged = false;\n this.setTime(event);\n this._bindGlobalEvents(event);\n };\n\n /**\n * Called when the user has moved their pointer after\n * starting to drag. Bound on the document level.\n */\n private _pointerMove = (event: TouchEvent | MouseEvent) => {\n if (event.cancelable) {\n event.preventDefault();\n }\n this.setTime(event);\n };\n\n /** Called when the user has lifted their pointer. Bound on the document level. */\n private _pointerUp = (event: TouchEvent | MouseEvent) => {\n if (event.cancelable) {\n event.preventDefault();\n }\n this._removeGlobalEvents();\n\n if (this._timeChanged) {\n this.selectedChange.emit(this.activeDate);\n if (this.actionButtons || !this._hourView) {\n this._userSelection.emit();\n }\n }\n };\n\n /** Binds our global move and end events. */\n private _bindGlobalEvents(triggerEvent: TouchEvent | MouseEvent) {\n // Note that we bind the events to the `document`, because it allows us to capture\n // drag cancel events where the user's pointer is outside the browser window.\n const document = this._document;\n const isTouch = isTouchEvent(triggerEvent);\n const moveEventName = isTouch ? 'touchmove' : 'mousemove';\n const endEventName = isTouch ? 'touchend' : 'mouseup';\n document.addEventListener(moveEventName, this._pointerMove, activeEventOptions);\n document.addEventListener(endEventName, this._pointerUp, activeEventOptions);\n\n if (isTouch) {\n document.addEventListener('touchcancel', this._pointerUp, activeEventOptions);\n }\n }\n\n /** Removes any global event listeners that we may have added. */\n private _removeGlobalEvents() {\n const document = this._document;\n document.removeEventListener('mousemove', this._pointerMove, activeEventOptions);\n document.removeEventListener('mouseup', this._pointerUp, activeEventOptions);\n document.removeEventListener('touchmove', this._pointerMove, activeEventOptions);\n document.removeEventListener('touchend', this._pointerUp, activeEventOptions);\n document.removeEventListener('touchcancel', this._pointerUp, activeEventOptions);\n }\n\n /** Initializes this clock view. */\n private _init() {\n this._hours.length = 0;\n this._minutes.length = 0;\n\n const hourNames = this._adapter.getHourNames();\n const minuteNames = this._adapter.getMinuteNames();\n if (this.twelvehour) {\n const hours: ClockCell[] = [];\n for (let i = 0; i < hourNames.length; i++) {\n const radian = (i / 6) * Math.PI;\n const radius = CLOCK_OUTER_RADIUS;\n\n const hour = i;\n const date = this._adapter.createDatetime(\n this._adapter.getYear(this.activeDate),\n this._adapter.getMonth(this.activeDate),\n this._adapter.getDate(this.activeDate),\n hour,\n 0\n );\n\n // Check if the date is enabled, no need to respect the minute setting here\n const enabled =\n (!this.minDate ||\n (this._adapter.compareDatetime(date, this.minDate, false) as number) >= 0) &&\n (!this.maxDate ||\n (this._adapter.compareDatetime(date, this.maxDate, false) as number) <= 0) &&\n (!this.dateFilter || this.dateFilter(date, MtxDatetimepickerFilterType.HOUR));\n\n // display value for twelvehour clock should be from 1-12 not including 0 and not above 12\n hours.push({\n value: i,\n displayValue: i % 12 === 0 ? '12' : hourNames[i % 12],\n enabled,\n top: CLOCK_RADIUS - Math.cos(radian) * radius - CLOCK_TICK_RADIUS,\n left: CLOCK_RADIUS + Math.sin(radian) * radius - CLOCK_TICK_RADIUS,\n });\n }\n\n // filter out AM or PM hours based on AMPM\n if (this.AMPM === 'AM') {\n this._hours = hours.filter(x => x.value < 12);\n } else {\n this._hours = hours.filter(x => x.value >= 12);\n }\n } else {\n for (let i = 0; i < hourNames.length; i++) {\n const radian = (i / 6) * Math.PI;\n const outer = i > 0 && i < 13;\n const radius = outer ? CLOCK_OUTER_RADIUS : CLOCK_INNER_RADIUS;\n const date = this._adapter.createDatetime(\n this._adapter.getYear(this.activeDate),\n this._adapter.getMonth(this.activeDate),\n this._adapter.getDate(this.activeDate),\n i,\n 0\n );\n\n // Check if the date is enabled, no need to respect the minute setting here\n const enabled =\n (!this.minDate ||\n (this._adapter.compareDatetime(date, this.minDate, false) as number) >= 0) &&\n (!this.maxDate ||\n (this._adapter.compareDatetime(date, this.maxDate, false) as number) <= 0) &&\n (!this.dateFilter || this.dateFilter(date, MtxDatetimepickerFilterType.HOUR));\n\n this._hours.push({\n value: i,\n displayValue: i === 0 ? '00' : hourNames[i],\n enabled,\n top: CLOCK_RADIUS - Math.cos(radian) * radius - CLOCK_TICK_RADIUS,\n left: CLOCK_RADIUS + Math.sin(radian) * radius - CLOCK_TICK_RADIUS,\n fontSize: i > 0 && i < 13 ? '' : '80%',\n });\n }\n }\n\n for (let i = 0; i < minuteNames.length; i += 5) {\n const radian = (i / 30) * Math.PI;\n const date = this._adapter.createDatetime(\n this._adapter.getYear(this.activeDate),\n this._adapter.getMonth(this.activeDate),\n this._adapter.getDate(this.activeDate),\n this._adapter.getHour(this.activeDate),\n i\n );\n const enabled =\n (!this.minDate || (this._adapter.compareDatetime(date, this.minDate) as number) >= 0) &&\n (!this.maxDate || (this._adapter.compareDatetime(date, this.maxDate) as number) <= 0) &&\n (!this.dateFilter || this.dateFilter(date, MtxDatetimepickerFilterType.MINUTE));\n this._minutes.push({\n value: i,\n displayValue: i === 0 ? '00' : minuteNames[i],\n enabled,\n top: CLOCK_RADIUS - Math.cos(radian) * CLOCK_OUTER_RADIUS - CLOCK_TICK_RADIUS,\n left: CLOCK_RADIUS + Math.sin(radian) * CLOCK_OUTER_RADIUS - CLOCK_TICK_RADIUS,\n });\n }\n }\n\n /**\n * Set Time\n * @param event\n */\n private setTime(event: TouchEvent | MouseEvent) {\n const trigger = this._elementRef.nativeElement;\n const triggerRect = trigger.getBoundingClientRect();\n const width = trigger.offsetWidth;\n const height = trigger.offsetHeight;\n const { pageX, pageY } = getPointerPositionOnPage(event);\n const x = width / 2 - (pageX - triggerRect.left - window.pageXOffset);\n const y = height / 2 - (pageY - triggerRect.top - window.pageYOffset);\n\n let radian = Math.atan2(-x, y);\n const unit = Math.PI / (this._hourView ? 6 : this.interval ? 30 / this.interval : 30);\n const z = Math.sqrt(x * x + y * y);\n const outer =\n this._hourView &&\n z > (width * (CLOCK_OUTER_RADIUS / 100) + width * (CLOCK_INNER_RADIUS / 100)) / 2;\n\n if (radian < 0) {\n radian = Math.PI * 2 + radian;\n }\n let value = Math.round(radian / unit);\n\n let date;\n if (this._hourView) {\n if (this.twelvehour) {\n if (this.AMPM === 'AM') {\n value = value === 0 ? 12 : value;\n } else {\n // if we chosen 12 in PM, the value should be 0 for 0:00,\n // else we can safely add 12 to the final value\n value = value === 12 ? 0 : value + 12;\n }\n } else {\n if (value === 12) {\n value = 0;\n }\n value = outer ? (value === 0 ? 12 : value) : value === 0 ? 0 : value + 12;\n }\n\n date = this._adapter.createDatetime(\n this._adapter.getYear(this.activeDate),\n this._adapter.getMonth(this.activeDate),\n this._adapter.getDate(this.activeDate),\n value,\n this._adapter.getMinute(this.activeDate)\n );\n } else {\n if