@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 +21 -0
- package/dist/index.d.mts +542 -0
- package/dist/index.d.ts +542 -0
- package/dist/index.js +794 -0
- package/dist/index.mjs +747 -0
- package/package.json +54 -0
- package/src/index.ts +15 -0
- package/src/presentation.ts +39 -0
- package/src/tokens.ts +73 -0
- package/src/types.ts +70 -0
- package/src/utils/dateRange.ts +163 -0
- package/src/utils/dates.ts +118 -0
- package/src/utils/drag.ts +79 -0
- package/src/utils/eventDisplay.ts +180 -0
- package/src/utils/layout.ts +193 -0
- package/src/utils/monthGrid.ts +144 -0
- package/src/utils/recurrence.ts +98 -0
- package/src/utils/timezone.ts +57 -0
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.
|
package/dist/index.d.mts
ADDED
|
@@ -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 };
|