import { context, type WithRefOpts } from '../../provider'; import { createAttrs } from '$uix/morfo'; import { boolToEmptyStrOrUndef } from '$adom'; import { readableActive, state, type ActiveProps, type StateProps } from '$libs/reactive'; import type { SomaKeyboardEvent, SomaMouseEvent, OnChangeFn } from '../../types'; import { KEYS, getDirectionalKeys } from '../../keyboard'; import { Soma } from '../../core/soma.svelte'; import { CALENDAR_LANGS } from './langs'; import { type DateValue, type DateMatcher, type Month, type WeekStartsOn, type DateOrder, isSameDay, startOfMonth, endOfMonth, getLocalTimeZone, CalendarDate, today, resolveDateOrder } from '$libs/days'; import { applyMultipleSelection, applySingleSelection, computeDateFlags, createMonths, getIsoWeekNumber, getSimpleWeekNumber, getWeekdays, isOutsideMonth, isToday, isWeekend, shiftDate, validateCalendarBounds } from './helpers'; import type { CalendarCellSnippetProps, CalendarDaySnippetProps, CalendarMonthSelectSnippetProps, CalendarView, CalendarYearSelectSnippetProps } from './types'; // ── Contract ───────────────────────────────────────────────────────────────── import { calendarMorfo } from '../../../morfo/components/calendar'; import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte'; const attrs = createAttrs(calendarMorfo); // ── Root ──────────────────────────────────────────────────────────────────── export interface CalendarOpts extends WithRefOpts, StateProps<{ placeholder: DateValue; value: DateValue | DateValue[] | undefined; }>, ActiveProps<{ type: 'single' | 'multiple'; preventDeselect: boolean; minValue: DateValue | undefined; maxValue: DateValue | undefined; minViewValue: DateValue | undefined; maxViewValue: DateValue | undefined; disabled: boolean; pagedNavigation: boolean; weekStartsOn: WeekStartsOn | undefined; weekdayFormat: Intl.DateTimeFormatOptions['weekday']; isDateDisabled: DateMatcher | undefined; isDateUnavailable: DateMatcher | undefined; isDateHoliday: DateMatcher | undefined; fixedWeeks: boolean; numberOfMonths: number; calendarLabel: string | undefined; readonly: boolean; disableDaysOutsideMonth: boolean; maxDays: number | undefined; monthFormat: Intl.DateTimeFormatOptions['month'] | ((month: number) => string); yearFormat: Intl.DateTimeFormatOptions['year'] | ((year: number) => string); showWeekNumbers: boolean; weekNumberFormat: 'iso' | 'simple'; announceNavigation: boolean; initialFocus: boolean; dir: 'ltr' | 'rtl'; locale: string; }> { onValueChange?: OnChangeFn; } /** * Resolve the initial placeholder when the consumer has not supplied one. * Prefers the current selection, falls back to today. Exposed so the wrapper * can stay thin instead of mutating props. */ export function resolveInitialCalendarPlaceholder( placeholder: DateValue | undefined, value: DateValue | DateValue[] | undefined, type: 'single' | 'multiple' ): DateValue { if (placeholder) return placeholder; if (type === 'single' && value && !Array.isArray(value)) return value; if (type === 'multiple' && Array.isArray(value) && value.length > 0) { return value[value.length - 1]; } const t = today(getLocalTimeZone()); return new CalendarDate(t.year, t.month, t.day); } function sameDateArray(a: DateValue[], b: DateValue[]): boolean { if (a.length !== b.length) return false; return a.every((date, index) => isSameDay(date, b[index])); } export class CalendarProvider { readonly opts: CalendarOpts; readonly runtimePart: SomaRuntimePart; readonly soma: Soma; readonly runtime: SomaRuntime; static readonly ctx = context('Calendar'); static get(): CalendarProvider | undefined { return this.ctx.getOr(undefined) as CalendarProvider | undefined; } static require(): CalendarProvider { return this.ctx.get(); } static create(opts: CalendarOpts) { return new CalendarProvider(opts); } /** Child ids exposed so the Grid can wire aria-labelledby. */ headingId = state(''); /** Live-region text for announcements. */ announcement = $state(''); /** * Navigation view (Ark/Mantine/MUI pattern): the day grid switches in-place * to a month grid then a year grid for fast navigation — no native `