Skip to content

Commit

Permalink
feat: add now mixin
Browse files Browse the repository at this point in the history
  • Loading branch information
DavideMininni-Fincons committed May 23, 2024
1 parent 439f5c9 commit 0eaf476
Show file tree
Hide file tree
Showing 19 changed files with 103 additions and 104 deletions.
28 changes: 13 additions & 15 deletions src/components/calendar/calendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
i18nYearMonthSelection,
} from '../core/i18n.js';
import type { SbbDateLike } from '../core/interfaces.js';
import { SbbNowMixin } from '../core/mixins.js';

import style from './calendar.scss?lit&inline';

Expand Down Expand Up @@ -90,7 +91,7 @@ export type CalendarView = 'day' | 'month' | 'year';
* @event {CustomEvent<T>} dateSelected - Event emitted on date selection.
*/
@customElement('sbb-calendar')
export class SbbCalendarElement<T = Date> extends LitElement {
export class SbbCalendarElement<T = Date> extends SbbNowMixin(LitElement) {
public static override styles: CSSResultGroup = style;
public static readonly events = {
dateSelected: 'dateSelected',
Expand Down Expand Up @@ -141,9 +142,6 @@ export class SbbCalendarElement<T = Date> extends LitElement {
/** A function used to filter out dates. */
@property({ attribute: 'date-filter' }) public dateFilter?: (date: T | null) => boolean;

/** A specific date for the current datetime (timestamp in milliseconds). */
@property({ attribute: 'now' }) public dataNow?: number;

private _dateAdapter: DateAdapter<T> = defaultDateAdapter as unknown as DateAdapter<T>;

/** Event emitted on date selection. */
Expand All @@ -153,7 +151,7 @@ export class SbbCalendarElement<T = Date> extends LitElement {
);

/** The currently active date. */
@state() private _activeDate: T = this._now();
@state() private _activeDate: T = this._getNow();

/** The selected date as ISOString. */
@state() private _selected?: string;
Expand Down Expand Up @@ -237,7 +235,7 @@ export class SbbCalendarElement<T = Date> extends LitElement {
if (this._calendarView !== 'day') {
this._resetToDayView();
}
this._activeDate = this.selected ?? this._now();
this._activeDate = this.selected ?? this._getNow();
this._init();
}

Expand Down Expand Up @@ -609,7 +607,7 @@ export class SbbCalendarElement<T = Date> extends LitElement {
}

private _getFirstFocusable(): HTMLButtonElement {
const active = this._selected ? this._dateAdapter.deserialize(this._selected)! : this._now();
const active = this._selected ? this._dateAdapter.deserialize(this._selected)! : this._getNow();
let firstFocusable =
this.shadowRoot!.querySelector('.sbb-calendar__selected') ??
this.shadowRoot!.querySelector(
Expand Down Expand Up @@ -798,9 +796,9 @@ export class SbbCalendarElement<T = Date> extends LitElement {
: this._findNext(days, nextIndex, -verticalOffset);
}

private _now(): T {
if (this.dataNow) {
const today = new Date(+this.dataNow);
private _getNow(): T {
if (this.now) {
const today = new Date(+this.now);
if (defaultDateAdapter.isValid(today)) {
return this._dateAdapter.createDate(
today.getFullYear(),
Expand All @@ -814,7 +812,7 @@ export class SbbCalendarElement<T = Date> extends LitElement {

private _resetToDayView(): void {
this._resetFocus = true;
this._activeDate = this.selected ?? this._now();
this._activeDate = this.selected ?? this._getNow();

Check warning on line 815 in src/components/calendar/calendar.ts

View check run for this annotation

Codecov / codecov/patch

src/components/calendar/calendar.ts#L815

Added line #L815 was not covered by tests
this._chosenYear = undefined;
this._chosenMonth = undefined;
this._nextCalendarView = 'day';
Expand Down Expand Up @@ -926,7 +924,7 @@ export class SbbCalendarElement<T = Date> extends LitElement {

/** Creates the table body with the day cells. For the first row, it also considers the possible day's offset. */
private _createDayTableBody(weeks: Day[][]): TemplateResult[] {
const today: string = this._dateAdapter.toIso8601(this._now());
const today: string = this._dateAdapter.toIso8601(this._getNow());
return weeks.map((week: Day[], rowIndex: number) => {
const firstRowOffset: number = DAYS_PER_ROW - week.length;
if (rowIndex === 0 && firstRowOffset) {
Expand Down Expand Up @@ -1061,8 +1059,8 @@ export class SbbCalendarElement<T = Date> extends LitElement {
!!this._selected && year === selectedYear && month.monthValue === selectedMonth;
const isCurrentMonth =
year === this._dateAdapter.getYear(this._now()) &&
this._dateAdapter.getMonth(this._now()) === month.monthValue;
year === this._dateAdapter.getYear(this._getNow()) &&
this._dateAdapter.getMonth(this._getNow()) === month.monthValue;
return html` <td
class=${classMap({
Expand Down Expand Up @@ -1180,7 +1178,7 @@ export class SbbCalendarElement<T = Date> extends LitElement {

/** Creates the table for the year selection view. */
private _createYearTable(years: number[][], shiftRight = false): TemplateResult {
const now = this._now();
const now = this._getNow();
return html` <table
class="sbb-calendar__table"
@animationend=${(e: AnimationEvent) => this._tableAnimationEnd(e)}
Expand Down
4 changes: 2 additions & 2 deletions src/components/calendar/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ It's recommended to set the time to 00:00:00.
<sbb-calendar min="1600000000" max="1700000000" selected="1650000000"></sbb-calendar>
```

To specify a specific date for the current datetime, you can use the `now` property (timestamp in milliseconds).
To simulate the current datetime, you can use the `now` property (timestamp in milliseconds).
This is helpful if you need a specific state of the component.

## Style
Expand Down Expand Up @@ -64,10 +64,10 @@ For accessibility purposes, the component is rendered as a native table element

| Name | Attribute | Privacy | Type | Default | Description |
| ------------ | ------------- | ------- | ------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------ |
| `dataNow` | `now` | public | `number \| undefined` | | A specific date for the current datetime (timestamp in milliseconds). |
| `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 | `number \| undefined` | | A specific date for the current datetime (timestamp in milliseconds). |
| `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 |

Expand Down
6 changes: 3 additions & 3 deletions src/components/clock/clock.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import readme from './readme.md?raw';

import './clock.js';

const dataNow: InputType = {
const now: InputType = {
control: {
type: 'date',
},
Expand All @@ -21,13 +21,13 @@ const Template = (args: Args): TemplateResult => html`<sbb-clock ${sbbSpread(arg

export const Default: StoryObj = {
render: Template,
argTypes: { now: dataNow },
argTypes: { ...now },
args: { now: undefined },
};

export const Paused: StoryObj = {
render: Template,
argTypes: { now: dataNow },
argTypes: { ...now },
args: { now: new Date('2023-01-24T10:10:30+01:00').valueOf() },
};

Expand Down
24 changes: 8 additions & 16 deletions src/components/clock/clock.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import type { CSSResultGroup, PropertyValues, TemplateResult } from 'lit';
import { html, LitElement } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { customElement } from 'lit/decorators.js';
import { ref } from 'lit/directives/ref.js';

import { SbbNowMixin } from '../core/mixins.js';

import clockFaceSVG from './assets/sbb_clock_face.svg?raw';
import clockHandleHoursSVG from './assets/sbb_clock_hours.svg?raw';
import clockHandleMinutesSVG from './assets/sbb_clock_minutes.svg?raw';
Expand Down Expand Up @@ -48,12 +50,9 @@ const ADD_EVENT_LISTENER_OPTIONS: AddEventListenerOptions = {
* It displays an analog clock with the classic SBB face.
*/
@customElement('sbb-clock')
export class SbbClockElement extends LitElement {
export class SbbClockElement extends SbbNowMixin(LitElement) {
public static override styles: CSSResultGroup = style;

/** A specific date for the current datetime (timestamp in milliseconds). */
@property({ attribute: 'now' }) public dataNow?: number;

/** Reference to the hour hand. */
private _clockHandHours!: HTMLElement;

Expand Down Expand Up @@ -84,7 +83,7 @@ export class SbbClockElement extends LitElement {
private async _handlePageVisibilityChange(): Promise<void> {
if (document.visibilityState === 'hidden') {
this._stopClock();
} else if (!this.dataNow) {
} else if (!this.now) {

Check warning on line 86 in src/components/clock/clock.ts

View check run for this annotation

Codecov / codecov/patch

src/components/clock/clock.ts#L86

Added line #L86 was not covered by tests
await this._startClock();
}
}
Expand Down Expand Up @@ -119,7 +118,7 @@ export class SbbClockElement extends LitElement {

/** Given the current date, calculates the hh/mm/ss values and the hh/mm/ss left to the next midnight. */
private _assignCurrentTime(): void {
const date = this._now();
const date = new Date(this.dateNow);
this._hours = date.getHours() % 12;
this._minutes = date.getMinutes();
this._seconds = date.getSeconds();
Expand Down Expand Up @@ -219,7 +218,7 @@ export class SbbClockElement extends LitElement {
private _stopClock(): void {
clearInterval(this._handMovement);

if (this.dataNow) {
if (this.now) {
this._setHandsStartingPosition();
this._clockHandSeconds?.classList.add('sbb-clock__hand-seconds--initial-minute');
this._clockHandHours?.classList.add('sbb-clock__hand-hours--initial-hour');
Expand Down Expand Up @@ -254,19 +253,12 @@ export class SbbClockElement extends LitElement {
);
}

private _now(): Date {
if (this.dataNow) {
return new Date(+this.dataNow);
}
return new Date();
}

protected override async firstUpdated(changedProperties: PropertyValues<this>): Promise<void> {
super.firstUpdated(changedProperties);

this._addEventListeners();

if (this.dataNow) {
if (this.now) {
this._stopClock();
} else {
await this._startClock();
Expand Down
8 changes: 4 additions & 4 deletions src/components/clock/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ then it briefly pauses at the clock top before starting a new rotation.
<sbb-clock></sbb-clock>
```

To specify a specific date for the current datetime, you can use the `now` property (timestamp in milliseconds).
To simulate the current datetime, you can use the `now` property (timestamp in milliseconds).
This is helpful if you need a specific state of the component.

```html
Expand All @@ -18,6 +18,6 @@ This is helpful if you need a specific state of the component.

## Properties

| Name | Attribute | Privacy | Type | Default | Description |
| --------- | --------- | ------- | --------------------- | ------- | --------------------------------------------------------------------- |
| `dataNow` | `now` | public | `number \| undefined` | | A specific date for the current datetime (timestamp in milliseconds). |
| Name | Attribute | Privacy | Type | Default | Description |
| ----- | --------- | ------- | --------------------- | ------- | --------------------------------------------------------------------- |
| `now` | `now` | public | `number \| undefined` | | A specific date for the current datetime (timestamp in milliseconds). |
1 change: 1 addition & 0 deletions src/components/core/mixins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ export * from './mixins/form-associated-mixin.js';
export * from './mixins/hydration-mixin.js';
export * from './mixins/named-slot-list-mixin.js';
export * from './mixins/negative-mixin.js';
export * from './mixins/now-mixin.js';
export * from './mixins/required-mixin.js';
export * from './mixins/update-scheduler-mixin.js';
37 changes: 37 additions & 0 deletions src/components/core/mixins/now-mixin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type { LitElement } from 'lit';
import { property } from 'lit/decorators.js';

import type { AbstractConstructor } from './constructor.js';

export declare class SbbNowMixinType {
public set now(value: number | string);
public get now(): number;
protected get dateNow(): number;
}

/**
* Enhance your component with a `now` property.
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
export const SbbNowMixin = <T extends AbstractConstructor<LitElement>>(
superClass: T,
): AbstractConstructor<SbbNowMixinType> & T => {
abstract class SbbNowElement extends superClass implements Partial<SbbNowMixinType> {
/** A specific date for the current datetime (timestamp in milliseconds). */
@property({ type: Number })
public get now(): number | undefined {
return this._now;
}
public set now(value: number | string) {
this._now = +value;
}
private _now?: number;

/** Returns the `_now` value if available, otherwise the current datetime (as timestamp in millisecond). */
protected get dateNow(): number {
return this._now ?? Date.now();
}
}

return SbbNowElement as unknown as AbstractConstructor<SbbNowMixinType> & T;
};
4 changes: 2 additions & 2 deletions src/components/datepicker/common/datepicker-button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export abstract class SbbDatepickerButton extends SbbNegativeMixin(SbbButtonBase
return;
}
const startingDate: Date =
this.datePickerElement.getValueAsDate() ?? this.datePickerElement.now();
this.datePickerElement.getValueAsDate() ?? this.datePickerElement.getNow();
const date: Date = this.findAvailableDate(
startingDate,
this.datePickerElement.dateFilter,
Expand Down Expand Up @@ -175,7 +175,7 @@ export abstract class SbbDatepickerButton extends SbbNegativeMixin(SbbButtonBase
}

const currentDateString =
this.datePickerElement?.now().toDateString() === currentDate.toDateString()
this.datePickerElement?.getNow().toDateString() === currentDate.toDateString()
? i18nToday[this._language.current].toLowerCase()
: this._dateAdapter.getAccessibilityFormatDate(currentDate);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,8 @@ export class SbbDatepickerToggleElement extends SbbNegativeMixin(LitElement) {
}

private _now(): Date | undefined {
if (this._datePickerElement?.dataNow) {
const today = new Date(+this._datePickerElement?.dataNow);
if (this._datePickerElement?.now) {
const today = new Date(+this._datePickerElement?.now);

Check warning on line 160 in src/components/datepicker/datepicker-toggle/datepicker-toggle.ts

View check run for this annotation

Codecov / codecov/patch

src/components/datepicker/datepicker-toggle/datepicker-toggle.ts#L160

Added line #L160 was not covered by tests
today.setHours(0, 0, 0, 0);
return today;
}
Expand Down
19 changes: 7 additions & 12 deletions src/components/datepicker/datepicker/datepicker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { findInput, findReferencedElement } from '../../core/dom.js';
import { EventEmitter } from '../../core/eventing.js';
import { i18nDateChangedTo, i18nDatePickerPlaceholder } from '../../core/i18n.js';
import type { SbbDateLike, SbbValidationChangeEvent } from '../../core/interfaces.js';
import { SbbNowMixin } from '../../core/mixins.js';
import { AgnosticMutationObserver } from '../../core/observers.js';
import type { SbbDatepickerButton } from '../common.js';
import type { SbbDatepickerToggleElement } from '../datepicker-toggle.js';
Expand Down Expand Up @@ -165,7 +166,7 @@ export const datepickerControlRegisteredEventFactory = (): CustomEvent =>
* @event {CustomEvent<SbbValidationChangeEvent>} validationChange - Emits whenever the internal validation state changes.
*/
@customElement('sbb-datepicker')
export class SbbDatepickerElement extends LitElement {
export class SbbDatepickerElement extends SbbNowMixin(LitElement) {
public static override styles: CSSResultGroup = style;
public static readonly events = {
didChange: 'didChange',
Expand All @@ -191,9 +192,6 @@ export class SbbDatepickerElement extends LitElement {
/** Reference of the native input connected to the datepicker. */
@property() public input?: string | HTMLElement;

/** A specific date for the current datetime (timestamp in milliseconds). */
@property({ attribute: 'now' }) public dataNow?: number;

/**
* @deprecated only used for React. Will probably be removed once React 19 is available.
*/
Expand Down Expand Up @@ -462,17 +460,14 @@ export class SbbDatepickerElement extends LitElement {
* @internal
* Returns current date or configured date.
*/
public now(): Date {
if (this.dataNow) {
const today = new Date(+this.dataNow);
today.setHours(0, 0, 0, 0);
return today;
}
return this._dateAdapter.today();
public getNow(): Date {
const today = new Date(this.dateNow);
today.setHours(0, 0, 0, 0);
return today;
}

private _parse(value: string): Date | undefined {
return this.dateParser ? this.dateParser(value) : this._dateAdapter.parse(value, this.now());
return this.dateParser ? this.dateParser(value) : this._dateAdapter.parse(value, this.getNow());
}

private _format(date: Date): string {
Expand Down
4 changes: 2 additions & 2 deletions src/components/datepicker/datepicker/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ a `blur` event is fired on the input to ensure compatibility with any framework

## Custom date formats

To specify a specific date for the current datetime, you can use the `now` property (timestamp in milliseconds).
To simulate the current datetime, you can use the `now` property (timestamp in milliseconds).
This is helpful if you need a specific state of the component.

Using a combination of the `dateParser` and `format` properties, it's possible to configure the datepicker
Expand Down Expand Up @@ -104,11 +104,11 @@ Whenever the validation state changes (e.g., a valid value becomes invalid or vi

| Name | Attribute | Privacy | Type | Default | Description |
| ------------ | ------------- | ------- | --------------------------------------------------- | ------- | --------------------------------------------------------------------- |
| `dataNow` | `now` | public | `number \| undefined` | | A specific date for the current datetime (timestamp in milliseconds). |
| `dateFilter` | `date-filter` | public | `(date: Date \| null) => boolean` | | A function used to filter out dates. |
| `dateParser` | `date-parser` | public | `(value: string) => Date \| undefined \| undefined` | | A function used to parse string value into dates. |
| `format` | `format` | public | `(date: Date) => string \| undefined` | | A function used to format dates into the preferred string format. |
| `input` | `input` | public | `string \| HTMLElement \| undefined` | | Reference of the native input connected to the datepicker. |
| `now` | `now` | public | `number \| undefined` | | A specific date for the current datetime (timestamp in milliseconds). |
| `wide` | `wide` | public | `boolean` | `false` | If set to true, two months are displayed. |

## Methods
Expand Down
Loading

0 comments on commit 0eaf476

Please sign in to comment.