import { Locale } from "date-fns"; import { Dispatch, SetStateAction } from "react"; //#region src/types.d.ts /** The view the calendar renders: a day-column grid (`day`, `3days`, `week`, `custom`), the `month` grid, or the `schedule` list. */ type CalendarMode = "day" | "3days" | "week" | "custom" | "month" | "schedule" | "year"; /** The time-grid modes (day-column views, excluding month and schedule). */ type TimeGridMode = Exclude; /** * The minimal shape every calendar event must have. Layout (positioning, * overlap resolution, paging) only ever reads `start`/`end`; `title` is used by * the built-in default renderer. Anything else lives in your own type and is * threaded through untouched via the `T` generic. */ interface ICalendarEvent { /** When the event begins. */ start: Date; /** When the event ends. */ end: Date; /** Display label, shown by the built-in default renderer. */ title?: string; /** * Force this event into the all-day lane (above the time grid) instead of the * timed columns. When omitted, an event is treated as all-day only if it spans * whole days (both `start` and `end` land on midnight). */ allDay?: boolean; /** Ignore taps/long-presses on this event (the built-in renderer also dims it). */ disabled?: boolean; /** * Whether this event can be dragged to move or resize. Defaults to `true`, so * enabling drag with `onDragEvent` makes every event movable. Set `false` to * lock a specific event (a confirmed booking, someone else's event): it keeps * its normal look and still responds to taps, it just can't be picked up. Use * this for events that can *never* move; to reject a move only for certain * targets (an overlap, an out-of-bounds slot), return `false` from `onDragEvent` * instead so the event drags and snaps back. */ draggable?: boolean; /** * Whether this event can be moved (dragged to a new time/day). Defaults to the * grid's `eventStartEditable` (itself `true`). Set `false` to allow only resize. * A `draggable: false` event ignores this (it can neither move nor resize). */ startEditable?: boolean; /** * Whether this event can be resized (its start or end edge dragged). Defaults to * the grid's `eventDurationEditable` (itself `true`). Set `false` to allow only * move. A `draggable: false` event ignores this (it can neither move nor resize). */ durationEditable?: boolean; /** * How the event renders. `"auto"` (default) is a normal event box/chip; * `"background"` paints the event's time range as a non-interactive shaded * band behind the grid instead (blocked time, holidays). Background events * are excluded from chips, the agenda, and year-view dots. */ display?: "auto" | "background"; /** * Repeat rule. Pass the event to `expandRecurringEvents(events, start, end)` to * materialise its occurrences within a range; the calendar itself doesn't * expand recurrences. */ recurrence?: RecurrenceRule; } /** How often a recurring event repeats. */ type RecurrenceFrequency = "daily" | "weekly" | "monthly" | "yearly"; /** A simple, RRULE-inspired repeat rule expanded by `expandRecurringEvents`. */ interface RecurrenceRule { /** How often the event repeats. */ freq: RecurrenceFrequency; /** Repeat every N periods. Default 1. */ interval?: number; /** Stop after this many occurrences (including the first). */ count?: number; /** Stop on/after this date (inclusive). */ until?: Date; /** * For `weekly`: the weekdays to repeat on (0 = Sunday … 6 = Saturday), keeping * the event's time of day. Omit to repeat on the start date's own weekday. */ weekdays?: WeekStartsOn[]; /** * For `monthly`/`yearly`: repeat on the Nth weekday of the period instead of the * start date's day-of-month — e.g. `{ week: 3, weekday: 1 }` is the 3rd Monday. * `week` is 1–5, or -1 for the last such weekday. Maps to an ordinal iCal `BYDAY` * (e.g. `3MO`, `-1FR`). */ nthWeekday?: { week: number; weekday: WeekStartsOn; }; /** * For `monthly`: the day(s) of the month to repeat on — 1–31, or negative to * count from the month's end (-1 is the last day, -2 the second-to-last). Days * that don't exist in a given month (e.g. the 31st in February) are skipped. * Takes precedence over the start date's own day-of-month. Maps to iCal * `BYMONTHDAY`. */ monthDays?: number[]; /** * For `yearly`: the month(s) to repeat in (1 = January … 12 = December), keeping * the start date's day-of-month. Years where a listed month lacks that day are * skipped. Maps to iCal `BYMONTH`. */ months?: number[]; /** * Dates to skip (exceptions), dropped by `expandRecurringEvents`. A date at * local midnight drops every occurrence on that calendar day (iCal * `EXDATE;VALUE=DATE`); a date with a time drops only the occurrence starting * at that exact instant. Corollary: an occurrence that itself starts at * midnight can only be excluded day-wide. Maps to iCal `EXDATE`. */ exdates?: Date[]; /** * Extra one-off start dates added to the set, even ones the rule wouldn't * produce. Merged in chronological order and de-duplicated against rule * occurrences; `exdates` still apply. Maps to iCal `RDATE`. */ rdates?: Date[]; } /** * An event carrying arbitrary extra fields `T` alongside the required shape. * `ICalendarEvent` is authoritative: keys it reserves (`start`/`end`/`title`) * cannot be re-typed by `T`. */ type CalendarEvent = ICalendarEvent & Omit; /** Build a stable key for an event. Defaults to start-time + index. */ type EventKeyExtractor = (event: CalendarEvent, index: number) => string; /** Sunday = 0 … Saturday = 6, matching `Date.prototype.getDay()`. */ type WeekStartsOn = 0 | 1 | 2 | 3 | 4 | 5 | 6; /** * A day's open hours for `businessHours` shading, as hours (fractions allowed, * e.g. `9.5`): a single window `{ start, end }`, or an array of windows to leave * a closed gap between them (a lunch break: `[{ start: 9, end: 12 }, { start: 13, * end: 17 }]`). Everything outside the window(s) is shaded closed. */ type BusinessHoursValue = { start: number; end: number; } | { start: number; end: number; }[] | null; /** * The `businessHours` callback: return the open hours for `date` (see * {@link BusinessHoursValue}), `null` when the day is fully closed (all shaded), * or `undefined` for no business-hours shading. Shared by both renderers; pair * with `closedHourBands` to get the spans to shade. */ type BusinessHours = (date: Date) => BusinessHoursValue; /** * One closed-hours band handed to a `renderBusinessHours` override: the day it * belongs to and its span as fractional hours, already clamped to the visible * window. `ResourceTimeline` extends it with the lane's resource. */ interface BusinessHoursBand { date: Date; start: number; end: number; } //#endregion //#region src/tokens.d.ts /** The shared colour palette both renderers derive their themes from. */ interface CalendarColors { /** Opaque surface behind floating chrome (e.g. the date-picker field and popover). */ surface: string; /** Hour lines, day separators and month-cell borders. */ gridLine: string; /** Background tint behind weekend columns/cells. */ weekendBackground: string; /** Background tint over hours outside business hours (time grid). */ outsideHoursBackground: string; /** Today badge fill. */ todayBackground: string; /** Today badge text. */ todayText: string; /** Selected day / range-endpoint badge fill. */ selectedBackground: string; /** Selected day / range-endpoint badge text. */ selectedText: string; /** Band behind a selected range. */ rangeBackground: string; /** Hover highlight behind a day (DOM, mouse only). */ hoverBackground: string; /** Current-time indicator line (time grid). */ nowIndicator: string; /** Primary text (day numbers, weekday labels). */ text: string; /** Muted text (hour labels, "+N more"). */ textMuted: string; /** Dimmed text for disabled / adjacent-month days. */ textDisabled: string; /** Default event chip fill. */ eventBackground: string; /** Default event chip text. */ eventText: string; /** Shaded band behind a `display: "background"` event. */ backgroundEvent: string; } /** The default light-theme colour palette. */ declare const lightColors: CalendarColors; /** The default dark-theme colour palette. */ declare const darkColors: CalendarColors; //#endregion //#region src/presentation.d.ts /** Which range-band shape a day shows. */ type RangeBandKind = "none" | "fill" | "pill-start" | "pill-mid" | "pill-end"; /** The band shape for a day, given the fill-cell option. */ declare function rangeBandKind(day: { isInRange: boolean; isRangeStart: boolean; isRangeEnd: boolean; }, fillCell: boolean): RangeBandKind; /** Whether a band shape rounds its leading / trailing edge (pill ends). */ declare function bandRounding(kind: RangeBandKind): { start: boolean; end: boolean; }; /** Which filled badge a day shows (today wins over a selection). */ type DayBadgeKind = "none" | "today" | "selected"; /** * The filled-badge kind for a day. `isSelected` is true for both range endpoints * and discrete selected days; today always wins when it coincides. */ declare function dayBadgeKind(day: { isSelected: boolean; }, isToday: boolean): DayBadgeKind; //#endregion //#region src/utils/dateRange.d.ts /** A selected span. `end` is `null` while only the first endpoint has been picked. */ interface DateRange { /** The first endpoint. */ start: Date; /** The second endpoint, or `null` while only the first has been picked. */ end: Date | null; } /** Limits applied before a date can be selected. */ interface DateSelectionConstraints { /** Earliest selectable day (inclusive). */ minDate?: Date; /** Latest selectable day (inclusive). */ maxDate?: Date; /** Return true to forbid selecting a specific day. */ isDateDisabled?: (date: Date) => boolean; } /** Whether `date` passes the min/max/disabled constraints (compared by calendar day). */ declare function isDateSelectable(date: Date, constraints?: DateSelectionConstraints): boolean; /** * The range after pressing `pressed`, mirroring the familiar date-picker model: * - no range yet, or a complete range exists → start fresh (`{ start: pressed, end: null }`), * so a third press resets the selection. * - an open range (a start but no end) → close it, auto-swapping when the press * precedes the start so `start <= end` always holds. * * Returns `current` unchanged when `pressed` isn't selectable. */ declare function nextDateRange(current: DateRange | null, pressed: Date, constraints?: DateSelectionConstraints): DateRange | null; /** True when `date` is one of the range's two endpoints. */ declare function isRangeEndpoint(date: Date, range: DateRange | null): boolean; /** True when `date` falls within a complete range (endpoints included). */ declare function isWithinDateRange(date: Date, range: DateRange | null): boolean; /** The selection/disabled flags for one day. */ interface DaySelectionState { /** Fails the min/max/disabled constraints. */ isDisabled: boolean; /** A `selectedDates` day or a range endpoint (and not disabled). */ isSelected: boolean; /** Inside a complete range, endpoints included (and not disabled). */ isInRange: boolean; /** The range's start endpoint (and not disabled). */ isRangeStart: boolean; /** The range's end endpoint (and not disabled). */ isRangeEnd: boolean; } /** * The canonical per-day selection state, shared by `MonthView` (rendering) and * `buildMonthGrid` (the headless grid) so the built-in views and a custom * calendar can never disagree on what a day's state is. */ declare function daySelectionState(date: Date, selection: { selectedDates?: Date[]; selectedRange?: DateRange | null; }, constraints?: DateSelectionConstraints): DaySelectionState; /** * Per-day state shared with the month grid via context: the current selection * plus the selectability constraints. Threaded through context (not props) so * cached/virtualized day cells still repaint when any of it changes. */ interface CalendarSelection extends DateSelectionConstraints { /** Selected discrete days (single or multiple). */ selectedDates?: Date[]; /** Selected span. */ selectedRange?: DateRange; } /** * Provides the active selection to the month grid. Day cells read it via * {@link useCalendarSelection} so they repaint on selection changes even when * the virtualized list has cached (and so won't re-render) their page. */ declare const CalendarSelectionProvider: import("react").Provider; /** Reads the active selection provided by {@link CalendarSelectionProvider}. */ declare const useCalendarSelection: () => CalendarSelection; /** Options for {@link useDateRange}. */ interface UseDateRangeOptions extends DateSelectionConstraints { /** Pre-select a range on mount. */ initialRange?: DateRange | null; } /** The state and handlers returned by {@link useDateRange}. */ interface UseDateRangeResult { /** The current selection; `null` until the first endpoint is picked. */ range: DateRange | null; /** Wire to `onPressDay`: advances the range (start, then end, then restarts). */ onPressDate: (date: Date) => void; /** Set both endpoints at once (ordered); ignored if either isn't selectable. */ selectRange: (a: Date, b: Date) => void; /** Clear the selection. */ reset: () => void; /** The raw state setter, for full control. */ setRange: Dispatch>; } /** * Controlled-ish range selection state for the month view. Returns the current * `range` plus an `onPressDate` handler to wire to `Calendar`'s `onPressDay`, a * `reset`, and the raw `setRange` for full control. * * ```tsx * const { range, onPressDate } = useDateRange({ minDate: new Date() }); * * ``` */ declare function useDateRange(options?: UseDateRangeOptions): UseDateRangeResult; //#endregion //#region src/utils/dates.d.ts /** The seven dates of the week containing `date`, starting on `weekStartsOn`. */ declare const getWeekDays: (date: Date, weekStartsOn: WeekStartsOn) => Date[]; /** How many day columns a time-grid mode shows. `custom` uses `numberOfDays`. */ declare const viewDayCount: (mode: CalendarMode, numberOfDays?: number) => number; /** * Days in the inclusive span from `weekStartsOn` to `weekEndsOn` (1–7), * wrapping when the end precedes the start (e.g. Sat→Wed). Mirrors * react-native-big-calendar's `weekDaysCount`. */ declare const weekDaysCount: (weekStartsOn: WeekStartsOn, weekEndsOn: WeekStartsOn) => number; /** * The day columns to render for a time-grid page. `week` spans the calendar week * (honouring `weekStartsOn`). `custom` with a `weekEndsOn` spans the partial week * from `weekStartsOn` to `weekEndsOn` (anchored to `date`'s week, paging by week); * otherwise every mode shows `viewDayCount` consecutive days starting at `date`. */ declare const getViewDays: (mode: CalendarMode, date: Date, weekStartsOn: WeekStartsOn, numberOfDays?: number, isRTL?: boolean, weekEndsOn?: WeekStartsOn, hiddenDays?: number[]) => Date[]; /** * The calendar weeks covering `month`, padded to whole weeks starting on * `weekStartsOn`. `showSixWeeks` always returns six rows (42 days) for a * fixed-height grid; `isRTL` reverses each week's day order. */ declare const buildMonthWeeks: (month: Date, weekStartsOn: WeekStartsOn, { showSixWeeks, isRTL, hiddenDays }?: { showSixWeeks?: boolean; isRTL?: boolean; hiddenDays?: number[]; }) => Date[][]; /** * Drop the days whose weekday (0=Sunday…6=Saturday) is listed in `hiddenDays`. * Hiding all seven weekdays is treated as hiding nothing (matching * `getViewDays`), so a misconfiguration degrades to the full grid, not a blank. */ declare const filterHiddenDays: (days: Date[], hiddenDays?: number[]) => Date[]; /** True when `date` is a Saturday or Sunday. */ declare const isWeekend: (date: Date) => boolean; /** True when `date` falls on the current calendar day. */ declare const getIsToday: (date: Date) => boolean; /** True when `a` and `b` are the same calendar day (ignoring the time of day). */ declare const isSameCalendarDay: (a: Date, b: Date) => boolean; /** Minutes elapsed since midnight (0–1439). */ declare const minutesIntoDay: (date: Date) => number; /** The twelve month anchors (first-of-month) of `date`'s year, January first. */ declare const getYearMonths: (date: Date) => Date[]; //#endregion //#region src/utils/drag.d.ts /** * True when two time ranges overlap. Edges touching (one ends exactly when the * next begins) do not count as an overlap. Compares absolute instants, so events * at the same wall-clock time on different days do not overlap. */ declare function eventsOverlap(aStart: Date, aEnd: Date, bStart: Date, bEnd: Date): boolean; /** * True when the range `[start, end)` would overlap any event in `events` other * than `moved` (compared by reference). Used by the `eventOverlap` guard to reject * a drag/resize that would land an event on top of another. */ declare function overlapsOtherEvents(events: readonly CalendarEvent[], moved: CalendarEvent, start: Date, end: Date): boolean; /** * How many calendar days one time-grid page spans, used to advance the view by a * whole page (e.g. when dragging an event past the edge into the next week). * Deliberately ignores `hiddenDays` so a week always steps 7, matching the * keyboard PageUp/PageDown paging and `Calendar`'s own page step. */ declare function pageStepDays(mode: CalendarMode, date: Date, weekStartsOn: WeekStartsOn, numberOfDays?: number): number; /** * Minutes to shift an event, snapping a vertical pixel drag to the nearest * `stepMinutes`. Runs on the UI thread inside the drag gesture. Returns 0 for a * degenerate grid (non-positive height/step). */ declare function snapDeltaMinutes(translationPx: number, cellHeightPx: number, stepMinutes: number): number; /** * Where a moved event may start, in minutes from midnight, given the grid's * visible hour window. A move keeps its duration, so only the start is held * inside the window: it can reach one `snapMinutes` step before `maxHour`, and * the end is free to run past the end of the day, continuing on the following * day. That keeps the event starting in the column it was dropped on (a * cross-day *move* is the horizontal drag) while still allowing a range that * spans midnight. Runs on the UI thread inside the drag gesture. */ declare function clampMoveStartMinutes(startMinutes: number, minHour: number, maxHour: number, snapMinutes: number): number; /** A copy of `date` shifted by `minutes` (may be negative). */ declare function shiftMinutes(date: Date, minutes: number): Date; /** * Resolve a committed drag into the event's new bounds: `start` shifts by * `deltaStartMinutes`, `end` by `deltaEndMinutes` (a move passes the same delta * to both and preserves elapsed duration, including across DST; a resize passes * 0 for the unchanged edge). Returns `null` only when the change * would *shrink* the event below one `snapMinutes` step, so a resize can't commit * a degenerate duration; a pure move (both deltas equal) keeps its duration and is * never rejected, even for an already sub-step event. Pure, so the commit path is * unit-testable without a running gesture. */ declare function resolveDraggedBounds(start: Date, end: Date, deltaStartMinutes: number, deltaEndMinutes: number, snapMinutes: number): { start: Date; end: Date; } | null; /** * The all-day range swept out between two day cells of the month grid, ordered * whichever way the sweep ran: `start` is midnight of the first day and `end` is * midnight after the last, so a one-day sweep still yields a usable event. Shared * by both renderers so a month drag-to-create means the same thing on each. */ declare function monthCreateRange(anchor: Date, hover: Date): { start: Date; end: Date; }; /** * The new bounds for an event picked up on `fromDay` and dropped on `toDay` in * the month grid: both ends move by the same number of calendar days, so the * event keeps its time of day and duration (DST included). Returns `null` when * the drop lands on the day it started, so a stray drag commits nothing. */ declare function monthDropBounds(event: CalendarEvent, fromDay: Date, toDay: Date): { start: Date; end: Date; } | null; /** * The start/end of a new event swept out on `day` by dragging from `startPx` to * `endPx` (vertical pixels from the grid's top, i.e. the `minHour` line). Both * ends snap to `snapMinutes`; the range is ordered (drag up or down) and widened * to at least one step so a stationary press still yields a usable event. * Returns `null` for a degenerate grid (non-positive height/step). Pure, so the * commit path is unit-testable without a running gesture. */ declare function cellRangeFromDrag(day: Date, startPx: number, endPx: number, cellHeightPx: number, minHour: number, snapMinutes: number): { start: Date; end: Date; } | null; //#endregion //#region src/utils/eventDisplay.d.ts /** * Minimum event-box height (px) before the built-in renderer shows the time line * on a narrow multi-column timed grid. Tied to the default theme's font sizes. */ declare const MIN_BOX_HEIGHT_FOR_TIME = 56; /** Hard-clip an overflowing title by default; opt into a trailing ellipsis. */ declare function titleEllipsizeMode(ellipsizeTitle: boolean): "clip" | "tail"; /** * Screen-reader label for an event: its title followed by "all day" or its time * range (which the grid otherwise only conveys visually). Empty title is dropped. */ declare function eventAccessibilityLabel(args: { title?: string; isAllDay: boolean; start: Date; end: Date; ampm: boolean; /** Spoken text for an all-day event. Default "all day". */ allDayLabel?: string; }): string; /** * Context describing how an event is being rendered, passed to a consumer's * {@link EventAccessibilityLabeler} so the label can adapt to the view (e.g. omit * the time in month mode, or read the 12-hour clock when `ampm` is set). */ interface EventAccessibilityLabelContext { /** The view the event is rendered in. */ mode: CalendarMode; /** Whether the event sits in the all-day lane (or is an all-day event in month view). */ isAllDay: boolean; /** Whether times are formatted as 12-hour AM/PM. */ ampm: boolean; } /** * Override for an event's screen-reader label. Return the full text to announce * for `event`; each renderer uses it verbatim in place of the built-in * {@link eventAccessibilityLabel}. Shared by both renderers, so a custom label * reads the same on web and native. */ type EventAccessibilityLabeler = (event: CalendarEvent, context: EventAccessibilityLabelContext) => string; /** * Month cells and the all-day lane show a single clipped line; timed-grid titles * (`undefined`) wrap to fill the box. */ declare function titleNumberOfLines(mode: CalendarMode, isAllDay: boolean): number | undefined; /** * The secondary line under the title in the built-in renderer, or `null` when * none should show. Timed events get their `start - end` range. An all-day event * gets the literal "All day" in the schedule (which has no all-day lane to * signal it positionally) and nothing on the day/week grid (the lane already * does). Month cells and `showTime={false}` always return `null`. */ declare function eventTimeLabel(args: { mode: CalendarMode; isAllDay: boolean; start: Date; end: Date; ampm: boolean; showTime: boolean; /** Text for an all-day event in the schedule. Default "All day". */ allDayLabel?: string; }): string | null; /** * The default hour-axis label shared by both renderers' time grids, so the gutter * reads the same on each: 24-hour "HH:00" (e.g. "08:00"), or a compact 12-hour * "h AM/PM" (e.g. "8 AM") when `ampm` is set. Exported so a custom hour renderer * can reuse the same formatting. */ declare function formatHour(hour: number, opts?: { ampm?: boolean; }): string; /** * Whether the time line fits in the box. The wide `day` column and contexts with * no live box height (e.g. schedule, where `boxHeightPx` is undefined) always * show it; narrow multi-column modes only once the box is at least * {@link MIN_BOX_HEIGHT_FOR_TIME} tall. Runs on the UI thread inside the event * renderer's animated style. */ declare function isTimeVisibleAtHeight(boxHeightPx: number | undefined, mode: CalendarMode): boolean; /** Layout for the built-in timed-grid event chip at a given box height. */ type EventChipLayout = { /** * Max whole title lines that fit in the box. `0` means "no clamp" (the box * height is unknown, e.g. the schedule), so the title may wrap freely. */ titleMaxLines: number; /** Whether the secondary time line still has room below the title. */ showTime: boolean; }; /** * Lay out the built-in timed-grid event chip for a box of `boxHeightPx`: how * many whole title lines fit, and whether the time line still has room below * them. The title is primary, so the title fills the box in whole lines (never a * half-cropped line) and the time only shows once a full line is left over. Pass * `titleLineHeightPx`/`timeLineHeightPx` matching the rendered line heights so * the clamp lands on a line boundary. * * Worklet-safe, so the native renderer can drive the title's max-height on the UI * thread as the grid zooms; the dom renderer calls it with its static box height. * A `boxHeightPx` of `undefined` (the schedule has no live box height) returns * `titleMaxLines: 0` (no clamp) with the unconditional time visibility. */ declare function eventChipLayout(args: { boxHeightPx: number | undefined; mode: CalendarMode; hasTime: boolean; titleLineHeightPx: number; timeLineHeightPx: number; paddingYPx: number; }): EventChipLayout; /** How many month-cell chips fit in the available height. */ type MonthEventCapacity = { /** Count when every event fits, with no overflow label. */ full: number; /** Count that leaves room for the "+N more" label. */ withMore: number; }; /** * Derive how many event chips fit in a month cell from the measured space. * `chipRowHeightPx` is one chip plus its gap; `moreRowHeightPx` is the overflow * label plus its gap. Both counts are clamped to >= 0. */ declare function monthEventCapacity(availableHeightPx: number, chipRowHeightPx: number, moreRowHeightPx: number): MonthEventCapacity; /** * Chips to show for a day: all of them when they fit, otherwise `withMore` (at * least one) so the rest collapse into a "+N more" label. */ declare function monthVisibleCount(total: number, capacity: MonthEventCapacity): number; //#endregion //#region src/utils/ical.d.ts /** An event parsed from iCal, carrying the standard fields it also round-trips. */ interface ICalEvent extends ICalendarEvent { /** The VEVENT `UID`, if present. */ uid?: string; /** The VEVENT `DESCRIPTION`, if present. */ description?: string; /** The VEVENT `LOCATION`, if present. */ location?: string; } /** Options for {@link toICalendar}. */ interface ToICalendarOptions { /** `PRODID` written to the calendar header. Default `-//super-calendar//EN`. */ prodId?: string; /** The `DTSTAMP` stamped on every event (when it was written). Default: now. */ now?: Date; } /** * Parse an iCalendar (`.ics`) string into events. Reads every `VEVENT`; ignores * VTODO/VJOURNAL/VTIMEZONE and unknown properties. Events without a usable * `DTSTART` are skipped. All-day events (`VALUE=DATE`) with no `DTEND` get a * one-day span. * * @example * ```ts * const events = parseICalendar(await file.text()); * ``` */ declare function parseICalendar(ics: string): ICalEvent[]; /** * Serialize events to an iCalendar (`.ics`) string. Timed events are written in * UTC (`...Z`); all-day events (`allDay: true`) use `VALUE=DATE`. A `recurrence` * becomes an `RRULE`, and `uid` / `description` / `location` round-trip. * * @example * ```ts * const ics = toICalendar(events); * ``` */ declare function toICalendar(events: ICalEvent[], options?: ToICalendarOptions): string; //#endregion //#region src/utils/useNow.d.ts /** Options for {@link useNow}. */ interface UseNowOptions { /** * A fixed instant to use instead of the device clock (a server-synced clock, * or a stable value for tests). A fixed instant doesn't tick. */ now?: Date; /** * Shift the instant into this IANA time zone's wall-clock (the same shift * `eventsInTimeZone` applies), so the now indicator lines up with events * displayed in that zone rather than the device's. */ timeZone?: string; /** Tick interval in ms (default one minute). */ tickMs?: number; } /** * The current wall-clock time, re-read every `tickMs` while `enabled`, shifted * into `timeZone` when given, or pinned to a fixed `now` override. Drives the * now indicator on both renderers so it can't disagree with zone-shifted events. */ declare function useNow(enabled: boolean, { now, timeZone, tickMs }?: UseNowOptions): Date; //#endregion //#region src/utils/useEventSource.d.ts /** Options for {@link useEventSource}. */ interface EventSourceOptions { /** * Feed format for URL sources: `"json"` (default) expects an array of event * objects with ISO `start`/`end` strings; `"ics"` parses an iCalendar feed. * URLs ending in `.ics` default to `"ics"`. */ format?: "json" | "ics"; /** * Map the parsed JSON array into events, for feeds whose shape doesn't match * `CalendarEvent` (rename fields, attach `resourceId`, filter). The default * revives `start`/`end` ISO strings on each item. */ map?: (items: unknown[]) => CalendarEvent[]; /** Re-fetch every N ms (a live feed). Omit to fetch once. */ refetchIntervalMs?: number; } /** What {@link useEventSource} returns. */ interface EventSourceState { /** The fetched events; the previous batch is kept while a refetch runs or fails. */ events: CalendarEvent[]; /** True while the first fetch (or a manual refetch) is in flight. */ loading: boolean; /** The last fetch error, or null. Events keep their previous value on error. */ error: Error | null; /** Fetch the source again now. */ refetch: () => Promise; } /** * Load events from a refreshable source: a JSON feed URL, an iCalendar feed * URL, or your own async function. The hook owns fetching, optional interval * refetching, and loading/error state; hand the returned `events` to any view. * * @example * ```tsx * const { events, loading, refetch } = useEventSource( * "https://example.com/feed.ics", * { refetchIntervalMs: 5 * 60_000 }, * ); * ; * ``` */ declare function useEventSource(source: string | (() => Promise[]>), { format, map, refetchIntervalMs }?: EventSourceOptions): EventSourceState; //#endregion //#region src/utils/layout.d.ts /** An event placed on a single day's time grid by {@link layoutDayEvents}, with its vertical span and overlap column. */ type PositionedEvent = { /** The source event for this segment. */ event: CalendarEvent; /** Hours from midnight to the event's segment start on this day (fractional). */ startHours: number; /** Segment duration in hours on this day (clamped to a small minimum). */ durationHours: number; /** Zero-based column index within its overlap cluster. */ column: number; /** Total columns in this event's overlap cluster. */ columns: number; /** True when the segment is clipped because the event continues before/after this day. */ continuesBefore: boolean; continuesAfter: boolean; }; /** * Lay out a single day's events: events that overlap in time are split into * side-by-side columns. Multi-day events are clipped to the portion that falls * on `day` (e.g. a 23:00→01:00 event renders 23:00–24:00 on the start day and * 00:00–01:00 on the next). Pure — safe to call per render, never per frame. */ declare function layoutDayEvents(events: CalendarEvent[], day: Date): PositionedEvent[]; /** * Whether an event belongs in the all-day lane. An explicit `allDay` flag wins; * otherwise it's inferred when the event spans whole days (both `start` and * `end` land on midnight, e.g. an iCal-style all-day event). Pure. */ declare function isAllDayEvent(event: CalendarEvent): boolean; /** * The `startOfDay` ISO keys of every calendar day an event touches (inclusive). * An event ending exactly at midnight does not count the following day. Used to * index events by day for the month grid. Pure. */ declare function eventDayKeys(event: CalendarEvent): string[]; /** * Index events by the `startOfDay` ISO key of every day they touch (via * {@link eventDayKeys}), so a month grid can look up a day's events with * `startOfDay(date).toISOString()`. Built once and shared across month cells. */ declare function groupEventsByDay(events: readonly CalendarEvent[]): Map[]>; /** * Order a day's events for the month and list views: all-day events come first * (they head the day regardless of their start time), then timed events by start. * Shared by both renderers so the order is identical. Use as an `Array.sort` * comparator. */ declare function compareDayEvents(a: CalendarEvent, b: CalendarEvent): number; /** * The closed hour-spans of a day to shade on the time grid, given a * `businessHours` callback and the visible `[minHour, maxHour]` window: the spans * before open and after close (clamped to the window), the whole window when the * day is closed (`null`) or the open hours are inverted/empty, or none when the * callback returns `undefined`. Shared by both renderers so shading stays * identical. Co-located with `groupEventsByDay`; both feed the grid layout. */ declare function closedHourBands(day: Date, businessHours: BusinessHours | undefined, minHour?: number, maxHour?: number): { start: number; end: number; }[]; /** True when the event paints as a shaded background band, not an event box. */ declare function isBackgroundEvent(event: CalendarEvent): boolean; /** * The background events of `events` sliced to `day`, as fractional-hour bands * (an all-day or multi-day background covers the day's full window). Shared by * both renderers so the shading can't disagree. */ declare function backgroundBandsForDay(events: CalendarEvent[], day: Date): { event: CalendarEvent; startHours: number; endHours: number; }[]; //#endregion //#region src/utils/monthGrid.d.ts /** * One event's placement within a single week row of the month grid: the column * span it covers and the lane (stacked row) it sits in, plus whether it continues * past this row's edges (so the renderer can draw a "continues" affordance and * square off the clipped end). Columns index into the row's `days` array, so with * `hiddenDays` a bar spans the visible columns it touches. */ interface MonthEventSegment { event: CalendarEvent; /** First covered column in the row (0-based, inclusive). */ startCol: number; /** Last covered column in the row (inclusive). */ endCol: number; /** Stacking row within the day cells; 0 is the topmost. */ lane: number; /** The event started before this row's first day. */ continuesBefore: boolean; /** The event ends after this row's last day. */ continuesAfter: boolean; } /** The laid-out event segments for one week row, with the number of lanes used. */ interface MonthWeekEvents { segments: MonthEventSegment[]; /** Highest lane index used + 1 (0 when the row has no events). */ laneCount: number; } /** * Lay out one week row's events as continuous horizontal bars: each event becomes * a single segment spanning the columns it covers (not a chip repeated per day), * stacked into lanes so overlapping events sit on separate rows. Multi-day events * that cross the row edges are marked `continuesBefore`/`continuesAfter`. Shared by * both renderers so the month grid draws identical spanning bars. * * Column indices map into the given `days` array in whatever display order it uses, * so this is order-independent: a right-to-left (reversed) week lays out correctly, * and `continuesBefore`/`continuesAfter` track the array's first/last column edge * (not chronology), so the clipped end is squared on the right visual side. * * Lanes are packed greedily after sorting by start column then longest span first, * matching how calendars keep long events on the upper lanes. */ declare function layoutMonthWeek(days: Date[], events: readonly CalendarEvent[]): MonthWeekEvents; /** A single day in the grid, with all the state a custom cell needs to render. */ interface MonthGridDay { /** The day this cell represents. */ date: Date; /** Stable `yyyy-MM-dd` id, handy as a React key. */ id: string; /** Day-of-month, e.g. "1". */ label: string; /** False for days bleeding in from the previous or next month. */ isCurrentMonth: boolean; /** The day is today. */ isToday: boolean; /** The day is a Saturday or Sunday. */ isWeekend: boolean; /** The day fails the min/max/disabled constraints. */ isDisabled: boolean; /** The day is a `selectedDates` day or a range endpoint (and not disabled). */ isSelected: boolean; /** The day is the range's start endpoint. */ isRangeStart: boolean; /** The day is the range's end endpoint. */ isRangeEnd: boolean; /** Inside a complete range (endpoints included). */ isInRange: boolean; } /** One week row. */ interface MonthGridWeek { /** Stable id for the week row, handy as a React key. */ id: string; /** The seven days of the row, in display order. */ days: MonthGridDay[]; } /** A weekday header cell (e.g. "Mon"). */ interface MonthGridWeekday { /** A representative date for the column, used to derive the label. */ date: Date; /** The short weekday name (e.g. "Mon"), localised via `locale`. */ label: string; } /** The full grid for one month: the week rows and the weekday header cells. */ interface MonthGrid { /** The week rows, padded to whole weeks. */ weeks: MonthGridWeek[]; /** The weekday header cells, in display order. */ weekdays: MonthGridWeekday[]; } /** * Weekday header label width: `narrow` ("M"), `short` ("Mon", the default), or * `long` ("Monday"). */ type WeekdayFormat = "narrow" | "short" | "long"; /** * The date-fns format token for a {@link WeekdayFormat}. Exposed so a renderer * that formats its own weekday header (rather than reading {@link buildMonthGrid}) * keeps the same mapping. */ declare function weekdayFormatToken(format: WeekdayFormat): string; /** Options for {@link buildMonthGrid} and {@link useMonthGrid}. */ interface UseMonthGridOptions extends DateSelectionConstraints { /** First day of the week. Sunday = 0 (default) … Saturday = 6. */ weekStartsOn?: WeekStartsOn; /** Weekdays (0=Sunday…6=Saturday) to drop from the grid and header. */ hiddenDays?: number[]; /** Weekday header label width. Default `short` ("Mon"). */ weekdayFormat?: WeekdayFormat; /** Always return six week rows for a fixed-height grid. Default false. */ showSixWeeks?: boolean; /** Reverse each week's day order (right-to-left). Default false. */ isRTL?: boolean; /** Selected discrete days (single/multiple). */ selectedDates?: Date[]; /** Selected span. */ selectedRange?: DateRange; /** A date-fns locale for the weekday labels. */ locale?: Locale; } /** * Pure month-grid builder: the weeks and weekday headers for `month`, each day * annotated with selection/disabled/today state. Use this when you need the * data outside React; inside a component prefer {@link useMonthGrid}. */ declare function buildMonthGrid(month: Date, options?: UseMonthGridOptions): MonthGrid; /** * Headless month-grid hook. Returns the weeks and weekday headers for `month`, * each day annotated with selection/disabled/today state, so you can render a * fully custom calendar without reimplementing the date maths. * * ```tsx * const { weeks, weekdays } = useMonthGrid(month, { selectedRange: range }); * // map weekdays -> header cells, weeks -> rows, days -> your own * ``` */ declare function useMonthGrid(month: Date, options?: UseMonthGridOptions): MonthGrid; //#endregion //#region src/utils/recurrence.d.ts /** * Materialise recurring events into concrete occurrences overlapping * `[rangeStart, rangeEnd]`. Non-recurring events pass through untouched, so the * result is ready to hand to ``. Each occurrence keeps * the original event's duration and fields (minus `recurrence`). */ declare function expandRecurringEvents(events: CalendarEvent[], rangeStart: Date, rangeEnd: Date): CalendarEvent[]; //#endregion //#region src/utils/timezone.d.ts /** * Reinterpret an instant as its wall-clock time in `timeZone`, returned as a * device-local `Date` whose fields (hours, minutes, …) read back as that zone's * clock. The calendar lays events out from `getHours()`/`getMinutes()`, so * passing zoned dates makes it render in `timeZone` regardless of the device. * * DST-correct via `Intl` (available on modern React Native Hermes/JSC and the * web). The result is for display/layout only; it no longer points at the * original UTC instant, so don't round-trip it back to a real time. */ declare function toZonedTime(date: Date, timeZone: string): Date; /** * The inverse of {@link toZonedTime}: given a wall-clock time in `timeZone`, * return the absolute UTC instant. Pass the wall clock as a `Date` whose **UTC** * fields hold the components (e.g. `new Date(Date.UTC(y, m, d, h, min))`). Used to * resolve iCal `TZID` times; DST-correct via a two-pass offset (ambiguous * fall-back times resolve to the post-transition offset). */ declare function zonedTimeToUtc(wallClock: Date, timeZone: string): Date; /** * Map every event's `start`/`end` through {@link toZonedTime} so the calendar * displays them in `timeZone`. Other fields are preserved. Memoize the result * (e.g. with `useMemo`) since it allocates new dates. */ declare function eventsInTimeZone(events: CalendarEvent[], timeZone: string): CalendarEvent[]; //#endregion export { BusinessHours, BusinessHoursBand, BusinessHoursValue, CalendarColors, CalendarEvent, CalendarMode, CalendarSelection, CalendarSelectionProvider, DateRange, DateSelectionConstraints, DayBadgeKind, DaySelectionState, EventAccessibilityLabelContext, EventAccessibilityLabeler, EventChipLayout, EventKeyExtractor, EventSourceOptions, EventSourceState, ICalEvent, ICalendarEvent, MIN_BOX_HEIGHT_FOR_TIME, MonthEventCapacity, MonthEventSegment, MonthGrid, MonthGridDay, MonthGridWeek, MonthGridWeekday, MonthWeekEvents, PositionedEvent, RangeBandKind, RecurrenceFrequency, RecurrenceRule, TimeGridMode, ToICalendarOptions, UseDateRangeOptions, UseDateRangeResult, UseMonthGridOptions, UseNowOptions, WeekStartsOn, WeekdayFormat, backgroundBandsForDay, bandRounding, buildMonthGrid, buildMonthWeeks, cellRangeFromDrag, clampMoveStartMinutes, closedHourBands, compareDayEvents, darkColors, dayBadgeKind, daySelectionState, eventAccessibilityLabel, eventChipLayout, eventDayKeys, eventTimeLabel, eventsInTimeZone, eventsOverlap, expandRecurringEvents, filterHiddenDays, formatHour, getIsToday, getViewDays, getWeekDays, getYearMonths, groupEventsByDay, isAllDayEvent, isBackgroundEvent, isDateSelectable, isRangeEndpoint, isSameCalendarDay, isTimeVisibleAtHeight, isWeekend, isWithinDateRange, layoutDayEvents, layoutMonthWeek, lightColors, minutesIntoDay, monthCreateRange, monthDropBounds, monthEventCapacity, monthVisibleCount, nextDateRange, overlapsOtherEvents, pageStepDays, parseICalendar, rangeBandKind, resolveDraggedBounds, shiftMinutes, snapDeltaMinutes, titleEllipsizeMode, titleNumberOfLines, toICalendar, toZonedTime, useCalendarSelection, useDateRange, useEventSource, useMonthGrid, useNow, viewDayCount, weekDaysCount, weekdayFormatToken, zonedTimeToUtc };