From 1cb8fff6cc4a373f62a750bb2f6f0b2ed92b3e96 Mon Sep 17 00:00:00 2001 From: Georgi Damyanov Date: Mon, 2 Feb 2026 10:50:12 +0200 Subject: [PATCH 01/32] feat: implement second calendar --- packages/main/src/Calendar.ts | 73 ++++++++ packages/main/src/CalendarTemplate.tsx | 161 +++++++++++++++--- packages/main/src/DateRangePickerTemplate.tsx | 1 + packages/main/src/themes/Calendar.css | 54 ++++++ .../main/src/themes/DatePickerPopover.css | 10 +- packages/main/test/pages/DateRangePicker.html | 8 +- 6 files changed, 277 insertions(+), 30 deletions(-) diff --git a/packages/main/src/Calendar.ts b/packages/main/src/Calendar.ts index 553c65c5af6b..c06baddd3226 100644 --- a/packages/main/src/Calendar.ts +++ b/packages/main/src/Calendar.ts @@ -280,6 +280,16 @@ class Calendar extends CalendarPart { @property({ type: Boolean }) hideWeekNumbers = false; + /** + * Defines the number of months to display side by side in day picker view. + * Only applicable when selection mode is "Range". + * @default 1 + * @public + * @since 2.0.0 + */ + @property({ type: Number }) + monthsToShow = 1; + /** * Which picker is currently visible to the user: day/month/year/yearRange * @private @@ -366,6 +376,69 @@ class Calendar extends CalendarPart { this._valueIsProcessed = false; } + /** + * Returns the timestamp for a specific month index when displaying multiple months + * @private + */ + _getMonthTimestamp(monthIndex: number): number { + if (monthIndex === 0) { + return this._timestamp; + } + + const calendarDate = CalendarDateComponent.fromTimestamp(this._timestamp * 1000, this._primaryCalendarType); + + // Set day to 1 to avoid day-of-month overflow issues + // (e.g., Jan 31 + 1 month would overflow to March if Feb doesn't have 31 days) + calendarDate.setDate(1); + + // Add months one by one to handle month boundaries correctly + for (let i = 0; i < monthIndex; i++) { + const currentMonth = calendarDate.getMonth(); + const currentYear = calendarDate.getYear(); + + if (currentMonth === 11) { + // December -> January of next year + calendarDate.setYear(currentYear + 1); + calendarDate.setMonth(0); + } else { + // Just increment the month + calendarDate.setMonth(currentMonth + 1); + } + } + + return calendarDate.valueOf() / 1000; + } + + /** + * Generates header button text (month and year) for a specific month timestamp + * @private + */ + _getHeaderTextForMonth(monthTimestamp: number): { monthText: string, yearText: string, secondMonthText?: string, secondYearText?: string } { + const calendarDate = CalendarDateComponent.fromTimestamp(monthTimestamp * 1000, this._primaryCalendarType); + const localeData = getCachedLocaleDataInstance(getLocale()); + const yearFormat = DateFormat.getDateInstance({ format: "y", calendarType: this.primaryCalendarType }); + + const monthText = localeData.getMonthsStandAlone("wide", this.primaryCalendarType)[calendarDate.getMonth()]; + const localDate = calendarDate.toLocalJSDate(); + const yearText = String(yearFormat.format(localDate, true)); + + const result: { monthText: string, yearText: string, secondMonthText?: string, secondYearText?: string } = { + monthText, + yearText, + }; + + if (this.hasSecondaryCalendarType) { + const secondaryDate = transformDateToSecondaryType(this.primaryCalendarType, this._secondaryCalendarType, monthTimestamp, true); + const secondaryCalendarDate = secondaryDate.firstDate || secondaryDate.lastDate; + const secondaryLocaleData = getCachedLocaleDataInstance(getLocale()); + result.secondMonthText = secondaryLocaleData.getMonthsStandAlone("wide", this._secondaryCalendarType)[secondaryCalendarDate.getMonth()]; + const secondaryYearFormat = DateFormat.getDateInstance({ format: "y", calendarType: this._secondaryCalendarType }); + result.secondYearText = String(secondaryYearFormat.format(secondaryCalendarDate.toLocalJSDate(), true)); + } + + return result; + } + /** * @private */ diff --git a/packages/main/src/CalendarTemplate.tsx b/packages/main/src/CalendarTemplate.tsx index 9c7b798181fa..7390ae806a25 100644 --- a/packages/main/src/CalendarTemplate.tsx +++ b/packages/main/src/CalendarTemplate.tsx @@ -5,38 +5,151 @@ import YearPicker from "./YearPicker.js"; import YearRangePicker from "./YearRangePicker.js"; import CalendarHeaderTemplate from "./CalendarHeaderTemplate.js"; import CalendarSelectionMode from "./types/CalendarSelectionMode.js"; +import Icon from "./Icon.js"; +import slimArrowLeft from "@ui5/webcomponents-icons/dist/slim-arrow-left.js"; +import slimArrowRight from "@ui5/webcomponents-icons/dist/slim-arrow-right.js"; export default function CalendarTemplate(this: Calendar) { + const showMultipleMonths = this.monthsToShow > 1 && !this._isDayPickerHidden; + return ( <>
-
- { CalendarHeaderTemplate.call(this) } -
-
-