From fc097c0619435d0918c40a3879ca8876a6b6f157 Mon Sep 17 00:00:00 2001 From: Seifeddine Largat Date: Thu, 25 Jun 2026 20:40:37 +0200 Subject: [PATCH 1/2] feat(calendar): fixedWeeks option for stable 6-row month grid Pads short months up to a fixed 6 week-rows (the max any month can occupy) so the month-view grid height stays constant. Only affects month mode; extends (never truncates) so multi-month spans are untouched. Consumed by rzrvi-mono apps/app /calendar2 (useCalendar fixedWeeks: true). --- packages/time/src/calendar/date-core.ts | 26 ++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/packages/time/src/calendar/date-core.ts b/packages/time/src/calendar/date-core.ts index a4dc8092..5db37672 100644 --- a/packages/time/src/calendar/date-core.ts +++ b/packages/time/src/calendar/date-core.ts @@ -60,6 +60,14 @@ export interface DateCoreOptions { calendar?: Temporal.CalendarLike /** Optional range of dates to be used. */ range?: DateRange + /** + * When `true`, the month view always spans a fixed 6 week-rows (the maximum + * any month can occupy) instead of the natural 4/5/6, padding with leading + * days of the following month. Keeps the grid height stable across months. + * Only affects the `month` view mode; ignored for week/day/workWeek. + * @default false + */ + fixedWeeks?: boolean /** Optional date formatter. */ dateFormatter?: Intl.DateTimeFormat /** Optional time formatter. */ @@ -70,9 +78,10 @@ export interface DateCoreOptions { export interface ParsedDateCoreOptions extends Omit< Required, - 'range' | 'dateFormatter' | 'timeFormatter' | 'dateTimeFormatter' + 'range' | 'fixedWeeks' | 'dateFormatter' | 'timeFormatter' | 'dateTimeFormatter' > { range: ParsedDateRange + fixedWeeks?: boolean } export abstract class DateCore { @@ -194,6 +203,21 @@ export abstract class DateCore { 7) % 7 end = lastDayOfMonth.add({ days: 6 - lastDayOfMonthWeekDay }) + // Pad short months up to a fixed 6 week-rows so the grid never shifts. + // Only extends (never truncates), so multi-month spans (>6 weeks) are + // left untouched. + if (this.options.fixedWeeks) { + const FIXED_WEEKS = 6 + // start and end are both week-boundary aligned, so the span is a whole + // number of weeks; round defensively so a fractional value can never + // reach Temporal's integer-only `add`. + const spanWeeks = Math.round( + (start.until(end, { largestUnit: 'day' }).days + 1) / 7, + ) + if (spanWeeks < FIXED_WEEKS) { + end = end.add({ weeks: FIXED_WEEKS - spanWeeks }) + } + } break } case 'week': { From 5878ada5ce148720998db137b5b0a418024c523f Mon Sep 17 00:00:00 2001 From: Seifeddine Largat Date: Thu, 25 Jun 2026 20:40:37 +0200 Subject: [PATCH 2/2] feat(calendar): weekStartsOn option to override locale week start --- packages/time/src/calendar/date-core.ts | 10 +++++++++- packages/time/src/utils/weekUtils.ts | 9 ++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/packages/time/src/calendar/date-core.ts b/packages/time/src/calendar/date-core.ts index 5db37672..2bf416f8 100644 --- a/packages/time/src/calendar/date-core.ts +++ b/packages/time/src/calendar/date-core.ts @@ -54,6 +54,11 @@ export interface DateCoreOptions { viewMode: ViewMode /** Optional locale for date formatting. Uses a BCP 47 language tag. */ locale?: Intl.UnicodeBCP47LocaleIdentifier + /** + * Optional first day of the week (ISO: 1=Mon … 7=Sun). Overrides the + * locale-derived first day across all views when provided. + */ + weekStartsOn?: number /** Optional time zone specification. */ timeZone?: Temporal.TimeZoneLike /** Optional calendar system to be used. */ @@ -78,10 +83,12 @@ export interface DateCoreOptions { export interface ParsedDateCoreOptions extends Omit< Required, - 'range' | 'fixedWeeks' | 'dateFormatter' | 'timeFormatter' | 'dateTimeFormatter' + 'range' | 'fixedWeeks' | 'weekStartsOn' | 'dateFormatter' | 'timeFormatter' | 'dateTimeFormatter' > { range: ParsedDateRange fixedWeeks?: boolean + /** ISO 1=Mon … 7=Sun, or undefined to derive from the locale. */ + weekStartsOn?: number } export abstract class DateCore { @@ -162,6 +169,7 @@ export abstract class DateCore { return getFirstDayOfWeek( this.store.state.currentPeriod.toString(), this.options.locale, + this.options.weekStartsOn, ) } diff --git a/packages/time/src/utils/weekUtils.ts b/packages/time/src/utils/weekUtils.ts index 7bd05cc5..f8cef44d 100644 --- a/packages/time/src/utils/weekUtils.ts +++ b/packages/time/src/utils/weekUtils.ts @@ -13,15 +13,18 @@ export function getFirstDayOfMonth(yearMonth: string): Temporal.PlainDate { } /** - * Get the first day of the week for a given date string and locale + * Get the first day of the week for a given date string and locale. + * + * `weekStartsOn` (ISO: 1=Mon … 7=Sun) overrides the locale-derived first day + * when provided; otherwise the locale's own convention is used. */ export function getFirstDayOfWeek( dateString: string, locale: string, + weekStartsOn?: number, ): Temporal.PlainDate { const date = Temporal.PlainDate.from(dateString) - const weekInfo = getWeekInfo(locale) - const firstDayOfWeek = weekInfo.firstDay + const firstDayOfWeek = weekStartsOn ?? getWeekInfo(locale).firstDay const dayOfWeek = date.dayOfWeek const daysToSubtract = (dayOfWeek - firstDayOfWeek + 7) % 7