@super-calendar/core 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Afonso Jorge Ramos
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,542 @@
1
+ import { Locale } from "date-fns";
2
+
3
+ //#region src/types.d.ts
4
+ type CalendarMode = "day" | "3days" | "week" | "custom" | "month" | "schedule";
5
+ /** The time-grid modes (day-column views, excluding month and schedule). */
6
+ type TimeGridMode = Exclude<CalendarMode, "month" | "schedule">;
7
+ /**
8
+ * The minimal shape every calendar event must have. Layout (positioning,
9
+ * overlap resolution, paging) only ever reads `start`/`end`; `title` is used by
10
+ * the built-in default renderer. Anything else lives in your own type and is
11
+ * threaded through untouched via the `T` generic.
12
+ */
13
+ interface ICalendarEvent {
14
+ start: Date;
15
+ end: Date;
16
+ title?: string;
17
+ /**
18
+ * Force this event into the all-day lane (above the time grid) instead of the
19
+ * timed columns. When omitted, an event is treated as all-day only if it spans
20
+ * whole days (both `start` and `end` land on midnight).
21
+ */
22
+ allDay?: boolean;
23
+ /** Ignore taps/long-presses on this event (the built-in renderer also dims it). */
24
+ disabled?: boolean;
25
+ /**
26
+ * Repeat rule. Pass the event to `expandRecurringEvents(events, start, end)` to
27
+ * materialise its occurrences within a range; the calendar itself doesn't
28
+ * expand recurrences.
29
+ */
30
+ recurrence?: RecurrenceRule;
31
+ }
32
+ /** How often a recurring event repeats. */
33
+ type RecurrenceFrequency = "daily" | "weekly" | "monthly" | "yearly";
34
+ /** A simple, RRULE-inspired repeat rule expanded by `expandRecurringEvents`. */
35
+ interface RecurrenceRule {
36
+ freq: RecurrenceFrequency;
37
+ /** Repeat every N periods. Default 1. */
38
+ interval?: number;
39
+ /** Stop after this many occurrences (including the first). */
40
+ count?: number;
41
+ /** Stop on/after this date (inclusive). */
42
+ until?: Date;
43
+ /**
44
+ * For `weekly`: the weekdays to repeat on (0 = Sunday … 6 = Saturday), keeping
45
+ * the event's time of day. Omit to repeat on the start date's own weekday.
46
+ */
47
+ weekdays?: WeekStartsOn[];
48
+ }
49
+ /**
50
+ * An event carrying arbitrary extra fields `T` alongside the required shape.
51
+ * `ICalendarEvent` is authoritative: keys it reserves (`start`/`end`/`title`)
52
+ * cannot be re-typed by `T`.
53
+ */
54
+ type CalendarEvent<T = unknown> = ICalendarEvent & Omit<T, keyof ICalendarEvent>;
55
+ /** Build a stable key for an event. Defaults to start-time + index. */
56
+ type EventKeyExtractor<T = unknown> = (event: CalendarEvent<T>, index: number) => string;
57
+ /** Sunday = 0 … Saturday = 6, matching `Date.prototype.getDay()`. */
58
+ type WeekStartsOn = 0 | 1 | 2 | 3 | 4 | 5 | 6;
59
+ /**
60
+ * A day's open hours for `businessHours` shading on the time grid: `{ start, end }`
61
+ * in hours (fractions allowed, e.g. 9.5), or `null` when the day is closed (fully
62
+ * shaded). `undefined` from the callback means "no business-hours shading". Shared
63
+ * by both renderers; pair with `closedHourBands` to get the spans to shade.
64
+ */
65
+ type BusinessHours = (date: Date) => {
66
+ start: number;
67
+ end: number;
68
+ } | null;
69
+ //#endregion
70
+ //#region src/tokens.d.ts
71
+ interface CalendarColors {
72
+ /** Hour lines, day separators and month-cell borders. */
73
+ gridLine: string;
74
+ /** Background tint behind weekend columns/cells. */
75
+ weekendBackground: string;
76
+ /** Background tint over hours outside business hours (time grid). */
77
+ outsideHoursBackground: string;
78
+ /** Today badge fill. */
79
+ todayBackground: string;
80
+ /** Today badge text. */
81
+ todayText: string;
82
+ /** Selected day / range-endpoint badge fill. */
83
+ selectedBackground: string;
84
+ /** Selected day / range-endpoint badge text. */
85
+ selectedText: string;
86
+ /** Band behind a selected range. */
87
+ rangeBackground: string;
88
+ /** Hover highlight behind a day (DOM, mouse only). */
89
+ hoverBackground: string;
90
+ /** Current-time indicator line (time grid). */
91
+ nowIndicator: string;
92
+ /** Primary text (day numbers, weekday labels). */
93
+ text: string;
94
+ /** Muted text (hour labels, "+N more"). */
95
+ textMuted: string;
96
+ /** Dimmed text for disabled / adjacent-month days. */
97
+ textDisabled: string;
98
+ /** Default event chip fill. */
99
+ eventBackground: string;
100
+ /** Default event chip text. */
101
+ eventText: string;
102
+ }
103
+ declare const lightColors: CalendarColors;
104
+ declare const darkColors: CalendarColors;
105
+ //#endregion
106
+ //#region src/presentation.d.ts
107
+ /** Which range-band shape a day shows. */
108
+ type RangeBandKind = "none" | "fill" | "pill-start" | "pill-mid" | "pill-end";
109
+ /** The band shape for a day, given the fill-cell option. */
110
+ declare function rangeBandKind(day: {
111
+ isInRange: boolean;
112
+ isRangeStart: boolean;
113
+ isRangeEnd: boolean;
114
+ }, fillCell: boolean): RangeBandKind;
115
+ /** Whether a band shape rounds its leading / trailing edge (pill ends). */
116
+ declare function bandRounding(kind: RangeBandKind): {
117
+ start: boolean;
118
+ end: boolean;
119
+ };
120
+ /** Which filled badge a day shows (today wins over a selection). */
121
+ type DayBadgeKind = "none" | "today" | "selected";
122
+ /**
123
+ * The filled-badge kind for a day. `isSelected` is true for both range endpoints
124
+ * and discrete selected days; today always wins when it coincides.
125
+ */
126
+ declare function dayBadgeKind(day: {
127
+ isSelected: boolean;
128
+ }, isToday: boolean): DayBadgeKind;
129
+ //#endregion
130
+ //#region src/utils/dateRange.d.ts
131
+ /** A selected span. `end` is `null` while only the first endpoint has been picked. */
132
+ interface DateRange {
133
+ start: Date;
134
+ end: Date | null;
135
+ }
136
+ /** Limits applied before a date can be selected. */
137
+ interface DateSelectionConstraints {
138
+ /** Earliest selectable day (inclusive). */
139
+ minDate?: Date;
140
+ /** Latest selectable day (inclusive). */
141
+ maxDate?: Date;
142
+ /** Return true to forbid selecting a specific day. */
143
+ isDateDisabled?: (date: Date) => boolean;
144
+ }
145
+ /** Whether `date` passes the min/max/disabled constraints (compared by calendar day). */
146
+ declare function isDateSelectable(date: Date, constraints?: DateSelectionConstraints): boolean;
147
+ /**
148
+ * The range after pressing `pressed`, mirroring the familiar date-picker model:
149
+ * - no range yet, or a complete range exists → start fresh (`{ start: pressed, end: null }`),
150
+ * so a third press resets the selection.
151
+ * - an open range (a start but no end) → close it, auto-swapping when the press
152
+ * precedes the start so `start <= end` always holds.
153
+ *
154
+ * Returns `current` unchanged when `pressed` isn't selectable.
155
+ */
156
+ declare function nextDateRange(current: DateRange | null, pressed: Date, constraints?: DateSelectionConstraints): DateRange | null;
157
+ /** True when `date` is one of the range's two endpoints. */
158
+ declare function isRangeEndpoint(date: Date, range: DateRange | null): boolean;
159
+ /** True when `date` falls within a complete range (endpoints included). */
160
+ declare function isWithinDateRange(date: Date, range: DateRange | null): boolean;
161
+ /** The selection/disabled flags for one day. */
162
+ interface DaySelectionState {
163
+ /** Fails the min/max/disabled constraints. */
164
+ isDisabled: boolean;
165
+ /** A `selectedDates` day or a range endpoint (and not disabled). */
166
+ isSelected: boolean;
167
+ /** Inside a complete range, endpoints included (and not disabled). */
168
+ isInRange: boolean;
169
+ isRangeStart: boolean;
170
+ isRangeEnd: boolean;
171
+ }
172
+ /**
173
+ * The canonical per-day selection state, shared by `MonthView` (rendering) and
174
+ * `buildMonthGrid` (the headless grid) so the built-in views and a custom
175
+ * calendar can never disagree on what a day's state is.
176
+ */
177
+ declare function daySelectionState(date: Date, selection: {
178
+ selectedDates?: Date[];
179
+ selectedRange?: DateRange | null;
180
+ }, constraints?: DateSelectionConstraints): DaySelectionState;
181
+ /**
182
+ * Per-day state shared with the month grid via context: the current selection
183
+ * plus the selectability constraints. Threaded through context (not props) so
184
+ * cached/virtualized day cells still repaint when any of it changes.
185
+ */
186
+ interface CalendarSelection extends DateSelectionConstraints {
187
+ selectedDates?: Date[];
188
+ selectedRange?: DateRange;
189
+ }
190
+ /**
191
+ * Provides the active selection to the month grid. Day cells read it via
192
+ * {@link useCalendarSelection} so they repaint on selection changes even when
193
+ * the virtualized list has cached (and so won't re-render) their page.
194
+ */
195
+ declare const CalendarSelectionProvider: import("react").Provider<CalendarSelection>;
196
+ declare const useCalendarSelection: () => CalendarSelection;
197
+ /** Options for {@link useDateRange}. */
198
+ interface UseDateRangeOptions extends DateSelectionConstraints {
199
+ /** Pre-select a range on mount. */
200
+ initialRange?: DateRange | null;
201
+ }
202
+ /**
203
+ * Controlled-ish range selection state for the month view. Returns the current
204
+ * `range` plus an `onPressDate` handler to wire to `Calendar`'s `onPressDay`, a
205
+ * `reset`, and the raw `setRange` for full control.
206
+ *
207
+ * ```tsx
208
+ * const { range, onPressDate } = useDateRange({ minDate: new Date() });
209
+ * <Calendar mode="month" selectedRange={range ?? undefined} onPressDay={onPressDate} … />
210
+ * ```
211
+ */
212
+ declare function useDateRange(options?: UseDateRangeOptions): {
213
+ range: DateRange | null;
214
+ onPressDate: (date: Date) => void;
215
+ selectRange: (a: Date, b: Date) => void;
216
+ reset: () => void;
217
+ setRange: import("react").Dispatch<import("react").SetStateAction<DateRange | null>>;
218
+ };
219
+ //#endregion
220
+ //#region src/utils/dates.d.ts
221
+ /** The seven dates of the week containing `date`, starting on `weekStartsOn`. */
222
+ declare const getWeekDays: (date: Date, weekStartsOn: WeekStartsOn) => Date[];
223
+ /** How many day columns a time-grid mode shows. `custom` uses `numberOfDays`. */
224
+ declare const viewDayCount: (mode: CalendarMode, numberOfDays?: number) => number;
225
+ /**
226
+ * Days in the inclusive span from `weekStartsOn` to `weekEndsOn` (1–7),
227
+ * wrapping when the end precedes the start (e.g. Sat→Wed). Mirrors
228
+ * react-native-big-calendar's `weekDaysCount`.
229
+ */
230
+ declare const weekDaysCount: (weekStartsOn: WeekStartsOn, weekEndsOn: WeekStartsOn) => number;
231
+ /**
232
+ * The day columns to render for a time-grid page. `week` spans the calendar week
233
+ * (honouring `weekStartsOn`). `custom` with a `weekEndsOn` spans the partial week
234
+ * from `weekStartsOn` to `weekEndsOn` (anchored to `date`'s week, paging by week);
235
+ * otherwise every mode shows `viewDayCount` consecutive days starting at `date`.
236
+ */
237
+ declare const getViewDays: (mode: CalendarMode, date: Date, weekStartsOn: WeekStartsOn, numberOfDays?: number, isRTL?: boolean, weekEndsOn?: WeekStartsOn) => Date[];
238
+ /**
239
+ * The calendar weeks covering `month`, padded to whole weeks starting on
240
+ * `weekStartsOn`. `showSixWeeks` always returns six rows (42 days) for a
241
+ * fixed-height grid; `isRTL` reverses each week's day order.
242
+ */
243
+ declare const buildMonthWeeks: (month: Date, weekStartsOn: WeekStartsOn, {
244
+ showSixWeeks,
245
+ isRTL
246
+ }?: {
247
+ showSixWeeks?: boolean;
248
+ isRTL?: boolean;
249
+ }) => Date[][];
250
+ declare const isWeekend: (date: Date) => boolean;
251
+ declare const getIsToday: (date: Date) => boolean;
252
+ declare const isSameCalendarDay: (a: Date, b: Date) => boolean;
253
+ /** Minutes elapsed since midnight (0–1439). */
254
+ declare const minutesIntoDay: (date: Date) => number;
255
+ //#endregion
256
+ //#region src/utils/drag.d.ts
257
+ /**
258
+ * Minutes to shift an event, snapping a vertical pixel drag to the nearest
259
+ * `stepMinutes`. Runs on the UI thread inside the drag gesture. Returns 0 for a
260
+ * degenerate grid (non-positive height/step).
261
+ */
262
+ declare function snapDeltaMinutes(translationPx: number, cellHeightPx: number, stepMinutes: number): number;
263
+ /** A copy of `date` shifted by `minutes` (may be negative). */
264
+ declare function shiftMinutes(date: Date, minutes: number): Date;
265
+ /**
266
+ * Resolve a committed drag into the event's new bounds: `start` shifts by
267
+ * `deltaStartMinutes`, `end` by `deltaEndMinutes` (a move passes the same delta
268
+ * to both; a resize passes 0 for the start). Returns `null` when the change
269
+ * would collapse the event below one `snapMinutes` step, so the gesture leaves
270
+ * the event untouched rather than committing a degenerate duration. Pure, so the
271
+ * commit path is unit-testable without a running gesture.
272
+ */
273
+ declare function resolveDraggedBounds(start: Date, end: Date, deltaStartMinutes: number, deltaEndMinutes: number, snapMinutes: number): {
274
+ start: Date;
275
+ end: Date;
276
+ } | null;
277
+ /**
278
+ * The start/end of a new event swept out on `day` by dragging from `startPx` to
279
+ * `endPx` (vertical pixels from the grid's top, i.e. the `minHour` line). Both
280
+ * ends snap to `snapMinutes`; the range is ordered (drag up or down) and widened
281
+ * to at least one step so a stationary press still yields a usable event.
282
+ * Returns `null` for a degenerate grid (non-positive height/step). Pure, so the
283
+ * commit path is unit-testable without a running gesture.
284
+ */
285
+ declare function cellRangeFromDrag(day: Date, startPx: number, endPx: number, cellHeightPx: number, minHour: number, snapMinutes: number): {
286
+ start: Date;
287
+ end: Date;
288
+ } | null;
289
+ //#endregion
290
+ //#region src/utils/eventDisplay.d.ts
291
+ /**
292
+ * Minimum event-box height (px) before the built-in renderer shows the time line
293
+ * on a narrow multi-column timed grid. Tied to the default theme's font sizes.
294
+ */
295
+ declare const MIN_BOX_HEIGHT_FOR_TIME = 56;
296
+ /** Hard-clip an overflowing title by default; opt into a trailing ellipsis. */
297
+ declare function titleEllipsizeMode(ellipsizeTitle: boolean): "clip" | "tail";
298
+ /**
299
+ * Screen-reader label for an event: its title followed by "all day" or its time
300
+ * range (which the grid otherwise only conveys visually). Empty title is dropped.
301
+ */
302
+ declare function eventAccessibilityLabel(args: {
303
+ title?: string;
304
+ isAllDay: boolean;
305
+ start: Date;
306
+ end: Date;
307
+ ampm: boolean; /** Spoken text for an all-day event. Default "all day". */
308
+ allDayLabel?: string;
309
+ }): string;
310
+ /**
311
+ * Month cells and the all-day lane show a single clipped line; timed-grid titles
312
+ * (`undefined`) wrap to fill the box.
313
+ */
314
+ declare function titleNumberOfLines(mode: CalendarMode, isAllDay: boolean): number | undefined;
315
+ /**
316
+ * The secondary line under the title in the built-in renderer, or `null` when
317
+ * none should show. Timed events get their `start - end` range. An all-day event
318
+ * gets the literal "All day" in the schedule (which has no all-day lane to
319
+ * signal it positionally) and nothing on the day/week grid (the lane already
320
+ * does). Month cells and `showTime={false}` always return `null`.
321
+ */
322
+ declare function eventTimeLabel(args: {
323
+ mode: CalendarMode;
324
+ isAllDay: boolean;
325
+ start: Date;
326
+ end: Date;
327
+ ampm: boolean;
328
+ showTime: boolean; /** Text for an all-day event in the schedule. Default "All day". */
329
+ allDayLabel?: string;
330
+ }): string | null;
331
+ /**
332
+ * The default hour-axis label shared by both renderers' time grids, so the gutter
333
+ * reads the same on each: 24-hour "HH:00" (e.g. "08:00"), or a compact 12-hour
334
+ * "h AM/PM" (e.g. "8 AM") when `ampm` is set. Exported so a custom hour renderer
335
+ * can reuse the same formatting.
336
+ */
337
+ declare function formatHour(hour: number, opts?: {
338
+ ampm?: boolean;
339
+ }): string;
340
+ /**
341
+ * Whether the time line fits in the box. The wide `day` column and contexts with
342
+ * no live box height (e.g. schedule, where `boxHeightPx` is undefined) always
343
+ * show it; narrow multi-column modes only once the box is at least
344
+ * {@link MIN_BOX_HEIGHT_FOR_TIME} tall. Runs on the UI thread inside the event
345
+ * renderer's animated style.
346
+ */
347
+ declare function isTimeVisibleAtHeight(boxHeightPx: number | undefined, mode: CalendarMode): boolean;
348
+ /** Layout for the built-in timed-grid event chip at a given box height. */
349
+ type EventChipLayout = {
350
+ /**
351
+ * Max whole title lines that fit in the box. `0` means "no clamp" (the box
352
+ * height is unknown, e.g. the schedule), so the title may wrap freely.
353
+ */
354
+ titleMaxLines: number; /** Whether the secondary time line still has room below the title. */
355
+ showTime: boolean;
356
+ };
357
+ /**
358
+ * Lay out the built-in timed-grid event chip for a box of `boxHeightPx`: how
359
+ * many whole title lines fit, and whether the time line still has room below
360
+ * them. The title is primary, so the title fills the box in whole lines (never a
361
+ * half-cropped line) and the time only shows once a full line is left over. Pass
362
+ * `titleLineHeightPx`/`timeLineHeightPx` matching the rendered line heights so
363
+ * the clamp lands on a line boundary.
364
+ *
365
+ * Worklet-safe, so the native renderer can drive the title's max-height on the UI
366
+ * thread as the grid zooms; the dom renderer calls it with its static box height.
367
+ * A `boxHeightPx` of `undefined` (the schedule has no live box height) returns
368
+ * `titleMaxLines: 0` (no clamp) with the unconditional time visibility.
369
+ */
370
+ declare function eventChipLayout(args: {
371
+ boxHeightPx: number | undefined;
372
+ mode: CalendarMode;
373
+ hasTime: boolean;
374
+ titleLineHeightPx: number;
375
+ timeLineHeightPx: number;
376
+ paddingYPx: number;
377
+ }): EventChipLayout;
378
+ /** How many month-cell chips fit in the available height. */
379
+ type MonthEventCapacity = {
380
+ /** Count when every event fits, with no overflow label. */full: number; /** Count that leaves room for the "+N more" label. */
381
+ withMore: number;
382
+ };
383
+ /**
384
+ * Derive how many event chips fit in a month cell from the measured space.
385
+ * `chipRowHeightPx` is one chip plus its gap; `moreRowHeightPx` is the overflow
386
+ * label plus its gap. Both counts are clamped to >= 0.
387
+ */
388
+ declare function monthEventCapacity(availableHeightPx: number, chipRowHeightPx: number, moreRowHeightPx: number): MonthEventCapacity;
389
+ /**
390
+ * Chips to show for a day: all of them when they fit, otherwise `withMore` (at
391
+ * least one) so the rest collapse into a "+N more" label.
392
+ */
393
+ declare function monthVisibleCount(total: number, capacity: MonthEventCapacity): number;
394
+ //#endregion
395
+ //#region src/utils/layout.d.ts
396
+ type PositionedEvent<T> = {
397
+ event: CalendarEvent<T>; /** Hours from midnight to the event's segment start on this day (fractional). */
398
+ startHours: number; /** Segment duration in hours on this day (clamped to a small minimum). */
399
+ durationHours: number; /** Zero-based column index within its overlap cluster. */
400
+ column: number; /** Total columns in this event's overlap cluster. */
401
+ columns: number; /** True when the segment is clipped because the event continues before/after this day. */
402
+ continuesBefore: boolean;
403
+ continuesAfter: boolean;
404
+ };
405
+ /**
406
+ * Lay out a single day's events: events that overlap in time are split into
407
+ * side-by-side columns. Multi-day events are clipped to the portion that falls
408
+ * on `day` (e.g. a 23:00→01:00 event renders 23:00–24:00 on the start day and
409
+ * 00:00–01:00 on the next). Pure — safe to call per render, never per frame.
410
+ */
411
+ declare function layoutDayEvents<T>(events: CalendarEvent<T>[], day: Date): PositionedEvent<T>[];
412
+ /**
413
+ * Whether an event belongs in the all-day lane. An explicit `allDay` flag wins;
414
+ * otherwise it's inferred when the event spans whole days (both `start` and
415
+ * `end` land on midnight, e.g. an iCal-style all-day event). Pure.
416
+ */
417
+ declare function isAllDayEvent<T>(event: CalendarEvent<T>): boolean;
418
+ /**
419
+ * The `startOfDay` ISO keys of every calendar day an event touches (inclusive).
420
+ * An event ending exactly at midnight does not count the following day. Used to
421
+ * index events by day for the month grid. Pure.
422
+ */
423
+ declare function eventDayKeys<T>(event: CalendarEvent<T>): string[];
424
+ /**
425
+ * Index events by the `startOfDay` ISO key of every day they touch (via
426
+ * {@link eventDayKeys}), so a month grid can look up a day's events with
427
+ * `startOfDay(date).toISOString()`. Built once and shared across month cells.
428
+ */
429
+ declare function groupEventsByDay<T>(events: readonly CalendarEvent<T>[]): Map<string, CalendarEvent<T>[]>;
430
+ /**
431
+ * Order a day's events for the month and list views: all-day events come first
432
+ * (they head the day regardless of their start time), then timed events by start.
433
+ * Shared by both renderers so the order is identical. Use as an `Array.sort`
434
+ * comparator.
435
+ */
436
+ declare function compareDayEvents<T>(a: CalendarEvent<T>, b: CalendarEvent<T>): number;
437
+ /**
438
+ * The closed hour-spans of a day to shade on the time grid, given a
439
+ * `businessHours` callback and the visible `[minHour, maxHour]` window: the spans
440
+ * before open and after close (clamped to the window), the whole window when the
441
+ * day is closed (`null`) or the open hours are inverted/empty, or none when the
442
+ * callback returns `undefined`. Shared by both renderers so shading stays
443
+ * identical. Co-located with `groupEventsByDay`; both feed the grid layout.
444
+ */
445
+ declare function closedHourBands(day: Date, businessHours: BusinessHours | undefined, minHour?: number, maxHour?: number): {
446
+ start: number;
447
+ end: number;
448
+ }[];
449
+ //#endregion
450
+ //#region src/utils/monthGrid.d.ts
451
+ /** A single day in the grid, with all the state a custom cell needs to render. */
452
+ interface MonthGridDay {
453
+ date: Date;
454
+ /** Stable `yyyy-MM-dd` id, handy as a React key. */
455
+ id: string;
456
+ /** Day-of-month, e.g. "1". */
457
+ label: string;
458
+ isCurrentMonth: boolean;
459
+ isToday: boolean;
460
+ isWeekend: boolean;
461
+ isDisabled: boolean;
462
+ isSelected: boolean;
463
+ isRangeStart: boolean;
464
+ isRangeEnd: boolean;
465
+ /** Inside a complete range (endpoints included). */
466
+ isInRange: boolean;
467
+ }
468
+ /** One week row. */
469
+ interface MonthGridWeek {
470
+ id: string;
471
+ days: MonthGridDay[];
472
+ }
473
+ /** A weekday header cell (e.g. "Mon"). */
474
+ interface MonthGridWeekday {
475
+ date: Date;
476
+ label: string;
477
+ }
478
+ interface MonthGrid {
479
+ weeks: MonthGridWeek[];
480
+ weekdays: MonthGridWeekday[];
481
+ }
482
+ interface UseMonthGridOptions extends DateSelectionConstraints {
483
+ /** First day of the week. Sunday = 0 (default) … Saturday = 6. */
484
+ weekStartsOn?: WeekStartsOn;
485
+ /** Always return six week rows for a fixed-height grid. Default false. */
486
+ showSixWeeks?: boolean;
487
+ /** Reverse each week's day order (right-to-left). Default false. */
488
+ isRTL?: boolean;
489
+ /** Selected discrete days (single/multiple). */
490
+ selectedDates?: Date[];
491
+ /** Selected span. */
492
+ selectedRange?: DateRange;
493
+ /** A date-fns locale for the weekday labels. */
494
+ locale?: Locale;
495
+ }
496
+ /**
497
+ * Pure month-grid builder: the weeks and weekday headers for `month`, each day
498
+ * annotated with selection/disabled/today state. Use this when you need the
499
+ * data outside React; inside a component prefer {@link useMonthGrid}.
500
+ */
501
+ declare function buildMonthGrid(month: Date, options?: UseMonthGridOptions): MonthGrid;
502
+ /**
503
+ * Headless month-grid hook. Returns the weeks and weekday headers for `month`,
504
+ * each day annotated with selection/disabled/today state, so you can render a
505
+ * fully custom calendar without reimplementing the date maths.
506
+ *
507
+ * ```tsx
508
+ * const { weeks, weekdays } = useMonthGrid(month, { selectedRange: range });
509
+ * // map weekdays -> header cells, weeks -> rows, days -> your own <DayCell />
510
+ * ```
511
+ */
512
+ declare function useMonthGrid(month: Date, options?: UseMonthGridOptions): MonthGrid;
513
+ //#endregion
514
+ //#region src/utils/recurrence.d.ts
515
+ /**
516
+ * Materialise recurring events into concrete occurrences overlapping
517
+ * `[rangeStart, rangeEnd]`. Non-recurring events pass through untouched, so the
518
+ * result is ready to hand to `<Calendar events={...} />`. Each occurrence keeps
519
+ * the original event's duration and fields (minus `recurrence`).
520
+ */
521
+ declare function expandRecurringEvents<T>(events: CalendarEvent<T>[], rangeStart: Date, rangeEnd: Date): CalendarEvent<T>[];
522
+ //#endregion
523
+ //#region src/utils/timezone.d.ts
524
+ /**
525
+ * Reinterpret an instant as its wall-clock time in `timeZone`, returned as a
526
+ * device-local `Date` whose fields (hours, minutes, …) read back as that zone's
527
+ * clock. The calendar lays events out from `getHours()`/`getMinutes()`, so
528
+ * passing zoned dates makes it render in `timeZone` regardless of the device.
529
+ *
530
+ * DST-correct via `Intl` (available on modern React Native Hermes/JSC and the
531
+ * web). The result is for display/layout only; it no longer points at the
532
+ * original UTC instant, so don't round-trip it back to a real time.
533
+ */
534
+ declare function toZonedTime(date: Date, timeZone: string): Date;
535
+ /**
536
+ * Map every event's `start`/`end` through {@link toZonedTime} so the calendar
537
+ * displays them in `timeZone`. Other fields are preserved. Memoize the result
538
+ * (e.g. with `useMemo`) since it allocates new dates.
539
+ */
540
+ declare function eventsInTimeZone<T>(events: CalendarEvent<T>[], timeZone: string): CalendarEvent<T>[];
541
+ //#endregion
542
+ export { BusinessHours, CalendarColors, CalendarEvent, CalendarMode, CalendarSelection, CalendarSelectionProvider, DateRange, DateSelectionConstraints, DayBadgeKind, DaySelectionState, EventChipLayout, EventKeyExtractor, ICalendarEvent, MIN_BOX_HEIGHT_FOR_TIME, MonthEventCapacity, MonthGrid, MonthGridDay, MonthGridWeek, MonthGridWeekday, PositionedEvent, RangeBandKind, RecurrenceFrequency, RecurrenceRule, TimeGridMode, UseDateRangeOptions, UseMonthGridOptions, WeekStartsOn, bandRounding, buildMonthGrid, buildMonthWeeks, cellRangeFromDrag, closedHourBands, compareDayEvents, darkColors, dayBadgeKind, daySelectionState, eventAccessibilityLabel, eventChipLayout, eventDayKeys, eventTimeLabel, eventsInTimeZone, expandRecurringEvents, formatHour, getIsToday, getViewDays, getWeekDays, groupEventsByDay, isAllDayEvent, isDateSelectable, isRangeEndpoint, isSameCalendarDay, isTimeVisibleAtHeight, isWeekend, isWithinDateRange, layoutDayEvents, lightColors, minutesIntoDay, monthEventCapacity, monthVisibleCount, nextDateRange, rangeBandKind, resolveDraggedBounds, shiftMinutes, snapDeltaMinutes, titleEllipsizeMode, titleNumberOfLines, toZonedTime, useCalendarSelection, useDateRange, useMonthGrid, viewDayCount, weekDaysCount };