diff --git a/.circleci/config.yml b/.circleci/config.yml index bb45cbc278d..947e73add36 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -10,7 +10,7 @@ executors: parameters: current_golden_images_hash: type: string - default: 07a5cba6726f993d5ddc38a9c30e9c9e3e94ebc7 + default: ee8f9f1cc2fbe29917c792fd40bdb6fce2f622bd wireit_cache_name: type: string default: wireit diff --git a/packages/calendar/package.json b/packages/calendar/package.json index 14614ba5a82..843908042fb 100644 --- a/packages/calendar/package.json +++ b/packages/calendar/package.json @@ -69,8 +69,7 @@ "@spectrum-web-components/reactive-controllers": "^0.41.2" }, "devDependencies": { - "@spectrum-css/calendar": "^4.2.4", - "@spectrum-web-components/story-decorator": "^0.41.2" + "@spectrum-css/calendar": "^4.2.4" }, "types": "./src/index.d.ts", "customElements": "custom-elements.json", diff --git a/packages/calendar/src/Calendar.ts b/packages/calendar/src/Calendar.ts index cc48871e492..815b7ca80ff 100644 --- a/packages/calendar/src/Calendar.ts +++ b/packages/calendar/src/Calendar.ts @@ -15,6 +15,7 @@ import { getLocalTimeZone, getWeeksInMonth, isSameDay, + parseDate, startOfMonth, startOfWeek, today, @@ -36,9 +37,12 @@ import { classMap, ifDefined, } from '@spectrum-web-components/base/src/directives.js'; -import { LanguageResolutionController } from '@spectrum-web-components/reactive-controllers/src/LanguageResolution.js'; +import { + LanguageResolutionController, + languageResolverUpdatedSymbol, +} from '@spectrum-web-components/reactive-controllers/src/LanguageResolution.js'; -import { CalendarWeekday, daysInWeek } from './types.js'; +import { CalendarWeekday, DateCellProperties } from './types.js'; import styles from './calendar.css.js'; @@ -46,6 +50,7 @@ import '@spectrum-web-components/action-button/sp-action-button.js'; import '@spectrum-web-components/icons-workflow/icons/sp-icon-chevron-left.js'; import '@spectrum-web-components/icons-workflow/icons/sp-icon-chevron-right.js'; +export const DAYS_PER_WEEK = 7; /** * @element sp-calendar * @@ -60,43 +65,72 @@ export class Calendar extends SpectrumElement { } /** - * Date used to display the calendar. If no date is given, the current month will be used + * The selected date in the calendar. If defined, this also indicates where the calendar opens. + * If not, the calendar opens at the current month. */ @property({ attribute: false }) - selectedDate?: Date; + public set selectedDate(date: Date) { + if (!this.isValidDate(date)) return; + + this._selectedDate = this.toCalendarDate(date); + this.currentDate = this._selectedDate; + + this.requestUpdate('selectedDate', this._selectedDate); + } + + public get selectedDate(): Date { + if (!this._selectedDate) return new Date('Invalid Date'); + return this._selectedDate?.toDate(this.timeZone); + } + private _selectedDate?: CalendarDate; + + /** + * The date that indicates the current position in the calendar. + */ + @state() + private currentDate: CalendarDate = this.today; /** * The minimum allowed date a user can select */ @property({ attribute: false }) min?: Date; + private minDate?: CalendarDate; /** * The maximum allowed date a user can select */ @property({ attribute: false }) max?: Date; + private maxDate?: CalendarDate; /** * Indicates when the calendar should be disabled entirely */ @property({ type: Boolean, reflect: true }) - disabled = false; + public disabled = false; /** * Adds a padding around the calendar */ @property({ type: Boolean, reflect: true }) - padded = false; + public padded = false; - @state() - private currentDate!: CalendarDate; + private languageResolver = new LanguageResolutionController(this); - @state() - private minDate!: CalendarDate; + /** + * The locale used to format the dates and weekdays. + * The default value is the language of the document or the user's browser. + */ + private get locale(): string { + return this.languageResolver.language; + } - @state() - private maxDate!: CalendarDate; + // TODO: Implement a cache mechanism to store the value of `today` + private timeZone: string = getLocalTimeZone(); + public get today(): CalendarDate { + return today(this.timeZone); + } @state() private weeksInCurrentMonth: number[] = []; @@ -104,40 +138,60 @@ export class Calendar extends SpectrumElement { @state() private weekdays: CalendarWeekday[] = []; - private languageResolver = new LanguageResolutionController(this); - private timeZone: string = getLocalTimeZone(); + @state() + protected set isDateFocusIntent(value: boolean) { + if (this._isDateFocusIntent === value) return; - private get locale(): string { - return this.languageResolver.language; + this._isDateFocusIntent = value; + this.requestUpdate('isDateFocusIntent', !value); } - // TODO: Implement a cache mechanism to store the value of `today` and use this value to initialise `currentDate` - public get today(): CalendarDate { - return today(this.timeZone); + protected get isDateFocusIntent(): boolean { + return this._isDateFocusIntent; } + private _isDateFocusIntent: boolean = false; - constructor() { - super(); - this.setInitialCalendarDate(); + private setDateFocusIntent(): void { + this.isDateFocusIntent = true; } - protected override willUpdate(changedProperties: PropertyValues): void { - if (changedProperties.has('selectedDate')) { - this.setCurrentCalendarDate(); - } + private resetDateFocusIntent(): void { + this.isDateFocusIntent = false; + } - if (changedProperties.has('min')) { - this.setMinCalendarDate(); - } + override connectedCallback(): void { + super.connectedCallback(); + document.addEventListener('mousedown', this.resetDateFocusIntent); + } - if (changedProperties.has('max')) { - this.setMaxCalendarDate(); - } + override disconnectedCallback(): void { + super.disconnectedCallback(); + document.removeEventListener('mousedown', this.resetDateFocusIntent); + } - this.setWeeksInCurrentMonth(); + override willUpdate(changedProperties: PropertyValues): void { + if (changedProperties.has('min')) this.setMinCalendarDate(); + + if (changedProperties.has('max')) this.setMaxCalendarDate(); - // TODO: Include a condition to run the `setWeekdays()` method only when really needed - this.setWeekdays(); + if (changedProperties.has(languageResolverUpdatedSymbol)) { + this.setNumberFormatter(); + this.setWeekdays(); + this.setWeeksInCurrentMonth(); + } + } + + override updated(changedProperties: PropertyValues): void { + /** + * Keeps the focus on the correct day when navigating through the calendar. + * Particularly useful when the month changes and the focus is lost. + */ + if (changedProperties.has('currentDate') && this.isDateFocusIntent) { + const elementToFocus = this.shadowRoot?.querySelector( + 'td[tabindex="0"]' + ) as HTMLElement; + elementToFocus.focus(); + } } protected override render(): TemplateResult { @@ -146,20 +200,20 @@ export class Calendar extends SpectrumElement { `; } - public renderCalendarHeader(): TemplateResult { + protected renderCalendarHeader(): TemplateResult { const monthAndYear = this.formatDate(this.currentDate, { month: 'long', year: 'numeric', }); return html` -