Skip to content

Commit

Permalink
fix(sbb-datepicker): handle hydration correctly (#2721)
Browse files Browse the repository at this point in the history
Closes #2691
  • Loading branch information
kyubisation committed Jun 6, 2024
1 parent 55b3446 commit 058489a
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 29 deletions.
7 changes: 1 addition & 6 deletions src/elements/calendar/calendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,12 +227,6 @@ export class SbbCalendarElement<T = Date> extends LitElement {
super();
this._createMonthRows();
this._setWeekdays();

// Workaround to execute initialization immediately after hydration
// If no hydration is needed, will be executed before the first rendering
this.addController({
hostConnected: () => this.resetPosition(),
});
}

private get _dateFilter(): (date: T) => boolean {
Expand All @@ -250,6 +244,7 @@ export class SbbCalendarElement<T = Date> extends LitElement {

public override connectedCallback(): void {
super.connectedCallback();
this.resetPosition();
this.focus = () => {
this._resetFocus = true;
this._focusCell();
Expand Down
45 changes: 28 additions & 17 deletions src/elements/datepicker/datepicker-toggle/datepicker-toggle.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { CSSResultGroup, PropertyValues, TemplateResult } from 'lit';
import { html, LitElement } from 'lit';
import { html, isServer, LitElement, nothing } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import { ref } from 'lit/directives/ref.js';

Expand All @@ -8,7 +8,7 @@ import { sbbInputModalityDetector } from '../../core/a11y.js';
import { SbbLanguageController } from '../../core/controllers.js';
import { hostAttributes } from '../../core/decorators.js';
import { i18nShowCalendar } from '../../core/i18n.js';
import { SbbNegativeMixin } from '../../core/mixins.js';
import { SbbHydrationMixin, SbbNegativeMixin } from '../../core/mixins.js';
import type { SbbPopoverElement, SbbPopoverTriggerElement } from '../../popover.js';
import type { SbbDatepickerElement, SbbInputUpdateEvent } from '../datepicker.js';
import { datepickerControlRegisteredEventFactory, getDatePicker } from '../datepicker.js';
Expand All @@ -25,7 +25,7 @@ import '../../popover.js';
@hostAttributes({
slot: 'prefix',
})
export class SbbDatepickerToggleElement extends SbbNegativeMixin(LitElement) {
export class SbbDatepickerToggleElement extends SbbNegativeMixin(SbbHydrationMixin(LitElement)) {
public static override styles: CSSResultGroup = style;

/** Datepicker reference. */
Expand All @@ -37,6 +37,8 @@ export class SbbDatepickerToggleElement extends SbbNegativeMixin(LitElement) {

@state() private _max: string | number | null | undefined = null;

@state() private _renderCalendar = false;

private _datePickerElement: SbbDatepickerElement | null | undefined;

private _calendarElement!: SbbCalendarElement;
Expand All @@ -49,6 +51,13 @@ export class SbbDatepickerToggleElement extends SbbNegativeMixin(LitElement) {

private _language = new SbbLanguageController(this);

public constructor() {
super();
if (!isServer) {
this.hydrationComplete.then(() => (this._renderCalendar = true));
}
}

/**
* Opens the calendar.
*/
Expand Down Expand Up @@ -171,7 +180,7 @@ export class SbbDatepickerToggleElement extends SbbNegativeMixin(LitElement) {
<sbb-popover-trigger
icon-name="calendar-small"
aria-label=${i18nShowCalendar[this._language.current]}
?disabled=${!this._datePickerElement || this._disabled}
?disabled=${!isServer && (!this._datePickerElement || this._disabled)}
?negative=${this.negative}
data-icon-small
${ref((el?: Element) => (this._triggerElement = el as SbbPopoverTriggerElement))}
Expand All @@ -186,19 +195,21 @@ export class SbbDatepickerToggleElement extends SbbNegativeMixin(LitElement) {
hide-close-button
${ref((el?: Element) => (this._popoverElement = el as SbbPopoverElement))}
>
<sbb-calendar
.min=${this._min}
.max=${this._max}
.now=${this._nowOrUndefined()}
?wide=${this._datePickerElement?.wide}
.dateFilter=${this._datePickerElement?.dateFilter}
@dateSelected=${(d: CustomEvent<Date>) => {
const newDate = new Date(d.detail);
this._calendarElement.selected = newDate;
this._datePickerElement?.setValueAsDate(newDate);
}}
${ref((calendar?: Element) => this._assignCalendar(calendar as SbbCalendarElement))}
></sbb-calendar>
${this._renderCalendar
? html`<sbb-calendar
.min=${this._min}
.max=${this._max}
.now=${this._nowOrUndefined()}
?wide=${this._datePickerElement?.wide}
.dateFilter=${this._datePickerElement?.dateFilter}
@dateSelected=${(d: CustomEvent<Date>) => {
const newDate = new Date(d.detail);
this._calendarElement.selected = newDate;
this._datePickerElement?.setValueAsDate(newDate);
}}
${ref((calendar?: Element) => this._assignCalendar(calendar as SbbCalendarElement))}
></sbb-calendar>`
: nothing}
</sbb-popover>
`;
}
Expand Down
45 changes: 39 additions & 6 deletions src/elements/datepicker/datepicker/datepicker.ssr.spec.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,53 @@
import { assert } from '@open-wc/testing';
import { assert, expect } from '@open-wc/testing';
import { html } from 'lit';

import { defaultDateAdapter } from '../../core/datetime.js';
import { fixture } from '../../core/testing/private.js';

import { SbbDatepickerElement } from './datepicker.js';

import '../../form-field.js';
import '../datepicker-next-day.js';
import '../datepicker-previous-day.js';
import '../datepicker-toggle.js';

describe(`sbb-datepicker ${fixture.name}`, () => {
let root: SbbDatepickerElement;
const asIso8601 = (date: Date): string => defaultDateAdapter.toIso8601(date);

beforeEach(async () => {
root = await fixture(html`<sbb-datepicker></sbb-datepicker>`, {
it('renders', async () => {
const root = await fixture(html`<sbb-datepicker></sbb-datepicker>`, {
modules: ['./datepicker.js'],
});
assert.instanceOf(root, SbbDatepickerElement);
});

it('renders', () => {
assert.instanceOf(root, SbbDatepickerElement);
it('should render full datepicker component set', async () => {
const root = await fixture(
html`
<sbb-form-field>
<sbb-datepicker-previous-day></sbb-datepicker-previous-day>
<sbb-datepicker-toggle></sbb-datepicker-toggle>
<input value="01.01.2023" />
<sbb-datepicker></sbb-datepicker>
<sbb-datepicker-next-day></sbb-datepicker-next-day>
</sbb-form-field>
`,
{
modules: [
'./datepicker.js',
'../../form-field.js',
'../datepicker-next-day.js',
'../datepicker-previous-day.js',
'../datepicker-toggle.js',
],
},
);
const datepicker = root.querySelector('sbb-datepicker')!;
expect(asIso8601(datepicker.getValueAsDate()!)).to.equal(asIso8601(new Date(2023, 0, 1)));

const datepickerToggle = root.querySelector('sbb-datepicker-toggle')!;
await datepickerToggle.hydrationComplete;
await datepickerToggle.updateComplete;
expect(datepickerToggle.shadowRoot?.querySelector('sbb-calendar')).to.not.be.null;
});
});

0 comments on commit 058489a

Please sign in to comment.