From 6d4e9c2d22ae9cdf4d2ea73cbca650fd6d3ec4c2 Mon Sep 17 00:00:00 2001 From: Lukas Spirig Date: Thu, 21 Mar 2024 15:09:10 +0100 Subject: [PATCH] refactor(sbb-calendar): implement initial support for other date libraries (#2511) BREAKING CHANGE: The `SbbDatepicker` property `selectedDate` has been renamed to `selected`. This also applies to the attribute `selected-date`, which has been renamed to `selected`. Additionally the `DateAdapter` (and `NativeDateAdapter`) have been superficially refactored. An important change is that the month is now `1`-based, instead of `0`-based. --- src/components/calendar/calendar.e2e.ts | 17 +- src/components/calendar/calendar.spec.ts | 14 +- src/components/calendar/calendar.stories.ts | 23 +- src/components/calendar/calendar.ts | 321 ++++++++++-------- src/components/calendar/readme.md | 24 +- src/components/core/datetime/date-adapter.ts | 196 +++++++---- .../core/datetime/native-date-adapter.spec.ts | 96 ++---- .../core/datetime/native-date-adapter.ts | 144 ++------ src/components/core/interfaces/types.ts | 2 +- .../datepicker-toggle/datepicker-toggle.ts | 6 +- .../datepicker/datepicker/datepicker.ts | 12 +- tsconfig.json | 5 +- 12 files changed, 423 insertions(+), 437 deletions(-) diff --git a/src/components/calendar/calendar.e2e.ts b/src/components/calendar/calendar.e2e.ts index 199f240796..54c390f404 100644 --- a/src/components/calendar/calendar.e2e.ts +++ b/src/components/calendar/calendar.e2e.ts @@ -6,8 +6,9 @@ import { waitForCondition, waitForLitRender, EventSpy } from '../core/testing'; import { SbbCalendarElement } from './calendar'; -describe('sbb-calendar', () => { - const selected = new Date(2023, 0, 15).getTime() / 1000; +import '../button'; + +describe(`sbb-calendar`, () => { let element: SbbCalendarElement; const waitForTransition = async (): Promise => { await waitForLitRender(element); @@ -16,7 +17,7 @@ describe('sbb-calendar', () => { beforeEach(async () => { element = await fixture( - html``, + html``, ); }); @@ -185,7 +186,13 @@ describe('sbb-calendar', () => { expect(monthCells.length).to.be.equal(12); expect(monthCells[0]).dom.to.be.equal(` - @@ -317,7 +324,7 @@ describe('sbb-calendar', () => { describe('navigation for year view', () => { beforeEach(async () => { element = await fixture( - html``, + html``, ); const yearSelectionButton: HTMLElement = element.shadowRoot!.querySelector( diff --git a/src/components/calendar/calendar.spec.ts b/src/components/calendar/calendar.spec.ts index 39e86cfe09..dce4278ae4 100644 --- a/src/components/calendar/calendar.spec.ts +++ b/src/components/calendar/calendar.spec.ts @@ -9,15 +9,12 @@ import './calendar'; describe('sbb-calendar', () => { it('renders', async () => { const root = await fixture( - html``, + html``, ); await waitForLitRender(root); expect(root).dom.to.be.equal( - ``, + ``, ); await expect(root).shadowDom.to.be.equalSnapshot(); }); @@ -25,7 +22,7 @@ describe('sbb-calendar', () => { it('renders with min and max', async () => { const page: HTMLElement = await fixture( html``, @@ -59,10 +56,7 @@ describe('sbb-calendar', () => { }); testA11yTreeSnapshot( - html``, + html``, undefined, { safari: true }, // We skip safari because it has an inconsistent behavior on ci environment ); diff --git a/src/components/calendar/calendar.stories.ts b/src/components/calendar/calendar.stories.ts index 7355ba577c..9ffc669415 100644 --- a/src/components/calendar/calendar.stories.ts +++ b/src/components/calendar/calendar.stories.ts @@ -6,25 +6,26 @@ import type { TemplateResult } from 'lit'; import { html } from 'lit'; import { styleMap } from 'lit/directives/style-map.js'; +import { defaultDateAdapter } from '../core/datetime'; import { sbbSpread } from '../core/dom'; import { SbbCalendarElement } from './calendar'; import readme from './readme.md?raw'; -const getCalendarAttr = (min: Date | string, max: Date | string): Record => { - const attr: Record = {}; +const getCalendarAttr = (min: Date | string, max: Date | string): Record => { + const attr: Record = {}; if (min) { - attr.min = new Date(min); + attr.min = defaultDateAdapter.toIso8601(new Date(min)); } if (max) { - attr.max = new Date(max); + attr.max = defaultDateAdapter.toIso8601(new Date(max)); } return attr; }; -const Template = ({ min, max, selectedDate, dateFilter, ...args }: Args): TemplateResult => html` +const Template = ({ min, max, selected, dateFilter, ...args }: Args): TemplateResult => html` html` = 15 ? 8 : 18); const defaultArgs: Args = { wide: false, - selectedDate: isChromatic() ? new Date(2023, 0, 20) : today, + selected: isChromatic() ? new Date(2023, 0, 20) : today, dataNow: isChromatic() ? new Date(2023, 0, 12, 0, 0, 0).valueOf() : undefined, }; diff --git a/src/components/calendar/calendar.ts b/src/components/calendar/calendar.ts index 7b1ed8d917..4f5b80b468 100644 --- a/src/components/calendar/calendar.ts +++ b/src/components/calendar/calendar.ts @@ -80,10 +80,10 @@ export type CalendarView = 'day' | 'month' | 'year'; /** * It displays a calendar which allows to choose a date. * - * @event {CustomEvent} dateSelected - Event emitted on date selection. + * @event {CustomEvent} dateSelected - Event emitted on date selection. */ @customElement('sbb-calendar') -export class SbbCalendarElement extends LitElement { +export class SbbCalendarElement extends LitElement { public static override styles: CSSResultGroup = style; public static readonly events = { dateSelected: 'dateSelected', @@ -92,26 +92,58 @@ export class SbbCalendarElement extends LitElement { /** If set to true, two months are displayed */ @property({ type: Boolean }) public wide = false; - /** The minimum valid date. Takes Date Object, ISOString, and Unix Timestamp (number of seconds since Jan 1, 1970). */ - @property() public min?: SbbDateLike; + /** The minimum valid date. Takes T Object, ISOString, and Unix Timestamp (number of seconds since Jan 1, 1970). */ + @property() + public set min(value: SbbDateLike | null) { + this._min = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)); + } + public get min(): T | null { + return this._min ?? null; + } + private _min?: T | null; - /** The maximum valid date. Takes Date Object, ISOString, and Unix Timestamp (number of seconds since Jan 1, 1970). */ - @property() public max?: SbbDateLike; + /** The maximum valid date. Takes T Object, ISOString, and Unix Timestamp (number of seconds since Jan 1, 1970). */ + @property() + public set max(value: SbbDateLike | undefined) { + this._max = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)); + } + public get max(): T | null { + return this._max ?? null; + } + private _max?: T | null; + + /** The selected date. Takes T Object, ISOString, and Unix Timestamp (number of seconds since Jan 1, 1970). */ + @property() + public set selected(value: SbbDateLike | undefined) { + this._selectedDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)); + if ( + !!this._selectedDate && + (!this._isDayInRange(this._dateAdapter.toIso8601(this._selectedDate)) || + this._dateFilter(this._selectedDate)) + ) { + this._selected = this._dateAdapter.toIso8601(this._selectedDate); + } else { + this._selected = undefined; + } + } + public get selected(): T | null { + return this._selectedDate ?? null; + } + private _selectedDate?: T | null; /** A function used to filter out dates. */ - @property({ attribute: 'date-filter' }) public dateFilter?: (date: Date | null) => boolean; + @property({ attribute: 'date-filter' }) public dateFilter?: (date: T | null) => boolean; - /** The selected date. Takes Date Object, ISOString, and Unix Timestamp (number of seconds since Jan 1, 1970). */ - @property({ attribute: 'selected-date' }) public selectedDate?: SbbDateLike; + private _dateAdapter: DateAdapter = defaultDateAdapter as unknown as DateAdapter; /** Event emitted on date selection. */ - private _dateSelected: EventEmitter = new EventEmitter( + private _dateSelected: EventEmitter = new EventEmitter( this, SbbCalendarElement.events.dateSelected, ); /** The currently active date. */ - @state() private _activeDate!: Date; + @state() private _activeDate: T = this._now(); /** The selected date as ISOString. */ @state() private _selected?: string; @@ -119,18 +151,10 @@ export class SbbCalendarElement extends LitElement { /** The current wide property considering property value and breakpoints. From zero to small `wide` has always to be false. */ @state() private _wide: boolean = false; - /** Minimum value converted to date */ - @state() private _min?: Date | null; - - /** Maximum value converted to date */ - @state() private _max?: Date | null; - @state() private _calendarView: CalendarView = 'day'; private _nextCalendarView: CalendarView = 'day'; - private _dateAdapter: DateAdapter = defaultDateAdapter; - /** A list of days, in two formats (long and single char). */ private _weekdays!: Weekday[]; @@ -184,37 +208,11 @@ export class SbbCalendarElement extends LitElement { // Workaround to execute initialization immediately after hydration // If no hydration is needed, will be executed before the first rendering this.addController({ - hostConnected: () => { - this._convertMinDate(this.min); - this._convertMaxDate(this.max); - this._setDates(); - this._init(); - }, + hostConnected: () => this.resetPosition(), }); } - private _convertMinDate(newMin?: SbbDateLike): void { - this._min = this._dateAdapter.deserializeDate(newMin); - } - - private _convertMaxDate(newMax?: SbbDateLike): void { - this._max = this._dateAdapter.deserializeDate(newMax); - } - - /** Sets the selected date. */ - private _setSelectedDate(selectedDate: SbbDateLike | null): void { - const value = this._dateAdapter.deserializeDate(selectedDate); - if ( - !!value && - (!this._isDayInRange(this._dateAdapter.getISOString(value)) || this._dateFilter(value)) - ) { - this._selected = this._dateAdapter.getISOString(value); - } else { - this._selected = undefined; - } - } - - private get _dateFilter(): (date: Date) => boolean { + private get _dateFilter(): (date: T) => boolean { return this.dateFilter ?? (() => true); } @@ -223,7 +221,7 @@ export class SbbCalendarElement extends LitElement { if (this._calendarView !== 'day') { this._resetToDayView(); } - this._setDates(); + this._activeDate = this.selected ?? this._now(); this._init(); } @@ -244,21 +242,13 @@ export class SbbCalendarElement extends LitElement { return; } - if (changedProperties.has('min')) { - this._convertMinDate(this.min); - } - if (changedProperties.has('max')) { - this._convertMaxDate(this.max); - } - if (changedProperties.has('selectedDate')) { - this._setSelectedDate(this.selectedDate as Date); - } if (changedProperties.has('wide')) { this.resetPosition(); } } - protected override updated(): void { + protected override updated(changedProperties: PropertyValues): void { + super.updated(changedProperties); // The calendar needs to calculate tab-indexes on first render, // and every time a date is selected or the month view changes. this._setTabIndex(); @@ -271,7 +261,7 @@ export class SbbCalendarElement extends LitElement { } /** Initializes the component. */ - private _init(activeDate?: Date): void { + private _init(activeDate?: T): void { //Due to its complexity, the caledar is only initialized on client side if (isServer) { return; @@ -281,19 +271,13 @@ export class SbbCalendarElement extends LitElement { this._assignActiveDate(activeDate); } this._wide = isBreakpoint('medium') && this.wide; - this._weeks = this._createWeekRows( - this._dateAdapter.getMonth(this._activeDate), - this._dateAdapter.getYear(this._activeDate), - ); + this._weeks = this._createWeekRows(this._activeDate); + this._years = this._createYearRows(); this._nextMonthWeeks = [[]]; this._nextMonthYears = [[]]; - this._years = this._createYearRows(); if (this._wide) { const nextMonthDate = this._dateAdapter.addCalendarMonths(this._activeDate, 1); - this._nextMonthWeeks = this._createWeekRows( - this._dateAdapter.getMonth(nextMonthDate), - this._dateAdapter.getYear(nextMonthDate), - ); + this._nextMonthWeeks = this._createWeekRows(nextMonthDate); this._nextMonthYears = this._createYearRows(YEARS_PER_PAGE); } this._initialized = true; @@ -307,13 +291,6 @@ export class SbbCalendarElement extends LitElement { } } - /** Sets the date variables. */ - private _setDates(): void { - const selectedDate = this._dateAdapter.deserializeDate(this.selectedDate); - this._activeDate = selectedDate ?? this._now(); - this._setSelectedDate(selectedDate); - } - /** Creates the array of weekdays. */ private _setWeekdays(): void { const narrowWeekdays: string[] = this._dateAdapter.getDayOfWeekNames('narrow'); @@ -329,22 +306,26 @@ export class SbbCalendarElement extends LitElement { } /** Creates the rows for each week. */ - private _createWeekRows(month: number, year: number): Day[][] { - const daysInMonth: number = this._dateAdapter.getNumDaysInMonth(year, month); + private _createWeekRows(value: T): Day[][] { + const daysInMonth: number = this._dateAdapter.getNumDaysInMonth(value); const dateNames: string[] = this._dateAdapter.getDateNames(); const weeks: Day[][] = [[]]; - const weekOffset = this._dateAdapter.getFirstWeekOffset(year, month); + const weekOffset = this._dateAdapter.getFirstWeekOffset(value); for (let i = 0, cell = weekOffset; i < daysInMonth; i++, cell++) { if (cell === DAYS_PER_ROW) { weeks.push([]); cell = 0; } - const date = this._dateAdapter.createDate(year, month, i + 1)!; + const date = this._dateAdapter.createDate( + this._dateAdapter.getYear(value), + this._dateAdapter.getMonth(value), + i + 1, + )!; weeks[weeks.length - 1].push({ - value: this._dateAdapter.getISOString(date), + value: this._dateAdapter.toIso8601(date), dayValue: dateNames[i], - monthValue: String(month + 1), - yearValue: String(year), + monthValue: String(this._dateAdapter.getMonth(date)), + yearValue: String(this._dateAdapter.getYear(date)), }); } return weeks; @@ -357,7 +338,7 @@ export class SbbCalendarElement extends LitElement { (_, i: number): Month => ({ value: shortNames[i], longValue: this._monthNames[i], - monthValue: i, + monthValue: i + 1, }), ); const rows: number = 12 / MONTHS_PER_ROW; @@ -370,11 +351,7 @@ export class SbbCalendarElement extends LitElement { /** Creates the rows for the year selection view. */ private _createYearRows(offset: number = 0): number[][] { - const startValueYearView: number = this._dateAdapter.getStartValueYearView( - this._activeDate, - this._min, - this._max, - ); + const startValueYearView: number = this._getStartValueYearView(); const allYears: number[] = new Array(YEARS_PER_PAGE) .fill(0) .map((_, i: number) => startValueYearView + offset + i); @@ -386,6 +363,30 @@ export class SbbCalendarElement extends LitElement { return yearArray; } + /** + * Calculates the first year that will be shown in the year selection panel. + * If `minDate` and `maxDate` are both null, the starting year is calculated as + * the multiple of YEARS_PER_PAGE closest to and less than activeDate, + * e.g., with `YEARS_PER_PAGE` = 24 and `activeDate` = 2020, the function will return 2016 (24 * 83), + * while with `activeDate` = 2000, the function will return 1992 (24 * 82). + * If `minDate` is not null, it returns the corresponding year; if `maxDate` is not null, + * it returns the corresponding year minus `YEARS_PER_PAGE`, so that the `maxDate` is the last rendered year. + * If both are not null, `maxDate` has priority over `minDate`. + */ + private _getStartValueYearView(): number { + let startingYear = 0; + if (this.max) { + startingYear = this._dateAdapter.getYear(this.max) - YEARS_PER_PAGE + 1; + } else if (this.min) { + startingYear = this._dateAdapter.getYear(this.min); + } + const activeYear = this._dateAdapter.getYear(this._activeDate); + return ( + activeYear - + ((((activeYear - startingYear) % YEARS_PER_PAGE) + YEARS_PER_PAGE) % YEARS_PER_PAGE) + ); + } + /** Checks if date is within the min-max range. */ private _isDayInRange(date: string): boolean { if (!this._min && !this._max) { @@ -393,12 +394,10 @@ export class SbbCalendarElement extends LitElement { } const isBeforeMin: boolean = this._dateAdapter.isValid(this._min) && - this._dateAdapter.compareDate(this._min!, this._dateAdapter.createDateFromISOString(date)!) > - 0; + this._dateAdapter.compareDate(this._min!, this._dateAdapter.deserialize(date)!) > 0; const isAfterMax: boolean = this._dateAdapter.isValid(this._max) && - this._dateAdapter.compareDate(this._max!, this._dateAdapter.createDateFromISOString(date)!) < - 0; + this._dateAdapter.compareDate(this._max!, this._dateAdapter.deserialize(date)!) < 0; return !(isBeforeMin || isAfterMax); } @@ -436,7 +435,7 @@ export class SbbCalendarElement extends LitElement { const firstOfMonth = this._dateAdapter.createDate(this._chosenYear!, month, 1)!; for ( - let date: Date = firstOfMonth; + let date: T = firstOfMonth; this._dateAdapter.getMonth(date) == month; date = this._dateAdapter.addCalendarDays(date, 1) ) { @@ -454,9 +453,9 @@ export class SbbCalendarElement extends LitElement { return true; } - const firstOfYear = this._dateAdapter.createDate(year, 0, 1)!; + const firstOfYear = this._dateAdapter.createDate(year, 1, 1)!; for ( - let date: Date = firstOfYear; + let date: T = firstOfYear; this._dateAdapter.getYear(date) == year; date = this._dateAdapter.addCalendarDays(date, 1) ) { @@ -474,11 +473,11 @@ export class SbbCalendarElement extends LitElement { this._chosenYear = undefined; if (this._selected !== day) { this._selected = day; - this._dateSelected.emit(this._dateAdapter.createDateFromISOString(day)!); + this._dateSelected.emit(this._dateAdapter.deserialize(day)!); } } - private _assignActiveDate(date: Date): void { + private _assignActiveDate(date: T): void { if (this._min && this._dateAdapter.compareDate(this._min, date) > 0) { this._activeDate = this._min; return; @@ -498,7 +497,7 @@ export class SbbCalendarElement extends LitElement { private _goToDifferentYear(years: number): void { this._chosenYear! += years; // Can't use `_assignActiveDate(...)` here, because it will set it to min/max value if argument is out of range - this._activeDate = new Date( + this._activeDate = this._dateAdapter.createDate( this._chosenYear!, this._dateAdapter.getMonth(this._activeDate), this._dateAdapter.getDate(this._activeDate), @@ -510,14 +509,14 @@ export class SbbCalendarElement extends LitElement { this._init(this._dateAdapter.addCalendarYears(this._activeDate, years)); } - private _prevDisabled(prevDate: Date): boolean { + private _prevDisabled(prevDate: T): boolean { if (!this._min) { return false; } return this._dateAdapter.compareDate(prevDate, this._min) < 0; } - private _nextDisabled(nextDate: Date): boolean { + private _nextDisabled(nextDate: T): boolean { if (!this._max) { return false; } @@ -526,46 +525,52 @@ export class SbbCalendarElement extends LitElement { /** Checks if the "previous month" button should be disabled. */ private _previousMonthDisabled(): boolean { - const prevMonth: Date = this._dateAdapter.clone(this._activeDate); - prevMonth.setDate(0); + const prevMonth = this._dateAdapter.addCalendarDays( + this._activeDate, + this._dateAdapter.getDate(this._activeDate) * -1, + ); return this._prevDisabled(prevMonth); } /** Checks if the "next month" button should be disabled. */ private _nextMonthDisabled(): boolean { - const nextMonth: Date = this._dateAdapter.addCalendarMonths( - this._activeDate, - this._wide ? 2 : 1, + let nextMonth = this._dateAdapter.addCalendarMonths(this._activeDate, this._wide ? 2 : 1); + nextMonth = this._dateAdapter.createDate( + this._dateAdapter.getYear(nextMonth), + this._dateAdapter.getMonth(nextMonth), + 1, ); - nextMonth.setDate(1); return this._nextDisabled(nextMonth); } private _previousYearDisabled(): boolean { - const prevYear: Date = this._dateAdapter.clone(this._activeDate); - prevYear.setFullYear(this._dateAdapter.getYear(this._activeDate) - 1, 11, 31); + const prevYear = this._dateAdapter.createDate( + this._dateAdapter.getYear(this._activeDate) - 1, + 12, + 31, + ); return this._prevDisabled(prevYear); } private _nextYearDisabled(): boolean { - const nextYear: Date = this._dateAdapter.clone(this._activeDate); - nextYear.setFullYear(this._dateAdapter.getYear(this._activeDate) + 1, 0, 1); + const nextYear = this._dateAdapter.createDate( + this._dateAdapter.getYear(this._activeDate) + 1, + 1, + 1, + ); return this._nextDisabled(nextYear); } private _previousYearRangeDisabled(): boolean { - const prevYear: Date = this._dateAdapter.clone(this._activeDate); - prevYear.setFullYear(this._years.flat()[0] - 1, 11, 31); + const prevYear = this._dateAdapter.createDate(this._years[0][0] - 1, 12, 31); return this._prevDisabled(prevYear); } private _nextYearRangeDisabled(): boolean { - const lastYearArray: number[] = ( - isBreakpoint('medium') && this.wide ? this._nextMonthYears : this._years - ).flat(); - const lastYear: number = lastYearArray[lastYearArray.length - 1]; - const nextYear: Date = this._dateAdapter.clone(this._activeDate); - nextYear.setFullYear(lastYear + 1, 0, 1); + const lastYear = (isBreakpoint('medium') && this.wide ? this._nextMonthYears : this._years) + .at(-1)! + .at(-1)!; + const nextYear = this._dateAdapter.createDate(lastYear + 1, 1, 1); return this._nextDisabled(nextYear); } @@ -586,16 +591,16 @@ export class SbbCalendarElement extends LitElement { } private _getFirstFocusable(): HTMLButtonElement { - const active = this._selected ? new Date(this._selected) : this._now(); + const active = this._selected ? this._dateAdapter.deserialize(this._selected)! : this._now(); let firstFocusable = this.shadowRoot!.querySelector('.sbb-calendar__selected') ?? this.shadowRoot!.querySelector( - `[data-day="${active.getDate()} ${ - this._activeDate.getMonth() + 1 - } ${this._activeDate.getFullYear()}"]`, + `[data-day="${this._dateAdapter.getDate(active)} ${this._dateAdapter.getMonth( + active, + )} ${this._dateAdapter.getYear(active)}"]`, ) ?? - this.shadowRoot!.querySelector(`[data-month="${this._activeDate.getMonth()}"]`) ?? - this.shadowRoot!.querySelector(`[data-year="${this._activeDate.getFullYear()}"]`); + this.shadowRoot!.querySelector(`[data-month="${this._dateAdapter.getMonth(active)}"]`) ?? + this.shadowRoot!.querySelector(`[data-year="${this._dateAdapter.getYear(active)}"]`); if (!firstFocusable || (firstFocusable as HTMLButtonElement)?.disabled) { firstFocusable = this.shadowRoot!.querySelector('.sbb-calendar__cell:not([disabled])'); } @@ -692,7 +697,12 @@ export class SbbCalendarElement extends LitElement { offsetForWideMode: index - indexInView, lastElementIndexForWideMode: index === indexInView - ? this._dateAdapter.getNumDaysInMonth(+day!.yearValue, +day!.monthValue - 1) + ? this._dateAdapter.getNumDaysInMonth( + this._dateAdapter.addCalendarMonths( + this._dateAdapter.deserialize(day!.value)!, + -1, + ), + ) : cells.length, }; } @@ -770,23 +780,23 @@ export class SbbCalendarElement extends LitElement { : this._findNext(days, nextIndex, -verticalOffset); } - private _now(): Date { - if (this._hasDataNow()) { - const today: Date = new Date(+(this.dataset.now as string)); - today.setHours(0, 0, 0, 0); - return today; + private _now(): T { + if (this.hasAttribute('data-now')) { + const today = new Date(+this.getAttribute('data-now')!); + if (defaultDateAdapter.isValid(today)) { + return this._dateAdapter.createDate( + today.getFullYear(), + today.getMonth() + 1, + today.getDate(), + ); + } } return this._dateAdapter.today(); } - private _hasDataNow(): boolean { - const dataNow = +(this.dataset?.now as string); - return !!dataNow; - } - private _resetToDayView(): void { this._resetFocus = true; - this._activeDate = this._dateAdapter.deserializeDate(this.selectedDate) ?? this._now(); + this._activeDate = this.selected ?? this._now(); this._chosenYear = undefined; this._chosenMonth = undefined; this._nextCalendarView = 'day'; @@ -828,9 +838,9 @@ export class SbbCalendarElement extends LitElement { } /** Creates the label with the month for the daily view. */ - private _createLabelForDayView(d: Date): TemplateResult { + private _createLabelForDayView(d: T): TemplateResult { const monthLabel = `${ - this._monthNames[this._dateAdapter.getMonth(d)] + this._monthNames[this._dateAdapter.getMonth(d) - 1] } ${this._dateAdapter.getYear(d)}`; return html`