/** * Headless controller for date-grid views (month / week / range / day). * * Owns the *invisible mechanics* shared by Calendar and Planner: grid geometry, * navigation, roving focus, hover and selection. It holds no event/item concept * and renders nothing — view components read it through `dateGridContext` and * `DateGridScaffold`. * * `referenceDate`, `view`, `selection` and the bounds are **controlled inputs** * supplied as reactive getters (role model: `utils/overlay-stack.svelte.ts` — * a `$state` class fed by getter options). Navigation and selection never mutate * those inputs directly; they call `onNavigate` / `onSelect` so the wrapper owns * the source of truth (`bind:value`). Only `focusedDate`, `hoveredDate` and * `navDirection` are controller-owned `$state`. */ import type { DateGridSelection, DateGridSelectionMode, DateGridView, DateRange, DayCellInfo, NavDirection } from './date-grid.types.js'; /** * Reactive inputs for a {@link DateGridController}. Value fields are getters so * the controller tracks them through `$derived`; callbacks are plain optional * methods invoked at action time. */ export interface DateGridOptions { /** The date the grid is anchored on (controlled). */ get referenceDate(): Date; /** Active view mode. */ get view(): DateGridView; /** Day the week starts on (0=Sun … 6=Sat). */ get weekStartsOn(): number; /** * Always emit 6 week rows in `view="month"`, so the grid keeps its height. * A plain optional member rather than a getter — TS has no optional-getter * syntax, and a wrapper may still satisfy it with one (Calendar does). */ readonly fixedWeeks?: boolean; /** BCP 47 locale tag for titles and weekday names. */ get locale(): string; /** Selection cardinality. */ get selectionMode(): DateGridSelectionMode; /** Current selection value (controlled), shaped by `selectionMode`. */ get selection(): DateGridSelection | undefined; /** Start of the explicit range for `view="range"`. */ get rangeStart(): Date | undefined; /** End of the explicit range for `view="range"`. */ get rangeEnd(): Date | undefined; /** Earliest navigable/selectable date. */ get minDate(): Date | undefined; /** Latest navigable/selectable date. */ get maxDate(): Date | undefined; /** Whether the whole grid is disabled. */ get disabled(): boolean; /** Extra per-date disable predicate (on top of min/max). */ isDateDisabled?: (date: Date) => boolean; /** Called when navigation wants a new reference date; the wrapper materialises it. */ onNavigate?: (date: Date, range: DateRange) => void; /** Called with the next selection value and the date that triggered it; the * wrapper materialises the selection. The trigger date lets consumers run * per-click side effects (Calendar's `onDateClick` / spill-day navigation, * Planner's `onDateSelect`) that the computed selection alone can't express. */ onSelect?: (selection: DateGridSelection, date: Date) => void; } export declare class DateGridController { #private; /** The roving keyboard focus target (local midnight). Controller-owned. */ focusedDate: Date; /** The hovered date, for range-selection preview. Controller-owned. */ hoveredDate: Date | null; /** Direction of the last navigation, for enter/exit transitions. */ navDirection: NavDirection; /** Today (local midnight). Refreshed via {@link refreshToday}. */ today: Date; constructor(opts: DateGridOptions); get view(): DateGridView; get referenceDate(): Date; get weekStartsOn(): number; get locale(): string; get disabled(): boolean; get selectionMode(): DateGridSelectionMode; /** Cell rows for the current view. The single source of truth for geometry: * month → 4–6 week rows; week/day → one row; range → chunked week rows. */ get cells(): Date[][]; /** The 7 dates of the week containing `referenceDate`. */ get weekDates(): Date[]; /** Localized short weekday names, ordered from `weekStartsOn`. */ get weekdayNames(): string[]; /** Localized narrow weekday names, ordered from `weekStartsOn`. */ get weekdayNamesNarrow(): string[]; /** First visible date (top-left cell) — the visible-range start. */ get rangeStart(): Date; /** Last visible date (bottom-right cell) — the visible-range end. */ get rangeEnd(): Date; /** Localized header title for the current view. */ get title(): string; get canGoBack(): boolean; get canGoForward(): boolean; /** Whether today lies within `[minDate, maxDate]` and can be navigated to. Drives * the header Today button's disabled state (mirrors the arrow buttons gating on * canGoBack/canGoForward). Day-granular: the button jumps to today's *day*, so a * maxDate earlier this month still disables it once today passes the bound. Reads * the reactive `today`, so it re-evaluates after the midnight refresh. */ get canGoToToday(): boolean; weekNumberFor(date: Date): number; isToday(date: Date): boolean; isWeekend(date: Date): boolean; /** Outside the focused month (month view) or the requested range (range view). */ isOutside(date: Date): boolean; isDisabled(date: Date): boolean; isFocused(date: Date): boolean; /** Assemble the shared per-day context for a cell. */ dayCellInfo(date: Date): DayCellInfo; isSelected(date: Date): boolean; isRangeStart(date: Date): boolean; isRangeEnd(date: Date): boolean; isInSelectedRange(date: Date): boolean; isInPreviewRange(date: Date): boolean; /** Step the view by `delta` units (months / weeks / days; range slides its * window by its own span in days). Every path clamps to [minDate, maxDate]; * the range window clamps span-preserving. Emits the next reference date and * range (visible cell range, or the shifted window for `range`). */ navigate(delta: number): void; /** Jump to today and focus it. Clamped to `[minDate, maxDate]`: the header Today * button gates itself on {@link canGoToToday} (a clamped "today" is not today, so * it disables rather than mislead), but a programmatic call still must not seat the * reference on an all-disabled month/week/day past the boundary. Uses `today` — the * controller's source of truth for "today" — so it lands where `isToday` marks. */ goToToday(): void; /** Jump the reference date to a specific date, clamped to `[minDate, maxDate]`. */ goTo(date: Date): void; /** Set the roving focus, navigating the view if the date left the visible window. * The target is clamped to `[minDate, maxDate]` — arrow/Home/End/Page keys are not * gated by canGoBack/canGoForward, so an unclamped focus could cross the boundary * and drag the view onto an all-disabled window (the same escape the stepping paths * close). Only the hard outer bounds clamp; custom `isDateDisabled` holes *inside* * the range stay focusable, per the ARIA grid pattern. */ setFocusedDate(date: Date): void; /** Move the roving focus by `deltaDays` (keyboard arrows / page keys). */ moveFocus(deltaDays: number): void; setHoveredDate(date: Date | null): void; /** Recompute `today` (call from a midnight timer in the wrapper). */ refreshToday(): void; /** Compute and emit the next selection value for `date` per `selectionMode`. */ selectDate(date: Date): void; }