From 9087e355ee981b1cf9e7e9d3f6e327a85b002404 Mon Sep 17 00:00:00 2001 From: Jeremias Peier Date: Wed, 7 Aug 2024 11:31:00 +0200 Subject: [PATCH 1/8] feat(sbb-calendar, sbb-datepicker-toggle): allow choosing initial calendar view --- src/elements/calendar/calendar.ts | 38 +++++++++++++------ src/elements/calendar/readme.md | 17 +++++---- .../datepicker-toggle.stories.ts | 16 ++++++++ .../datepicker-toggle/datepicker-toggle.ts | 7 +++- .../datepicker/datepicker-toggle/readme.md | 9 +++-- 5 files changed, 62 insertions(+), 25 deletions(-) diff --git a/src/elements/calendar/calendar.ts b/src/elements/calendar/calendar.ts index 35d6133662..0128510c2b 100644 --- a/src/elements/calendar/calendar.ts +++ b/src/elements/calendar/calendar.ts @@ -101,6 +101,10 @@ export class SbbCalendarElement extends SbbHydrationMixin(LitElement) /** If set to true, two months are displayed */ @property({ type: Boolean }) public wide = false; + /** The initial view of calendar which should be displayed on opening. */ + @property({ attribute: 'initial-calendar-view' }) public initialCalendarView: CalendarView = + 'day'; + /** 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) { @@ -238,9 +242,7 @@ export class SbbCalendarElement extends SbbHydrationMixin(LitElement) /** Resets the active month according to the new state of the calendar. */ public resetPosition(): void { - if (this._calendarView !== 'day') { - this._resetToDayView(); - } + this._resetCalendarView(); this._activeDate = this.selected ?? this.now; this._init(); } @@ -268,6 +270,10 @@ export class SbbCalendarElement extends SbbHydrationMixin(LitElement) if (changedProperties.has('wide')) { this.resetPosition(); } + + if (changedProperties.has('initialCalendarView')) { + this._calendarView = this.initialCalendarView ?? 'day'; + } } protected override updated(changedProperties: PropertyValues): void { @@ -496,13 +502,21 @@ export class SbbCalendarElement extends SbbHydrationMixin(LitElement) /** Emits the selected date and sets it internally. */ private _selectDate(day: string): void { this._chosenMonth = undefined; - this._chosenYear = undefined; + this._setChosenYear(); if (this._selected !== day) { this._selected = day; this._dateSelected.emit(this._dateAdapter.deserialize(day)!); } } + private _setChosenYear(): void { + if (this.initialCalendarView === 'month') { + this._chosenYear = this._dateAdapter.getYear(this.selected ?? this.now); + } else { + this._chosenYear = undefined; + } + } + private _assignActiveDate(date: T): void { if (this._min && this._dateAdapter.compareDate(this._min, date) > 0) { this._activeDate = this._min; @@ -806,12 +820,12 @@ export class SbbCalendarElement extends SbbHydrationMixin(LitElement) : this._findNext(days, nextIndex, -verticalOffset); } - private _resetToDayView(): void { + private _resetCalendarView(): void { this._resetFocus = true; this._activeDate = this.selected ?? this.now; - this._chosenYear = undefined; + this._setChosenYear(); this._chosenMonth = undefined; - this._nextCalendarView = 'day'; + this._nextCalendarView = this.initialCalendarView; this._removeTable(); } @@ -1016,7 +1030,7 @@ export class SbbCalendarElement extends SbbHydrationMixin(LitElement) id="sbb-calendar__month-selection" class="sbb-calendar__controls-change-date" aria-label=${`${i18nCalendarDateSelection[this._language.current]} ${this._chosenYear}`} - @click=${() => this._resetToDayView()} + @click=${() => this._resetCalendarView()} > ${this._chosenYear} ${this._wide ? ` - ${this._chosenYear! + 1}` : nothing} @@ -1163,7 +1177,7 @@ export class SbbCalendarElement extends SbbHydrationMixin(LitElement) id="sbb-calendar__year-selection" class="sbb-calendar__controls-change-date" aria-label="${i18nCalendarDateSelection[this._language.current]} ${yearLabel}" - @click=${() => this._resetToDayView()} + @click=${() => this._resetCalendarView()} > ${yearLabel} @@ -1263,9 +1277,9 @@ export class SbbCalendarElement extends SbbHydrationMixin(LitElement) private _removeTable(): void { this.toggleAttribute('data-transition', true); - this.shadowRoot!.querySelectorAll('table').forEach((e) => - e.classList.toggle('sbb-calendar__table-hide'), - ); + this.shadowRoot + ?.querySelectorAll('table') + ?.forEach((e) => e.classList.toggle('sbb-calendar__table-hide')); } protected override render(): TemplateResult { diff --git a/src/elements/calendar/readme.md b/src/elements/calendar/readme.md index 2e59c5ba53..197f274922 100644 --- a/src/elements/calendar/readme.md +++ b/src/elements/calendar/readme.md @@ -63,14 +63,15 @@ For accessibility purposes, the component is rendered as a native table element ## Properties -| Name | Attribute | Privacy | Type | Default | Description | -| ------------ | ------------- | ------- | ------------------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------- | -| `dateFilter` | `date-filter` | public | `(date: T \| null) => boolean \| undefined` | | A function used to filter out dates. | -| `max` | `max` | public | `T \| null` | | The maximum valid date. Takes T Object, ISOString, and Unix Timestamp (number of seconds since Jan 1, 1970). | -| `min` | `min` | public | `T \| null` | | The minimum valid date. Takes T Object, ISOString, and Unix Timestamp (number of seconds since Jan 1, 1970). | -| `now` | `now` | public | `T` | `null` | A configured date which acts as the current date instead of the real current date. Recommended for testing purposes. | -| `selected` | `selected` | public | `T \| null` | | The selected date. Takes T Object, ISOString, and Unix Timestamp (number of seconds since Jan 1, 1970). | -| `wide` | `wide` | public | `boolean` | `false` | If set to true, two months are displayed | +| Name | Attribute | Privacy | Type | Default | Description | +| --------------------- | ----------------------- | ------- | ------------------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------- | +| `dateFilter` | `date-filter` | public | `(date: T \| null) => boolean \| undefined` | | A function used to filter out dates. | +| `initialCalendarView` | `initial-calendar-view` | public | `CalendarView` | `'day'` | The initial view of calendar which should be displayed on opening. | +| `max` | `max` | public | `T \| null` | | The maximum valid date. Takes T Object, ISOString, and Unix Timestamp (number of seconds since Jan 1, 1970). | +| `min` | `min` | public | `T \| null` | | The minimum valid date. Takes T Object, ISOString, and Unix Timestamp (number of seconds since Jan 1, 1970). | +| `now` | `now` | public | `T` | `null` | A configured date which acts as the current date instead of the real current date. Recommended for testing purposes. | +| `selected` | `selected` | public | `T \| null` | | The selected date. Takes T Object, ISOString, and Unix Timestamp (number of seconds since Jan 1, 1970). | +| `wide` | `wide` | public | `boolean` | `false` | If set to true, two months are displayed | ## Methods diff --git a/src/elements/datepicker/datepicker-toggle/datepicker-toggle.stories.ts b/src/elements/datepicker/datepicker-toggle/datepicker-toggle.stories.ts index 8396608b43..6b85e9af75 100644 --- a/src/elements/datepicker/datepicker-toggle/datepicker-toggle.stories.ts +++ b/src/elements/datepicker/datepicker-toggle/datepicker-toggle.stories.ts @@ -31,12 +31,21 @@ const negative: InputType = { }, }; +const initialCalendarView: InputType = { + control: { + type: 'inline-radio', + }, + options: ['day', 'month', 'year'], +}; + const defaultArgTypes: ArgTypes = { negative, + 'initial-calendar-view': initialCalendarView, }; const defaultArgs: Args = { negative: false, + 'initial-calendar-view': initialCalendarView.options![0], }; // Story interaction executed after the story renders @@ -104,6 +113,13 @@ export const InFormFieldNegative: StoryObj = { play: isChromatic() ? playStory : undefined, }; +export const InitialYearSelection: StoryObj = { + render: FormFieldTemplate, + argTypes: defaultArgTypes, + args: { ...defaultArgs, 'initial-calendar-view': initialCalendarView.options![2] }, + play: isChromatic() ? playStory : undefined, +}; + const meta: Meta = { decorators: [withActions as Decorator], parameters: { diff --git a/src/elements/datepicker/datepicker-toggle/datepicker-toggle.ts b/src/elements/datepicker/datepicker-toggle/datepicker-toggle.ts index 5ecc8d5bdd..d4199f17ce 100644 --- a/src/elements/datepicker/datepicker-toggle/datepicker-toggle.ts +++ b/src/elements/datepicker/datepicker-toggle/datepicker-toggle.ts @@ -3,7 +3,7 @@ import { html, isServer, LitElement, nothing } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { ref } from 'lit/directives/ref.js'; -import type { SbbCalendarElement } from '../../calendar.js'; +import type { CalendarView, SbbCalendarElement } from '../../calendar.js'; import { sbbInputModalityDetector } from '../../core/a11y.js'; import { SbbLanguageController } from '../../core/controllers.js'; import { hostAttributes } from '../../core/decorators.js'; @@ -33,6 +33,10 @@ export class SbbDatepickerToggleElement extends SbbNegativeMixin( /** Datepicker reference. */ @property({ attribute: 'date-picker' }) public datePicker?: string | SbbDatepickerElement; + /** The initial view of calendar which should be displayed on opening. */ + @property({ attribute: 'initial-calendar-view' }) public initialCalendarView: CalendarView = + 'day'; + @state() private _disabled = false; @state() private _min: string | number | null | undefined = null; @@ -204,6 +208,7 @@ export class SbbDatepickerToggleElement extends SbbNegativeMixin( > ${this._renderCalendar ? html` Date: Thu, 15 Aug 2024 15:35:24 +0200 Subject: [PATCH 2/8] docs: fix spelling --- src/elements/calendar/calendar.ts | 2 +- src/elements/calendar/readme.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/elements/calendar/calendar.ts b/src/elements/calendar/calendar.ts index 0128510c2b..7748157ff8 100644 --- a/src/elements/calendar/calendar.ts +++ b/src/elements/calendar/calendar.ts @@ -101,7 +101,7 @@ export class SbbCalendarElement extends SbbHydrationMixin(LitElement) /** If set to true, two months are displayed */ @property({ type: Boolean }) public wide = false; - /** The initial view of calendar which should be displayed on opening. */ + /** The initial view of the calendar which should be displayed on opening. */ @property({ attribute: 'initial-calendar-view' }) public initialCalendarView: CalendarView = 'day'; diff --git a/src/elements/calendar/readme.md b/src/elements/calendar/readme.md index 197f274922..d2157759a6 100644 --- a/src/elements/calendar/readme.md +++ b/src/elements/calendar/readme.md @@ -66,7 +66,7 @@ For accessibility purposes, the component is rendered as a native table element | Name | Attribute | Privacy | Type | Default | Description | | --------------------- | ----------------------- | ------- | ------------------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------- | | `dateFilter` | `date-filter` | public | `(date: T \| null) => boolean \| undefined` | | A function used to filter out dates. | -| `initialCalendarView` | `initial-calendar-view` | public | `CalendarView` | `'day'` | The initial view of calendar which should be displayed on opening. | +| `initialCalendarView` | `initial-calendar-view` | public | `CalendarView` | `'day'` | The initial view of the calendar which should be displayed on opening. | | `max` | `max` | public | `T \| null` | | The maximum valid date. Takes T Object, ISOString, and Unix Timestamp (number of seconds since Jan 1, 1970). | | `min` | `min` | public | `T \| null` | | The minimum valid date. Takes T Object, ISOString, and Unix Timestamp (number of seconds since Jan 1, 1970). | | `now` | `now` | public | `T` | `null` | A configured date which acts as the current date instead of the real current date. Recommended for testing purposes. | From 219532648411a8b0ef36900f854ae4fcd2ef2c58 Mon Sep 17 00:00:00 2001 From: Jeremias Peier Date: Mon, 19 Aug 2024 12:39:21 +0200 Subject: [PATCH 3/8] docs: document 0.1ms animation --- src/elements/calendar/calendar.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/elements/calendar/calendar.scss b/src/elements/calendar/calendar.scss index c016075b1e..a71b66a954 100644 --- a/src/elements/calendar/calendar.scss +++ b/src/elements/calendar/calendar.scss @@ -28,6 +28,10 @@ --sbb-calendar-cell-transition-easing-function: var(--sbb-animation-easing); --sbb-calendar-tables-gap: var(--sbb-spacing-fixed-10x); --sbb-calendar-table-animation-shift: #{sbb.px-to-rem-build(0.1)}; + + // While changing views, there would be a few frames where the height of the calendar collapses to just + // the height of the controls and then grow back to the height of the next view. + // By using 0.1ms this can be avoided. --sbb-calendar-table-animation-duration: 0.1ms; --sbb-calendar-table-column-spaces: 12; --sbb-calendar-control-view-change-height: #{sbb.px-to-rem-build(44)}; From eef7bfa4f4a2225d6c0844f5a1397b3ae219e780 Mon Sep 17 00:00:00 2001 From: Jeremias Peier Date: Mon, 19 Aug 2024 12:41:07 +0200 Subject: [PATCH 4/8] test: try adding Safari for snapshot tests --- src/elements/calendar/calendar.snapshot.spec.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/elements/calendar/calendar.snapshot.spec.ts b/src/elements/calendar/calendar.snapshot.spec.ts index 0a13c064ea..4d43539140 100644 --- a/src/elements/calendar/calendar.snapshot.spec.ts +++ b/src/elements/calendar/calendar.snapshot.spec.ts @@ -24,7 +24,6 @@ describe(`sbb-calendar`, () => { await expect(element).shadowDom.to.be.equalSnapshot(); }); - // We skip safari because it has an inconsistent behavior on ci environment - testA11yTreeSnapshot(undefined, undefined, { safari: true }); + testA11yTreeSnapshot(); }); }); From c9b121d9292f1ba8bd9fe14a07de3c7c22791f82 Mon Sep 17 00:00:00 2001 From: Jeremias Peier Date: Mon, 19 Aug 2024 16:12:51 +0200 Subject: [PATCH 5/8] fix: fix logic --- src/elements/calendar/calendar.stories.ts | 15 ++++++++++++ src/elements/calendar/calendar.ts | 30 ++++++++++++++--------- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/src/elements/calendar/calendar.stories.ts b/src/elements/calendar/calendar.stories.ts index c8bd8658b7..68a6128207 100644 --- a/src/elements/calendar/calendar.stories.ts +++ b/src/elements/calendar/calendar.stories.ts @@ -87,6 +87,13 @@ const max: InputType = { }, }; +const initialCalendarView: InputType = { + control: { + type: 'inline-radio', + }, + options: ['day', 'month', 'year'], +}; + const now: InputType = { control: { type: 'date', @@ -127,6 +134,7 @@ const defaultArgTypes: ArgTypes = { min, max, dateFilter, + 'initial-calendar-view': initialCalendarView, now, }; @@ -137,6 +145,7 @@ const defaultArgs: Args = { wide: false, selected: isChromatic() ? new Date(2023, 0, 20) : today, now: isChromatic() ? new Date(2023, 0, 12, 0, 0, 0).valueOf() : undefined, + 'initial-calendar-view': initialCalendarView.options![0], }; export const Calendar: StoryObj = { @@ -186,6 +195,12 @@ export const CalendarWideDynamicWidth: StoryObj = { args: { ...defaultArgs, wide: true }, }; +export const CalendarWithInitialYearSelection: StoryObj = { + render: Template, + argTypes: defaultArgTypes, + args: { ...defaultArgs, 'initial-calendar-view': initialCalendarView.options![2] }, +}; + const meta: Meta = { excludeStories: /.*DynamicWidth$/, decorators: [withActions as Decorator], diff --git a/src/elements/calendar/calendar.ts b/src/elements/calendar/calendar.ts index 7748157ff8..1782752596 100644 --- a/src/elements/calendar/calendar.ts +++ b/src/elements/calendar/calendar.ts @@ -243,7 +243,6 @@ export class SbbCalendarElement extends SbbHydrationMixin(LitElement) /** Resets the active month according to the new state of the calendar. */ public resetPosition(): void { this._resetCalendarView(); - this._activeDate = this.selected ?? this.now; this._init(); } @@ -272,7 +271,9 @@ export class SbbCalendarElement extends SbbHydrationMixin(LitElement) } if (changedProperties.has('initialCalendarView')) { - this._calendarView = this.initialCalendarView ?? 'day'; + this._setChosenYear(); + this._chosenMonth = undefined; + this._nextCalendarView = this._calendarView = this.initialCalendarView; } } @@ -511,7 +512,9 @@ export class SbbCalendarElement extends SbbHydrationMixin(LitElement) private _setChosenYear(): void { if (this.initialCalendarView === 'month') { - this._chosenYear = this._dateAdapter.getYear(this.selected ?? this.now); + this._chosenYear = this._dateAdapter.getYear( + this._dateAdapter.deserialize(this._selected) ?? this.selected ?? this.now, + ); } else { this._chosenYear = undefined; } @@ -820,13 +823,16 @@ export class SbbCalendarElement extends SbbHydrationMixin(LitElement) : this._findNext(days, nextIndex, -verticalOffset); } - private _resetCalendarView(): void { + private _resetCalendarView(initTransition = false): void { this._resetFocus = true; this._activeDate = this.selected ?? this.now; this._setChosenYear(); this._chosenMonth = undefined; - this._nextCalendarView = this.initialCalendarView; - this._removeTable(); + this._nextCalendarView = this._calendarView = this.initialCalendarView; + + if (initTransition) { + this._startTableTransition(); + } } /** Render the view for the day selection. */ @@ -877,7 +883,7 @@ export class SbbCalendarElement extends SbbHydrationMixin(LitElement) @click=${() => { this._resetFocus = true; this._nextCalendarView = 'year'; - this._removeTable(); + this._startTableTransition(); }} > ${monthLabel} @@ -1030,7 +1036,7 @@ export class SbbCalendarElement extends SbbHydrationMixin(LitElement) id="sbb-calendar__month-selection" class="sbb-calendar__controls-change-date" aria-label=${`${i18nCalendarDateSelection[this._language.current]} ${this._chosenYear}`} - @click=${() => this._resetCalendarView()} + @click=${() => this._resetCalendarView(true)} > ${this._chosenYear} ${this._wide ? ` - ${this._chosenYear! + 1}` : nothing} @@ -1118,7 +1124,7 @@ export class SbbCalendarElement extends SbbHydrationMixin(LitElement) this._dateAdapter.getDate(this._activeDate), ), ); - this._removeTable(); + this._startTableTransition(); } /** Render the view for the year selection. */ @@ -1177,7 +1183,7 @@ export class SbbCalendarElement extends SbbHydrationMixin(LitElement) id="sbb-calendar__year-selection" class="sbb-calendar__controls-change-date" aria-label="${i18nCalendarDateSelection[this._language.current]} ${yearLabel}" - @click=${() => this._resetCalendarView()} + @click=${() => this._resetCalendarView(true)} > ${yearLabel} @@ -1244,7 +1250,7 @@ export class SbbCalendarElement extends SbbHydrationMixin(LitElement) this._dateAdapter.getDate(this._activeDate), ), ); - this._removeTable(); + this._startTableTransition(); } private get _getView(): TemplateResult { @@ -1275,7 +1281,7 @@ export class SbbCalendarElement extends SbbHydrationMixin(LitElement) } } - private _removeTable(): void { + private _startTableTransition(): void { this.toggleAttribute('data-transition', true); this.shadowRoot ?.querySelectorAll('table') From a3c331920b148963bb78c306f117a67f31369795 Mon Sep 17 00:00:00 2001 From: Jeremias Peier Date: Mon, 19 Aug 2024 16:24:42 +0200 Subject: [PATCH 6/8] fix: fix review --- src/elements/calendar/calendar.stories.ts | 8 ++++---- src/elements/calendar/calendar.ts | 11 +++++------ src/elements/calendar/readme.md | 18 +++++++++--------- .../datepicker-toggle.stories.ts | 8 ++++---- .../datepicker-toggle/datepicker-toggle.ts | 5 ++--- .../datepicker/datepicker-toggle/readme.md | 10 +++++----- 6 files changed, 29 insertions(+), 31 deletions(-) diff --git a/src/elements/calendar/calendar.stories.ts b/src/elements/calendar/calendar.stories.ts index 68a6128207..266e70c3e7 100644 --- a/src/elements/calendar/calendar.stories.ts +++ b/src/elements/calendar/calendar.stories.ts @@ -87,7 +87,7 @@ const max: InputType = { }, }; -const initialCalendarView: InputType = { +const view: InputType = { control: { type: 'inline-radio', }, @@ -134,7 +134,7 @@ const defaultArgTypes: ArgTypes = { min, max, dateFilter, - 'initial-calendar-view': initialCalendarView, + view: view, now, }; @@ -145,7 +145,7 @@ const defaultArgs: Args = { wide: false, selected: isChromatic() ? new Date(2023, 0, 20) : today, now: isChromatic() ? new Date(2023, 0, 12, 0, 0, 0).valueOf() : undefined, - 'initial-calendar-view': initialCalendarView.options![0], + view: view.options![0], }; export const Calendar: StoryObj = { @@ -198,7 +198,7 @@ export const CalendarWideDynamicWidth: StoryObj = { export const CalendarWithInitialYearSelection: StoryObj = { render: Template, argTypes: defaultArgTypes, - args: { ...defaultArgs, 'initial-calendar-view': initialCalendarView.options![2] }, + args: { ...defaultArgs, view: view.options![2] }, }; const meta: Meta = { diff --git a/src/elements/calendar/calendar.ts b/src/elements/calendar/calendar.ts index 1782752596..9ed1423f8d 100644 --- a/src/elements/calendar/calendar.ts +++ b/src/elements/calendar/calendar.ts @@ -102,8 +102,7 @@ export class SbbCalendarElement extends SbbHydrationMixin(LitElement) @property({ type: Boolean }) public wide = false; /** The initial view of the calendar which should be displayed on opening. */ - @property({ attribute: 'initial-calendar-view' }) public initialCalendarView: CalendarView = - 'day'; + @property() public view: CalendarView = 'day'; /** The minimum valid date. Takes T Object, ISOString, and Unix Timestamp (number of seconds since Jan 1, 1970). */ @property() @@ -270,10 +269,10 @@ export class SbbCalendarElement extends SbbHydrationMixin(LitElement) this.resetPosition(); } - if (changedProperties.has('initialCalendarView')) { + if (changedProperties.has('view')) { this._setChosenYear(); this._chosenMonth = undefined; - this._nextCalendarView = this._calendarView = this.initialCalendarView; + this._nextCalendarView = this._calendarView = this.view; } } @@ -511,7 +510,7 @@ export class SbbCalendarElement extends SbbHydrationMixin(LitElement) } private _setChosenYear(): void { - if (this.initialCalendarView === 'month') { + if (this.view === 'month') { this._chosenYear = this._dateAdapter.getYear( this._dateAdapter.deserialize(this._selected) ?? this.selected ?? this.now, ); @@ -828,7 +827,7 @@ export class SbbCalendarElement extends SbbHydrationMixin(LitElement) this._activeDate = this.selected ?? this.now; this._setChosenYear(); this._chosenMonth = undefined; - this._nextCalendarView = this._calendarView = this.initialCalendarView; + this._nextCalendarView = this._calendarView = this.view; if (initTransition) { this._startTableTransition(); diff --git a/src/elements/calendar/readme.md b/src/elements/calendar/readme.md index d2157759a6..47829d412e 100644 --- a/src/elements/calendar/readme.md +++ b/src/elements/calendar/readme.md @@ -63,15 +63,15 @@ For accessibility purposes, the component is rendered as a native table element ## Properties -| Name | Attribute | Privacy | Type | Default | Description | -| --------------------- | ----------------------- | ------- | ------------------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------- | -| `dateFilter` | `date-filter` | public | `(date: T \| null) => boolean \| undefined` | | A function used to filter out dates. | -| `initialCalendarView` | `initial-calendar-view` | public | `CalendarView` | `'day'` | The initial view of the calendar which should be displayed on opening. | -| `max` | `max` | public | `T \| null` | | The maximum valid date. Takes T Object, ISOString, and Unix Timestamp (number of seconds since Jan 1, 1970). | -| `min` | `min` | public | `T \| null` | | The minimum valid date. Takes T Object, ISOString, and Unix Timestamp (number of seconds since Jan 1, 1970). | -| `now` | `now` | public | `T` | `null` | A configured date which acts as the current date instead of the real current date. Recommended for testing purposes. | -| `selected` | `selected` | public | `T \| null` | | The selected date. Takes T Object, ISOString, and Unix Timestamp (number of seconds since Jan 1, 1970). | -| `wide` | `wide` | public | `boolean` | `false` | If set to true, two months are displayed | +| Name | Attribute | Privacy | Type | Default | Description | +| ------------ | ------------- | ------- | ------------------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------- | +| `dateFilter` | `date-filter` | public | `(date: T \| null) => boolean \| undefined` | | A function used to filter out dates. | +| `max` | `max` | public | `T \| null` | | The maximum valid date. Takes T Object, ISOString, and Unix Timestamp (number of seconds since Jan 1, 1970). | +| `min` | `min` | public | `T \| null` | | The minimum valid date. Takes T Object, ISOString, and Unix Timestamp (number of seconds since Jan 1, 1970). | +| `now` | `now` | public | `T` | `null` | A configured date which acts as the current date instead of the real current date. Recommended for testing purposes. | +| `selected` | `selected` | public | `T \| null` | | The selected date. Takes T Object, ISOString, and Unix Timestamp (number of seconds since Jan 1, 1970). | +| `view` | `view` | public | `CalendarView` | `'day'` | The initial view of the calendar which should be displayed on opening. | +| `wide` | `wide` | public | `boolean` | `false` | If set to true, two months are displayed | ## Methods diff --git a/src/elements/datepicker/datepicker-toggle/datepicker-toggle.stories.ts b/src/elements/datepicker/datepicker-toggle/datepicker-toggle.stories.ts index 6b85e9af75..1aa450437e 100644 --- a/src/elements/datepicker/datepicker-toggle/datepicker-toggle.stories.ts +++ b/src/elements/datepicker/datepicker-toggle/datepicker-toggle.stories.ts @@ -31,7 +31,7 @@ const negative: InputType = { }, }; -const initialCalendarView: InputType = { +const view: InputType = { control: { type: 'inline-radio', }, @@ -40,12 +40,12 @@ const initialCalendarView: InputType = { const defaultArgTypes: ArgTypes = { negative, - 'initial-calendar-view': initialCalendarView, + view: view, }; const defaultArgs: Args = { negative: false, - 'initial-calendar-view': initialCalendarView.options![0], + view: view.options![0], }; // Story interaction executed after the story renders @@ -116,7 +116,7 @@ export const InFormFieldNegative: StoryObj = { export const InitialYearSelection: StoryObj = { render: FormFieldTemplate, argTypes: defaultArgTypes, - args: { ...defaultArgs, 'initial-calendar-view': initialCalendarView.options![2] }, + args: { ...defaultArgs, view: view.options![2] }, play: isChromatic() ? playStory : undefined, }; diff --git a/src/elements/datepicker/datepicker-toggle/datepicker-toggle.ts b/src/elements/datepicker/datepicker-toggle/datepicker-toggle.ts index d4199f17ce..aa2cb37fe5 100644 --- a/src/elements/datepicker/datepicker-toggle/datepicker-toggle.ts +++ b/src/elements/datepicker/datepicker-toggle/datepicker-toggle.ts @@ -34,8 +34,7 @@ export class SbbDatepickerToggleElement extends SbbNegativeMixin( @property({ attribute: 'date-picker' }) public datePicker?: string | SbbDatepickerElement; /** The initial view of calendar which should be displayed on opening. */ - @property({ attribute: 'initial-calendar-view' }) public initialCalendarView: CalendarView = - 'day'; + @property() public view: CalendarView = 'day'; @state() private _disabled = false; @@ -208,7 +207,7 @@ export class SbbDatepickerToggleElement extends SbbNegativeMixin( > ${this._renderCalendar ? html` Date: Mon, 19 Aug 2024 22:48:29 +0200 Subject: [PATCH 7/8] test: add tests --- src/elements/calendar/calendar.spec.ts | 40 +++++++++- .../datepicker-toggle.spec.ts | 73 +++++++++++++++++++ 2 files changed, 112 insertions(+), 1 deletion(-) diff --git a/src/elements/calendar/calendar.spec.ts b/src/elements/calendar/calendar.spec.ts index 8a18db6250..f43ffa5ed9 100644 --- a/src/elements/calendar/calendar.spec.ts +++ b/src/elements/calendar/calendar.spec.ts @@ -4,7 +4,7 @@ import { html } from 'lit/static-html.js'; import type { SbbSecondaryButtonElement } from '../button/secondary-button.js'; import { fixture } from '../core/testing/private.js'; -import { waitForCondition, waitForLitRender, EventSpy } from '../core/testing.js'; +import { EventSpy, waitForCondition, waitForLitRender } from '../core/testing.js'; import { SbbCalendarElement } from './calendar.js'; @@ -245,6 +245,44 @@ describe(`sbb-calendar`, () => { expect(firstDisabledMaxDate).to.have.attribute('aria-disabled', 'true'); }); + it('opens year view', async () => { + element.view = 'year'; + await waitForLitRender(element); + + expect(element.shadowRoot!.querySelector('.sbb-calendar__table-year-view')).not.to.be.null; + }); + + it('opens month view', async () => { + element.view = 'month'; + await waitForLitRender(element); + + expect(element.shadowRoot!.querySelector('.sbb-calendar__table-month-view')).not.to.be.null; + expect( + element.shadowRoot!.querySelector('#sbb-calendar__month-selection')!.textContent!.trim(), + ).to.be.equal('2023'); + }); + + it('opens month view with selected date', async () => { + element.selected = '2017-01-22'; + element.view = 'month'; + await waitForLitRender(element); + + expect( + element.shadowRoot!.querySelector('#sbb-calendar__month-selection')!.textContent!.trim(), + ).to.be.equal('2017'); + }); + + it('opens month view with current date', async () => { + element.selected = undefined; + element.now = '2022-08-15'; + element.view = 'month'; + await waitForLitRender(element); + + expect( + element.shadowRoot!.querySelector('#sbb-calendar__month-selection')!.textContent!.trim(), + ).to.be.equal('2022'); + }); + describe('navigation', () => { it('navigates left via keyboard', async () => { element.focus(); diff --git a/src/elements/datepicker/datepicker-toggle/datepicker-toggle.spec.ts b/src/elements/datepicker/datepicker-toggle/datepicker-toggle.spec.ts index 71c00f375c..c101cc898a 100644 --- a/src/elements/datepicker/datepicker-toggle/datepicker-toggle.spec.ts +++ b/src/elements/datepicker/datepicker-toggle/datepicker-toggle.spec.ts @@ -1,4 +1,5 @@ import { assert, expect } from '@open-wc/testing'; +import { sendKeys } from '@web/test-runner-commands'; import { html } from 'lit/static-html.js'; import type { SbbCalendarElement } from '../../calendar.js'; @@ -181,4 +182,76 @@ describe(`sbb-datepicker-toggle`, () => { expect(changeSpy.count).to.be.equal(1); expect(blurSpy.count).to.be.equal(1); }); + + it('handles view property', async () => { + const element: SbbDatepickerToggleElement = await fixture( + html` + + + + `, + ); + + const didOpenEventSpy = new EventSpy(SbbPopoverElement.events.didOpen, element); + const didCloseEventSpy = new EventSpy(SbbPopoverElement.events.didClose, element); + const datepickerToggle = + element.querySelector('sbb-datepicker-toggle')!; + + // Open calendar + datepickerToggle.open(); + await waitForCondition(() => didOpenEventSpy.events.length === 1); + + // Year view should be active + const calendar = datepickerToggle.shadowRoot!.querySelector('sbb-calendar')!; + expect(calendar.shadowRoot!.querySelector('.sbb-calendar__table-year-view')!).not.to.be.null; + + // Select year + calendar.shadowRoot!.querySelectorAll('button')[5].click(); + await waitForLitRender(element); + await waitForCondition(() => !calendar.hasAttribute('data-transition')); + + // Select month + calendar.shadowRoot!.querySelectorAll('button')[5].click(); + await waitForLitRender(element); + await waitForCondition(() => !calendar.hasAttribute('data-transition')); + + // Select day + calendar.shadowRoot!.querySelectorAll('button')[5].click(); + await waitForLitRender(element); + await waitForCondition(() => !calendar.hasAttribute('data-transition')); + + // Expect selected date and closed calendar + expect(calendar.selected!.toISOString()).to.be.equal('2020-05-04T22:00:00.000Z'); + await waitForCondition(() => didCloseEventSpy.events.length === 1); + + // Open again + datepickerToggle.open(); + await waitForCondition(() => didOpenEventSpy.events.length === 2); + + // Should open with year view again + expect(calendar.shadowRoot!.querySelector('.sbb-calendar__table-year-view')!).not.to.be.null; + expect( + calendar.shadowRoot!.querySelector('.sbb-calendar__selected')!.textContent!.trim(), + ).to.be.equal('2020'); + + // Close again + await sendKeys({ press: 'Escape' }); + await waitForCondition(() => didCloseEventSpy.events.length === 2); + + // Changing to month view + datepickerToggle.view = 'month'; + await waitForLitRender(element); + + // Open again + datepickerToggle.open(); + await waitForCondition(() => didOpenEventSpy.events.length === 3); + + // Month view should be active and correct year preselected + expect(calendar.shadowRoot!.querySelector('.sbb-calendar__table-month-view')!).not.to.be.null; + expect( + calendar + .shadowRoot!.querySelector('.sbb-calendar__controls-change-date')! + .textContent!.trim(), + ).to.be.equal('2020'); + }); }); From 21d7aa5839cfee5d5d9fd55c02964b0514f5de6f Mon Sep 17 00:00:00 2001 From: Jeremias Peier Date: Tue, 20 Aug 2024 07:40:48 +0200 Subject: [PATCH 8/8] test: fix date assertion --- .../datepicker/datepicker-toggle/datepicker-toggle.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/elements/datepicker/datepicker-toggle/datepicker-toggle.spec.ts b/src/elements/datepicker/datepicker-toggle/datepicker-toggle.spec.ts index c101cc898a..36c1b26310 100644 --- a/src/elements/datepicker/datepicker-toggle/datepicker-toggle.spec.ts +++ b/src/elements/datepicker/datepicker-toggle/datepicker-toggle.spec.ts @@ -3,6 +3,7 @@ import { sendKeys } from '@web/test-runner-commands'; import { html } from 'lit/static-html.js'; import type { SbbCalendarElement } from '../../calendar.js'; +import { defaultDateAdapter } from '../../core/datetime/native-date-adapter.js'; import { fixture } from '../../core/testing/private.js'; import { EventSpy, waitForCondition, waitForLitRender } from '../../core/testing.js'; import type { SbbFormFieldElement } from '../../form-field.js'; @@ -221,7 +222,7 @@ describe(`sbb-datepicker-toggle`, () => { await waitForCondition(() => !calendar.hasAttribute('data-transition')); // Expect selected date and closed calendar - expect(calendar.selected!.toISOString()).to.be.equal('2020-05-04T22:00:00.000Z'); + expect(defaultDateAdapter.toIso8601(calendar.selected!)).to.be.equal('2020-05-05'); await waitForCondition(() => didCloseEventSpy.events.length === 1); // Open again