@super-calendar/core 2.0.0 → 2.1.3
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/README.md +56 -0
- package/dist/index.d.mts +52 -9
- package/dist/index.d.ts +52 -9
- package/dist/index.js +6 -0
- package/dist/index.mjs +6 -0
- package/package.json +3 -2
- package/src/index.ts +18 -4
- package/src/tokens.ts +3 -0
- package/src/types.ts +5 -0
- package/src/utils/dateRange.ts +34 -4
- package/src/utils/dates.ts +3 -0
- package/src/utils/layout.ts +2 -0
- package/src/utils/monthGrid.ts +16 -0
package/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# @super-calendar/core
|
|
2
|
+
|
|
3
|
+
[](https://jsr.io/@super-calendar/core) [](https://jsr.io/@super-calendar/core)
|
|
4
|
+
|
|
5
|
+
The render-agnostic core of [super-calendar](https://github.com/afonsojramos/react-native-super-calendar): date math, the selection model, event layout, the month-grid builder, the headless hooks, and the neutral theme tokens.
|
|
6
|
+
|
|
7
|
+
It imports nothing from React Native, react-dom, Reanimated, Gesture Handler, or Legend List, so it bundles into any renderer. Use it to drive your own UI, or pair it with [`@super-calendar/dom`](https://jsr.io/@super-calendar/dom) (react-dom) or [`@super-calendar/native`](https://jsr.io/@super-calendar/native) (React Native).
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
# Deno
|
|
13
|
+
deno add jsr:@super-calendar/core
|
|
14
|
+
|
|
15
|
+
# npm / pnpm / yarn / bun
|
|
16
|
+
npx jsr add @super-calendar/core
|
|
17
|
+
pnpm dlx jsr add @super-calendar/core
|
|
18
|
+
bunx jsr add @super-calendar/core
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Peer dependencies: `date-fns` (>=3) and `react` (>=19).
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
Build a fully custom month grid from the headless `useMonthGrid` hook. It returns the weeks, the weekday headers, and per-day state for you to render however you like.
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
import { useMonthGrid } from "@super-calendar/core";
|
|
29
|
+
|
|
30
|
+
function MyGrid({ month }: { month: Date }) {
|
|
31
|
+
const { weeks, weekdays } = useMonthGrid(month, { weekStartsOn: 1 });
|
|
32
|
+
|
|
33
|
+
return weeks.map((week) =>
|
|
34
|
+
week.days.map((day) => (
|
|
35
|
+
// day carries isToday / isSelected / isInRange / isDisabled / isCurrentMonth
|
|
36
|
+
<DayCell key={day.date.toISOString()} day={day} />
|
|
37
|
+
)),
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Manage single, multiple, or range selection with `useDateRange`:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import { useDateRange } from "@super-calendar/core";
|
|
46
|
+
|
|
47
|
+
const range = useDateRange({ mode: "range" });
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Documentation
|
|
51
|
+
|
|
52
|
+
See the [full documentation](https://super-calendar.afonsojramos.me) and the [API reference](https://super-calendar.afonsojramos.me/reference/api).
|
|
53
|
+
|
|
54
|
+
## License
|
|
55
|
+
|
|
56
|
+
MIT
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Locale } from "date-fns";
|
|
2
|
+
import { Dispatch, SetStateAction } from "react";
|
|
2
3
|
|
|
3
4
|
//#region src/types.d.ts
|
|
5
|
+
/** The view the calendar renders: a day-column grid (`day`, `3days`, `week`, `custom`), the `month` grid, or the `schedule` list. */
|
|
4
6
|
type CalendarMode = "day" | "3days" | "week" | "custom" | "month" | "schedule";
|
|
5
7
|
/** The time-grid modes (day-column views, excluding month and schedule). */
|
|
6
8
|
type TimeGridMode = Exclude<CalendarMode, "month" | "schedule">;
|
|
@@ -11,8 +13,11 @@ type TimeGridMode = Exclude<CalendarMode, "month" | "schedule">;
|
|
|
11
13
|
* threaded through untouched via the `T` generic.
|
|
12
14
|
*/
|
|
13
15
|
interface ICalendarEvent {
|
|
16
|
+
/** When the event begins. */
|
|
14
17
|
start: Date;
|
|
18
|
+
/** When the event ends. */
|
|
15
19
|
end: Date;
|
|
20
|
+
/** Display label, shown by the built-in default renderer. */
|
|
16
21
|
title?: string;
|
|
17
22
|
/**
|
|
18
23
|
* Force this event into the all-day lane (above the time grid) instead of the
|
|
@@ -33,6 +38,7 @@ interface ICalendarEvent {
|
|
|
33
38
|
type RecurrenceFrequency = "daily" | "weekly" | "monthly" | "yearly";
|
|
34
39
|
/** A simple, RRULE-inspired repeat rule expanded by `expandRecurringEvents`. */
|
|
35
40
|
interface RecurrenceRule {
|
|
41
|
+
/** How often the event repeats. */
|
|
36
42
|
freq: RecurrenceFrequency;
|
|
37
43
|
/** Repeat every N periods. Default 1. */
|
|
38
44
|
interval?: number;
|
|
@@ -68,6 +74,7 @@ type BusinessHours = (date: Date) => {
|
|
|
68
74
|
} | null;
|
|
69
75
|
//#endregion
|
|
70
76
|
//#region src/tokens.d.ts
|
|
77
|
+
/** The shared colour palette both renderers derive their themes from. */
|
|
71
78
|
interface CalendarColors {
|
|
72
79
|
/** Hour lines, day separators and month-cell borders. */
|
|
73
80
|
gridLine: string;
|
|
@@ -100,7 +107,9 @@ interface CalendarColors {
|
|
|
100
107
|
/** Default event chip text. */
|
|
101
108
|
eventText: string;
|
|
102
109
|
}
|
|
110
|
+
/** The default light-theme colour palette. */
|
|
103
111
|
declare const lightColors: CalendarColors;
|
|
112
|
+
/** The default dark-theme colour palette. */
|
|
104
113
|
declare const darkColors: CalendarColors;
|
|
105
114
|
//#endregion
|
|
106
115
|
//#region src/presentation.d.ts
|
|
@@ -130,7 +139,9 @@ declare function dayBadgeKind(day: {
|
|
|
130
139
|
//#region src/utils/dateRange.d.ts
|
|
131
140
|
/** A selected span. `end` is `null` while only the first endpoint has been picked. */
|
|
132
141
|
interface DateRange {
|
|
142
|
+
/** The first endpoint. */
|
|
133
143
|
start: Date;
|
|
144
|
+
/** The second endpoint, or `null` while only the first has been picked. */
|
|
134
145
|
end: Date | null;
|
|
135
146
|
}
|
|
136
147
|
/** Limits applied before a date can be selected. */
|
|
@@ -166,7 +177,9 @@ interface DaySelectionState {
|
|
|
166
177
|
isSelected: boolean;
|
|
167
178
|
/** Inside a complete range, endpoints included (and not disabled). */
|
|
168
179
|
isInRange: boolean;
|
|
180
|
+
/** The range's start endpoint (and not disabled). */
|
|
169
181
|
isRangeStart: boolean;
|
|
182
|
+
/** The range's end endpoint (and not disabled). */
|
|
170
183
|
isRangeEnd: boolean;
|
|
171
184
|
}
|
|
172
185
|
/**
|
|
@@ -184,7 +197,9 @@ declare function daySelectionState(date: Date, selection: {
|
|
|
184
197
|
* cached/virtualized day cells still repaint when any of it changes.
|
|
185
198
|
*/
|
|
186
199
|
interface CalendarSelection extends DateSelectionConstraints {
|
|
200
|
+
/** Selected discrete days (single or multiple). */
|
|
187
201
|
selectedDates?: Date[];
|
|
202
|
+
/** Selected span. */
|
|
188
203
|
selectedRange?: DateRange;
|
|
189
204
|
}
|
|
190
205
|
/**
|
|
@@ -193,12 +208,26 @@ interface CalendarSelection extends DateSelectionConstraints {
|
|
|
193
208
|
* the virtualized list has cached (and so won't re-render) their page.
|
|
194
209
|
*/
|
|
195
210
|
declare const CalendarSelectionProvider: import("react").Provider<CalendarSelection>;
|
|
211
|
+
/** Reads the active selection provided by {@link CalendarSelectionProvider}. */
|
|
196
212
|
declare const useCalendarSelection: () => CalendarSelection;
|
|
197
213
|
/** Options for {@link useDateRange}. */
|
|
198
214
|
interface UseDateRangeOptions extends DateSelectionConstraints {
|
|
199
215
|
/** Pre-select a range on mount. */
|
|
200
216
|
initialRange?: DateRange | null;
|
|
201
217
|
}
|
|
218
|
+
/** The state and handlers returned by {@link useDateRange}. */
|
|
219
|
+
interface UseDateRangeResult {
|
|
220
|
+
/** The current selection; `null` until the first endpoint is picked. */
|
|
221
|
+
range: DateRange | null;
|
|
222
|
+
/** Wire to `onPressDay`: advances the range (start, then end, then restarts). */
|
|
223
|
+
onPressDate: (date: Date) => void;
|
|
224
|
+
/** Set both endpoints at once (ordered); ignored if either isn't selectable. */
|
|
225
|
+
selectRange: (a: Date, b: Date) => void;
|
|
226
|
+
/** Clear the selection. */
|
|
227
|
+
reset: () => void;
|
|
228
|
+
/** The raw state setter, for full control. */
|
|
229
|
+
setRange: Dispatch<SetStateAction<DateRange | null>>;
|
|
230
|
+
}
|
|
202
231
|
/**
|
|
203
232
|
* Controlled-ish range selection state for the month view. Returns the current
|
|
204
233
|
* `range` plus an `onPressDate` handler to wire to `Calendar`'s `onPressDay`, a
|
|
@@ -209,13 +238,7 @@ interface UseDateRangeOptions extends DateSelectionConstraints {
|
|
|
209
238
|
* <Calendar mode="month" selectedRange={range ?? undefined} onPressDay={onPressDate} … />
|
|
210
239
|
* ```
|
|
211
240
|
*/
|
|
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
|
-
};
|
|
241
|
+
declare function useDateRange(options?: UseDateRangeOptions): UseDateRangeResult;
|
|
219
242
|
//#endregion
|
|
220
243
|
//#region src/utils/dates.d.ts
|
|
221
244
|
/** The seven dates of the week containing `date`, starting on `weekStartsOn`. */
|
|
@@ -247,8 +270,11 @@ declare const buildMonthWeeks: (month: Date, weekStartsOn: WeekStartsOn, {
|
|
|
247
270
|
showSixWeeks?: boolean;
|
|
248
271
|
isRTL?: boolean;
|
|
249
272
|
}) => Date[][];
|
|
273
|
+
/** True when `date` is a Saturday or Sunday. */
|
|
250
274
|
declare const isWeekend: (date: Date) => boolean;
|
|
275
|
+
/** True when `date` falls on the current calendar day. */
|
|
251
276
|
declare const getIsToday: (date: Date) => boolean;
|
|
277
|
+
/** True when `a` and `b` are the same calendar day (ignoring the time of day). */
|
|
252
278
|
declare const isSameCalendarDay: (a: Date, b: Date) => boolean;
|
|
253
279
|
/** Minutes elapsed since midnight (0–1439). */
|
|
254
280
|
declare const minutesIntoDay: (date: Date) => number;
|
|
@@ -393,8 +419,9 @@ declare function monthEventCapacity(availableHeightPx: number, chipRowHeightPx:
|
|
|
393
419
|
declare function monthVisibleCount(total: number, capacity: MonthEventCapacity): number;
|
|
394
420
|
//#endregion
|
|
395
421
|
//#region src/utils/layout.d.ts
|
|
422
|
+
/** An event placed on a single day's time grid by {@link layoutDayEvents}, with its vertical span and overlap column. */
|
|
396
423
|
type PositionedEvent<T> = {
|
|
397
|
-
event: CalendarEvent<T>; /** Hours from midnight to the event's segment start on this day (fractional). */
|
|
424
|
+
/** The source event for this segment. */event: CalendarEvent<T>; /** Hours from midnight to the event's segment start on this day (fractional). */
|
|
398
425
|
startHours: number; /** Segment duration in hours on this day (clamped to a small minimum). */
|
|
399
426
|
durationHours: number; /** Zero-based column index within its overlap cluster. */
|
|
400
427
|
column: number; /** Total columns in this event's overlap cluster. */
|
|
@@ -450,35 +477,51 @@ declare function closedHourBands(day: Date, businessHours: BusinessHours | undef
|
|
|
450
477
|
//#region src/utils/monthGrid.d.ts
|
|
451
478
|
/** A single day in the grid, with all the state a custom cell needs to render. */
|
|
452
479
|
interface MonthGridDay {
|
|
480
|
+
/** The day this cell represents. */
|
|
453
481
|
date: Date;
|
|
454
482
|
/** Stable `yyyy-MM-dd` id, handy as a React key. */
|
|
455
483
|
id: string;
|
|
456
484
|
/** Day-of-month, e.g. "1". */
|
|
457
485
|
label: string;
|
|
486
|
+
/** False for days bleeding in from the previous or next month. */
|
|
458
487
|
isCurrentMonth: boolean;
|
|
488
|
+
/** The day is today. */
|
|
459
489
|
isToday: boolean;
|
|
490
|
+
/** The day is a Saturday or Sunday. */
|
|
460
491
|
isWeekend: boolean;
|
|
492
|
+
/** The day fails the min/max/disabled constraints. */
|
|
461
493
|
isDisabled: boolean;
|
|
494
|
+
/** The day is a `selectedDates` day or a range endpoint (and not disabled). */
|
|
462
495
|
isSelected: boolean;
|
|
496
|
+
/** The day is the range's start endpoint. */
|
|
463
497
|
isRangeStart: boolean;
|
|
498
|
+
/** The day is the range's end endpoint. */
|
|
464
499
|
isRangeEnd: boolean;
|
|
465
500
|
/** Inside a complete range (endpoints included). */
|
|
466
501
|
isInRange: boolean;
|
|
467
502
|
}
|
|
468
503
|
/** One week row. */
|
|
469
504
|
interface MonthGridWeek {
|
|
505
|
+
/** Stable id for the week row, handy as a React key. */
|
|
470
506
|
id: string;
|
|
507
|
+
/** The seven days of the row, in display order. */
|
|
471
508
|
days: MonthGridDay[];
|
|
472
509
|
}
|
|
473
510
|
/** A weekday header cell (e.g. "Mon"). */
|
|
474
511
|
interface MonthGridWeekday {
|
|
512
|
+
/** A representative date for the column, used to derive the label. */
|
|
475
513
|
date: Date;
|
|
514
|
+
/** The short weekday name (e.g. "Mon"), localised via `locale`. */
|
|
476
515
|
label: string;
|
|
477
516
|
}
|
|
517
|
+
/** The full grid for one month: the week rows and the weekday header cells. */
|
|
478
518
|
interface MonthGrid {
|
|
519
|
+
/** The week rows, padded to whole weeks. */
|
|
479
520
|
weeks: MonthGridWeek[];
|
|
521
|
+
/** The weekday header cells, in display order. */
|
|
480
522
|
weekdays: MonthGridWeekday[];
|
|
481
523
|
}
|
|
524
|
+
/** Options for {@link buildMonthGrid} and {@link useMonthGrid}. */
|
|
482
525
|
interface UseMonthGridOptions extends DateSelectionConstraints {
|
|
483
526
|
/** First day of the week. Sunday = 0 (default) … Saturday = 6. */
|
|
484
527
|
weekStartsOn?: WeekStartsOn;
|
|
@@ -539,4 +582,4 @@ declare function toZonedTime(date: Date, timeZone: string): Date;
|
|
|
539
582
|
*/
|
|
540
583
|
declare function eventsInTimeZone<T>(events: CalendarEvent<T>[], timeZone: string): CalendarEvent<T>[];
|
|
541
584
|
//#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 };
|
|
585
|
+
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, UseDateRangeResult, 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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { Dispatch, SetStateAction } from "react";
|
|
1
2
|
import { Locale } from "date-fns";
|
|
2
3
|
|
|
3
4
|
//#region src/types.d.ts
|
|
5
|
+
/** The view the calendar renders: a day-column grid (`day`, `3days`, `week`, `custom`), the `month` grid, or the `schedule` list. */
|
|
4
6
|
type CalendarMode = "day" | "3days" | "week" | "custom" | "month" | "schedule";
|
|
5
7
|
/** The time-grid modes (day-column views, excluding month and schedule). */
|
|
6
8
|
type TimeGridMode = Exclude<CalendarMode, "month" | "schedule">;
|
|
@@ -11,8 +13,11 @@ type TimeGridMode = Exclude<CalendarMode, "month" | "schedule">;
|
|
|
11
13
|
* threaded through untouched via the `T` generic.
|
|
12
14
|
*/
|
|
13
15
|
interface ICalendarEvent {
|
|
16
|
+
/** When the event begins. */
|
|
14
17
|
start: Date;
|
|
18
|
+
/** When the event ends. */
|
|
15
19
|
end: Date;
|
|
20
|
+
/** Display label, shown by the built-in default renderer. */
|
|
16
21
|
title?: string;
|
|
17
22
|
/**
|
|
18
23
|
* Force this event into the all-day lane (above the time grid) instead of the
|
|
@@ -33,6 +38,7 @@ interface ICalendarEvent {
|
|
|
33
38
|
type RecurrenceFrequency = "daily" | "weekly" | "monthly" | "yearly";
|
|
34
39
|
/** A simple, RRULE-inspired repeat rule expanded by `expandRecurringEvents`. */
|
|
35
40
|
interface RecurrenceRule {
|
|
41
|
+
/** How often the event repeats. */
|
|
36
42
|
freq: RecurrenceFrequency;
|
|
37
43
|
/** Repeat every N periods. Default 1. */
|
|
38
44
|
interval?: number;
|
|
@@ -68,6 +74,7 @@ type BusinessHours = (date: Date) => {
|
|
|
68
74
|
} | null;
|
|
69
75
|
//#endregion
|
|
70
76
|
//#region src/tokens.d.ts
|
|
77
|
+
/** The shared colour palette both renderers derive their themes from. */
|
|
71
78
|
interface CalendarColors {
|
|
72
79
|
/** Hour lines, day separators and month-cell borders. */
|
|
73
80
|
gridLine: string;
|
|
@@ -100,7 +107,9 @@ interface CalendarColors {
|
|
|
100
107
|
/** Default event chip text. */
|
|
101
108
|
eventText: string;
|
|
102
109
|
}
|
|
110
|
+
/** The default light-theme colour palette. */
|
|
103
111
|
declare const lightColors: CalendarColors;
|
|
112
|
+
/** The default dark-theme colour palette. */
|
|
104
113
|
declare const darkColors: CalendarColors;
|
|
105
114
|
//#endregion
|
|
106
115
|
//#region src/presentation.d.ts
|
|
@@ -130,7 +139,9 @@ declare function dayBadgeKind(day: {
|
|
|
130
139
|
//#region src/utils/dateRange.d.ts
|
|
131
140
|
/** A selected span. `end` is `null` while only the first endpoint has been picked. */
|
|
132
141
|
interface DateRange {
|
|
142
|
+
/** The first endpoint. */
|
|
133
143
|
start: Date;
|
|
144
|
+
/** The second endpoint, or `null` while only the first has been picked. */
|
|
134
145
|
end: Date | null;
|
|
135
146
|
}
|
|
136
147
|
/** Limits applied before a date can be selected. */
|
|
@@ -166,7 +177,9 @@ interface DaySelectionState {
|
|
|
166
177
|
isSelected: boolean;
|
|
167
178
|
/** Inside a complete range, endpoints included (and not disabled). */
|
|
168
179
|
isInRange: boolean;
|
|
180
|
+
/** The range's start endpoint (and not disabled). */
|
|
169
181
|
isRangeStart: boolean;
|
|
182
|
+
/** The range's end endpoint (and not disabled). */
|
|
170
183
|
isRangeEnd: boolean;
|
|
171
184
|
}
|
|
172
185
|
/**
|
|
@@ -184,7 +197,9 @@ declare function daySelectionState(date: Date, selection: {
|
|
|
184
197
|
* cached/virtualized day cells still repaint when any of it changes.
|
|
185
198
|
*/
|
|
186
199
|
interface CalendarSelection extends DateSelectionConstraints {
|
|
200
|
+
/** Selected discrete days (single or multiple). */
|
|
187
201
|
selectedDates?: Date[];
|
|
202
|
+
/** Selected span. */
|
|
188
203
|
selectedRange?: DateRange;
|
|
189
204
|
}
|
|
190
205
|
/**
|
|
@@ -193,12 +208,26 @@ interface CalendarSelection extends DateSelectionConstraints {
|
|
|
193
208
|
* the virtualized list has cached (and so won't re-render) their page.
|
|
194
209
|
*/
|
|
195
210
|
declare const CalendarSelectionProvider: import("react").Provider<CalendarSelection>;
|
|
211
|
+
/** Reads the active selection provided by {@link CalendarSelectionProvider}. */
|
|
196
212
|
declare const useCalendarSelection: () => CalendarSelection;
|
|
197
213
|
/** Options for {@link useDateRange}. */
|
|
198
214
|
interface UseDateRangeOptions extends DateSelectionConstraints {
|
|
199
215
|
/** Pre-select a range on mount. */
|
|
200
216
|
initialRange?: DateRange | null;
|
|
201
217
|
}
|
|
218
|
+
/** The state and handlers returned by {@link useDateRange}. */
|
|
219
|
+
interface UseDateRangeResult {
|
|
220
|
+
/** The current selection; `null` until the first endpoint is picked. */
|
|
221
|
+
range: DateRange | null;
|
|
222
|
+
/** Wire to `onPressDay`: advances the range (start, then end, then restarts). */
|
|
223
|
+
onPressDate: (date: Date) => void;
|
|
224
|
+
/** Set both endpoints at once (ordered); ignored if either isn't selectable. */
|
|
225
|
+
selectRange: (a: Date, b: Date) => void;
|
|
226
|
+
/** Clear the selection. */
|
|
227
|
+
reset: () => void;
|
|
228
|
+
/** The raw state setter, for full control. */
|
|
229
|
+
setRange: Dispatch<SetStateAction<DateRange | null>>;
|
|
230
|
+
}
|
|
202
231
|
/**
|
|
203
232
|
* Controlled-ish range selection state for the month view. Returns the current
|
|
204
233
|
* `range` plus an `onPressDate` handler to wire to `Calendar`'s `onPressDay`, a
|
|
@@ -209,13 +238,7 @@ interface UseDateRangeOptions extends DateSelectionConstraints {
|
|
|
209
238
|
* <Calendar mode="month" selectedRange={range ?? undefined} onPressDay={onPressDate} … />
|
|
210
239
|
* ```
|
|
211
240
|
*/
|
|
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
|
-
};
|
|
241
|
+
declare function useDateRange(options?: UseDateRangeOptions): UseDateRangeResult;
|
|
219
242
|
//#endregion
|
|
220
243
|
//#region src/utils/dates.d.ts
|
|
221
244
|
/** The seven dates of the week containing `date`, starting on `weekStartsOn`. */
|
|
@@ -247,8 +270,11 @@ declare const buildMonthWeeks: (month: Date, weekStartsOn: WeekStartsOn, {
|
|
|
247
270
|
showSixWeeks?: boolean;
|
|
248
271
|
isRTL?: boolean;
|
|
249
272
|
}) => Date[][];
|
|
273
|
+
/** True when `date` is a Saturday or Sunday. */
|
|
250
274
|
declare const isWeekend: (date: Date) => boolean;
|
|
275
|
+
/** True when `date` falls on the current calendar day. */
|
|
251
276
|
declare const getIsToday: (date: Date) => boolean;
|
|
277
|
+
/** True when `a` and `b` are the same calendar day (ignoring the time of day). */
|
|
252
278
|
declare const isSameCalendarDay: (a: Date, b: Date) => boolean;
|
|
253
279
|
/** Minutes elapsed since midnight (0–1439). */
|
|
254
280
|
declare const minutesIntoDay: (date: Date) => number;
|
|
@@ -393,8 +419,9 @@ declare function monthEventCapacity(availableHeightPx: number, chipRowHeightPx:
|
|
|
393
419
|
declare function monthVisibleCount(total: number, capacity: MonthEventCapacity): number;
|
|
394
420
|
//#endregion
|
|
395
421
|
//#region src/utils/layout.d.ts
|
|
422
|
+
/** An event placed on a single day's time grid by {@link layoutDayEvents}, with its vertical span and overlap column. */
|
|
396
423
|
type PositionedEvent<T> = {
|
|
397
|
-
event: CalendarEvent<T>; /** Hours from midnight to the event's segment start on this day (fractional). */
|
|
424
|
+
/** The source event for this segment. */event: CalendarEvent<T>; /** Hours from midnight to the event's segment start on this day (fractional). */
|
|
398
425
|
startHours: number; /** Segment duration in hours on this day (clamped to a small minimum). */
|
|
399
426
|
durationHours: number; /** Zero-based column index within its overlap cluster. */
|
|
400
427
|
column: number; /** Total columns in this event's overlap cluster. */
|
|
@@ -450,35 +477,51 @@ declare function closedHourBands(day: Date, businessHours: BusinessHours | undef
|
|
|
450
477
|
//#region src/utils/monthGrid.d.ts
|
|
451
478
|
/** A single day in the grid, with all the state a custom cell needs to render. */
|
|
452
479
|
interface MonthGridDay {
|
|
480
|
+
/** The day this cell represents. */
|
|
453
481
|
date: Date;
|
|
454
482
|
/** Stable `yyyy-MM-dd` id, handy as a React key. */
|
|
455
483
|
id: string;
|
|
456
484
|
/** Day-of-month, e.g. "1". */
|
|
457
485
|
label: string;
|
|
486
|
+
/** False for days bleeding in from the previous or next month. */
|
|
458
487
|
isCurrentMonth: boolean;
|
|
488
|
+
/** The day is today. */
|
|
459
489
|
isToday: boolean;
|
|
490
|
+
/** The day is a Saturday or Sunday. */
|
|
460
491
|
isWeekend: boolean;
|
|
492
|
+
/** The day fails the min/max/disabled constraints. */
|
|
461
493
|
isDisabled: boolean;
|
|
494
|
+
/** The day is a `selectedDates` day or a range endpoint (and not disabled). */
|
|
462
495
|
isSelected: boolean;
|
|
496
|
+
/** The day is the range's start endpoint. */
|
|
463
497
|
isRangeStart: boolean;
|
|
498
|
+
/** The day is the range's end endpoint. */
|
|
464
499
|
isRangeEnd: boolean;
|
|
465
500
|
/** Inside a complete range (endpoints included). */
|
|
466
501
|
isInRange: boolean;
|
|
467
502
|
}
|
|
468
503
|
/** One week row. */
|
|
469
504
|
interface MonthGridWeek {
|
|
505
|
+
/** Stable id for the week row, handy as a React key. */
|
|
470
506
|
id: string;
|
|
507
|
+
/** The seven days of the row, in display order. */
|
|
471
508
|
days: MonthGridDay[];
|
|
472
509
|
}
|
|
473
510
|
/** A weekday header cell (e.g. "Mon"). */
|
|
474
511
|
interface MonthGridWeekday {
|
|
512
|
+
/** A representative date for the column, used to derive the label. */
|
|
475
513
|
date: Date;
|
|
514
|
+
/** The short weekday name (e.g. "Mon"), localised via `locale`. */
|
|
476
515
|
label: string;
|
|
477
516
|
}
|
|
517
|
+
/** The full grid for one month: the week rows and the weekday header cells. */
|
|
478
518
|
interface MonthGrid {
|
|
519
|
+
/** The week rows, padded to whole weeks. */
|
|
479
520
|
weeks: MonthGridWeek[];
|
|
521
|
+
/** The weekday header cells, in display order. */
|
|
480
522
|
weekdays: MonthGridWeekday[];
|
|
481
523
|
}
|
|
524
|
+
/** Options for {@link buildMonthGrid} and {@link useMonthGrid}. */
|
|
482
525
|
interface UseMonthGridOptions extends DateSelectionConstraints {
|
|
483
526
|
/** First day of the week. Sunday = 0 (default) … Saturday = 6. */
|
|
484
527
|
weekStartsOn?: WeekStartsOn;
|
|
@@ -539,4 +582,4 @@ declare function toZonedTime(date: Date, timeZone: string): Date;
|
|
|
539
582
|
*/
|
|
540
583
|
declare function eventsInTimeZone<T>(events: CalendarEvent<T>[], timeZone: string): CalendarEvent<T>[];
|
|
541
584
|
//#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 };
|
|
585
|
+
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, UseDateRangeResult, 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 };
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
2
2
|
let date_fns = require("date-fns");
|
|
3
3
|
let react = require("react");
|
|
4
4
|
//#region src/tokens.ts
|
|
5
|
+
/** The default light-theme colour palette. */
|
|
5
6
|
const lightColors = {
|
|
6
7
|
gridLine: "#E2E4E9",
|
|
7
8
|
weekendBackground: "#F6F7F9",
|
|
@@ -19,6 +20,7 @@ const lightColors = {
|
|
|
19
20
|
eventBackground: "#DCE7FF",
|
|
20
21
|
eventText: "#1A1B1E"
|
|
21
22
|
};
|
|
23
|
+
/** The default dark-theme colour palette. */
|
|
22
24
|
const darkColors = {
|
|
23
25
|
gridLine: "#2A2E37",
|
|
24
26
|
weekendBackground: "#15171C",
|
|
@@ -130,11 +132,14 @@ const buildMonthWeeks = (month, weekStartsOn, { showSixWeeks = false, isRTL = fa
|
|
|
130
132
|
}
|
|
131
133
|
return weeks;
|
|
132
134
|
};
|
|
135
|
+
/** True when `date` is a Saturday or Sunday. */
|
|
133
136
|
const isWeekend = (date) => {
|
|
134
137
|
const day = date.getDay();
|
|
135
138
|
return day === 0 || day === 6;
|
|
136
139
|
};
|
|
140
|
+
/** True when `date` falls on the current calendar day. */
|
|
137
141
|
const getIsToday = (date) => (0, date_fns.isToday)(date);
|
|
142
|
+
/** True when `a` and `b` are the same calendar day (ignoring the time of day). */
|
|
138
143
|
const isSameCalendarDay = (a, b) => (0, date_fns.isSameDay)(a, b);
|
|
139
144
|
/** Minutes elapsed since midnight (0–1439). */
|
|
140
145
|
const minutesIntoDay = (date) => (0, date_fns.getHours)(date) * 60 + (0, date_fns.getMinutes)(date);
|
|
@@ -210,6 +215,7 @@ const CalendarSelectionContext = (0, react.createContext)({});
|
|
|
210
215
|
* the virtualized list has cached (and so won't re-render) their page.
|
|
211
216
|
*/
|
|
212
217
|
const CalendarSelectionProvider = CalendarSelectionContext.Provider;
|
|
218
|
+
/** Reads the active selection provided by {@link CalendarSelectionProvider}. */
|
|
213
219
|
const useCalendarSelection = () => (0, react.useContext)(CalendarSelectionContext);
|
|
214
220
|
/**
|
|
215
221
|
* Controlled-ish range selection state for the month view. Returns the current
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { addDays, addMonths, addWeeks, addYears, differenceInMinutes, eachDayOfInterval, endOfMonth, endOfWeek, format, getHours, getMinutes, isAfter, isBefore, isSameDay, isSameMonth, isToday, max, min, startOfDay, startOfMonth, startOfWeek } from "date-fns";
|
|
2
2
|
import { createContext, useCallback, useContext, useMemo, useState } from "react";
|
|
3
3
|
//#region src/tokens.ts
|
|
4
|
+
/** The default light-theme colour palette. */
|
|
4
5
|
const lightColors = {
|
|
5
6
|
gridLine: "#E2E4E9",
|
|
6
7
|
weekendBackground: "#F6F7F9",
|
|
@@ -18,6 +19,7 @@ const lightColors = {
|
|
|
18
19
|
eventBackground: "#DCE7FF",
|
|
19
20
|
eventText: "#1A1B1E"
|
|
20
21
|
};
|
|
22
|
+
/** The default dark-theme colour palette. */
|
|
21
23
|
const darkColors = {
|
|
22
24
|
gridLine: "#2A2E37",
|
|
23
25
|
weekendBackground: "#15171C",
|
|
@@ -129,11 +131,14 @@ const buildMonthWeeks = (month, weekStartsOn, { showSixWeeks = false, isRTL = fa
|
|
|
129
131
|
}
|
|
130
132
|
return weeks;
|
|
131
133
|
};
|
|
134
|
+
/** True when `date` is a Saturday or Sunday. */
|
|
132
135
|
const isWeekend = (date) => {
|
|
133
136
|
const day = date.getDay();
|
|
134
137
|
return day === 0 || day === 6;
|
|
135
138
|
};
|
|
139
|
+
/** True when `date` falls on the current calendar day. */
|
|
136
140
|
const getIsToday = (date) => isToday(date);
|
|
141
|
+
/** True when `a` and `b` are the same calendar day (ignoring the time of day). */
|
|
137
142
|
const isSameCalendarDay = (a, b) => isSameDay(a, b);
|
|
138
143
|
/** Minutes elapsed since midnight (0–1439). */
|
|
139
144
|
const minutesIntoDay = (date) => getHours(date) * 60 + getMinutes(date);
|
|
@@ -209,6 +214,7 @@ const CalendarSelectionContext = createContext({});
|
|
|
209
214
|
* the virtualized list has cached (and so won't re-render) their page.
|
|
210
215
|
*/
|
|
211
216
|
const CalendarSelectionProvider = CalendarSelectionContext.Provider;
|
|
217
|
+
/** Reads the active selection provided by {@link CalendarSelectionProvider}. */
|
|
212
218
|
const useCalendarSelection = () => useContext(CalendarSelectionContext);
|
|
213
219
|
/**
|
|
214
220
|
* Controlled-ish range selection state for the month view. Returns the current
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@super-calendar/core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.3",
|
|
4
4
|
"description": "Render-agnostic core for super-calendar: date math, selection model, event layout, the month-grid builder, headless hooks, and neutral theme tokens. No renderer.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"calendar",
|
|
@@ -9,10 +9,11 @@
|
|
|
9
9
|
"headless",
|
|
10
10
|
"react"
|
|
11
11
|
],
|
|
12
|
+
"homepage": "https://super-calendar.afonsojramos.me",
|
|
12
13
|
"license": "MIT",
|
|
13
14
|
"repository": {
|
|
14
15
|
"type": "git",
|
|
15
|
-
"url": "git+https://github.com/afonsojramos/
|
|
16
|
+
"url": "git+https://github.com/afonsojramos/super-calendar.git",
|
|
16
17
|
"directory": "packages/core"
|
|
17
18
|
},
|
|
18
19
|
"source": "./src/index.ts",
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The render-agnostic core of super-calendar. Date math, the selection model,
|
|
3
|
+
* event layout, the month-grid builder, the headless hooks, and the neutral
|
|
4
|
+
* theme tokens.
|
|
5
|
+
*
|
|
6
|
+
* Imports nothing from React Native, react-dom, Reanimated, Gesture Handler, or
|
|
7
|
+
* Legend List, so it bundles into any renderer. Pair it with `@super-calendar/dom`
|
|
8
|
+
* or `@super-calendar/native`, or drive your own UI from the headless hooks.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { useMonthGrid, buildMonthWeeks } from "@super-calendar/core";
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* @see https://super-calendar.afonsojramos.me
|
|
16
|
+
*
|
|
17
|
+
* @module
|
|
18
|
+
*/
|
|
5
19
|
export * from "./types";
|
|
6
20
|
export * from "./tokens";
|
|
7
21
|
export * from "./presentation";
|
package/src/tokens.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// platform types. Renderer-specific metrics (cell / badge / band sizes) stay in
|
|
4
4
|
// each renderer's theme, since they legitimately differ between the two.
|
|
5
5
|
|
|
6
|
+
/** The shared colour palette both renderers derive their themes from. */
|
|
6
7
|
export interface CalendarColors {
|
|
7
8
|
/** Hour lines, day separators and month-cell borders. */
|
|
8
9
|
gridLine: string;
|
|
@@ -36,6 +37,7 @@ export interface CalendarColors {
|
|
|
36
37
|
eventText: string;
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
/** The default light-theme colour palette. */
|
|
39
41
|
export const lightColors: CalendarColors = {
|
|
40
42
|
gridLine: "#E2E4E9",
|
|
41
43
|
weekendBackground: "#F6F7F9",
|
|
@@ -54,6 +56,7 @@ export const lightColors: CalendarColors = {
|
|
|
54
56
|
eventText: "#1A1B1E",
|
|
55
57
|
};
|
|
56
58
|
|
|
59
|
+
/** The default dark-theme colour palette. */
|
|
57
60
|
export const darkColors: CalendarColors = {
|
|
58
61
|
gridLine: "#2A2E37",
|
|
59
62
|
weekendBackground: "#15171C",
|
package/src/types.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** The view the calendar renders: a day-column grid (`day`, `3days`, `week`, `custom`), the `month` grid, or the `schedule` list. */
|
|
1
2
|
export type CalendarMode = "day" | "3days" | "week" | "custom" | "month" | "schedule";
|
|
2
3
|
|
|
3
4
|
/** The time-grid modes (day-column views, excluding month and schedule). */
|
|
@@ -10,8 +11,11 @@ export type TimeGridMode = Exclude<CalendarMode, "month" | "schedule">;
|
|
|
10
11
|
* threaded through untouched via the `T` generic.
|
|
11
12
|
*/
|
|
12
13
|
export interface ICalendarEvent {
|
|
14
|
+
/** When the event begins. */
|
|
13
15
|
start: Date;
|
|
16
|
+
/** When the event ends. */
|
|
14
17
|
end: Date;
|
|
18
|
+
/** Display label, shown by the built-in default renderer. */
|
|
15
19
|
title?: string;
|
|
16
20
|
/**
|
|
17
21
|
* Force this event into the all-day lane (above the time grid) instead of the
|
|
@@ -34,6 +38,7 @@ export type RecurrenceFrequency = "daily" | "weekly" | "monthly" | "yearly";
|
|
|
34
38
|
|
|
35
39
|
/** A simple, RRULE-inspired repeat rule expanded by `expandRecurringEvents`. */
|
|
36
40
|
export interface RecurrenceRule {
|
|
41
|
+
/** How often the event repeats. */
|
|
37
42
|
freq: RecurrenceFrequency;
|
|
38
43
|
/** Repeat every N periods. Default 1. */
|
|
39
44
|
interval?: number;
|
package/src/utils/dateRange.ts
CHANGED
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
import { isAfter, isBefore, startOfDay } from "date-fns";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
type Context,
|
|
4
|
+
createContext,
|
|
5
|
+
type Dispatch,
|
|
6
|
+
type SetStateAction,
|
|
7
|
+
useCallback,
|
|
8
|
+
useContext,
|
|
9
|
+
useMemo,
|
|
10
|
+
useState,
|
|
11
|
+
} from "react";
|
|
3
12
|
import { isSameCalendarDay } from "./dates";
|
|
4
13
|
|
|
5
14
|
/** A selected span. `end` is `null` while only the first endpoint has been picked. */
|
|
6
15
|
export interface DateRange {
|
|
16
|
+
/** The first endpoint. */
|
|
7
17
|
start: Date;
|
|
18
|
+
/** The second endpoint, or `null` while only the first has been picked. */
|
|
8
19
|
end: Date | null;
|
|
9
20
|
}
|
|
10
21
|
|
|
@@ -72,7 +83,9 @@ export interface DaySelectionState {
|
|
|
72
83
|
isSelected: boolean;
|
|
73
84
|
/** Inside a complete range, endpoints included (and not disabled). */
|
|
74
85
|
isInRange: boolean;
|
|
86
|
+
/** The range's start endpoint (and not disabled). */
|
|
75
87
|
isRangeStart: boolean;
|
|
88
|
+
/** The range's end endpoint (and not disabled). */
|
|
76
89
|
isRangeEnd: boolean;
|
|
77
90
|
}
|
|
78
91
|
|
|
@@ -106,11 +119,13 @@ export function daySelectionState(
|
|
|
106
119
|
* cached/virtualized day cells still repaint when any of it changes.
|
|
107
120
|
*/
|
|
108
121
|
export interface CalendarSelection extends DateSelectionConstraints {
|
|
122
|
+
/** Selected discrete days (single or multiple). */
|
|
109
123
|
selectedDates?: Date[];
|
|
124
|
+
/** Selected span. */
|
|
110
125
|
selectedRange?: DateRange;
|
|
111
126
|
}
|
|
112
127
|
|
|
113
|
-
const CalendarSelectionContext = createContext<CalendarSelection>({});
|
|
128
|
+
const CalendarSelectionContext: Context<CalendarSelection> = createContext<CalendarSelection>({});
|
|
114
129
|
|
|
115
130
|
/**
|
|
116
131
|
* Provides the active selection to the month grid. Day cells read it via
|
|
@@ -119,7 +134,8 @@ const CalendarSelectionContext = createContext<CalendarSelection>({});
|
|
|
119
134
|
*/
|
|
120
135
|
export const CalendarSelectionProvider = CalendarSelectionContext.Provider;
|
|
121
136
|
|
|
122
|
-
|
|
137
|
+
/** Reads the active selection provided by {@link CalendarSelectionProvider}. */
|
|
138
|
+
export const useCalendarSelection = (): CalendarSelection => useContext(CalendarSelectionContext);
|
|
123
139
|
|
|
124
140
|
/** Options for {@link useDateRange}. */
|
|
125
141
|
export interface UseDateRangeOptions extends DateSelectionConstraints {
|
|
@@ -127,6 +143,20 @@ export interface UseDateRangeOptions extends DateSelectionConstraints {
|
|
|
127
143
|
initialRange?: DateRange | null;
|
|
128
144
|
}
|
|
129
145
|
|
|
146
|
+
/** The state and handlers returned by {@link useDateRange}. */
|
|
147
|
+
export interface UseDateRangeResult {
|
|
148
|
+
/** The current selection; `null` until the first endpoint is picked. */
|
|
149
|
+
range: DateRange | null;
|
|
150
|
+
/** Wire to `onPressDay`: advances the range (start, then end, then restarts). */
|
|
151
|
+
onPressDate: (date: Date) => void;
|
|
152
|
+
/** Set both endpoints at once (ordered); ignored if either isn't selectable. */
|
|
153
|
+
selectRange: (a: Date, b: Date) => void;
|
|
154
|
+
/** Clear the selection. */
|
|
155
|
+
reset: () => void;
|
|
156
|
+
/** The raw state setter, for full control. */
|
|
157
|
+
setRange: Dispatch<SetStateAction<DateRange | null>>;
|
|
158
|
+
}
|
|
159
|
+
|
|
130
160
|
/**
|
|
131
161
|
* Controlled-ish range selection state for the month view. Returns the current
|
|
132
162
|
* `range` plus an `onPressDate` handler to wire to `Calendar`'s `onPressDay`, a
|
|
@@ -137,7 +167,7 @@ export interface UseDateRangeOptions extends DateSelectionConstraints {
|
|
|
137
167
|
* <Calendar mode="month" selectedRange={range ?? undefined} onPressDay={onPressDate} … />
|
|
138
168
|
* ```
|
|
139
169
|
*/
|
|
140
|
-
export function useDateRange(options: UseDateRangeOptions = {}) {
|
|
170
|
+
export function useDateRange(options: UseDateRangeOptions = {}): UseDateRangeResult {
|
|
141
171
|
const { initialRange = null, minDate, maxDate, isDateDisabled } = options;
|
|
142
172
|
const [range, setRange] = useState<DateRange | null>(initialRange);
|
|
143
173
|
const constraints = useMemo<DateSelectionConstraints>(
|
package/src/utils/dates.ts
CHANGED
|
@@ -105,13 +105,16 @@ export const buildMonthWeeks = (
|
|
|
105
105
|
return weeks;
|
|
106
106
|
};
|
|
107
107
|
|
|
108
|
+
/** True when `date` is a Saturday or Sunday. */
|
|
108
109
|
export const isWeekend = (date: Date): boolean => {
|
|
109
110
|
const day = date.getDay();
|
|
110
111
|
return day === 0 || day === 6;
|
|
111
112
|
};
|
|
112
113
|
|
|
114
|
+
/** True when `date` falls on the current calendar day. */
|
|
113
115
|
export const getIsToday = (date: Date): boolean => isToday(date);
|
|
114
116
|
|
|
117
|
+
/** True when `a` and `b` are the same calendar day (ignoring the time of day). */
|
|
115
118
|
export const isSameCalendarDay = (a: Date, b: Date): boolean => isSameDay(a, b);
|
|
116
119
|
|
|
117
120
|
/** Minutes elapsed since midnight (0–1439). */
|
package/src/utils/layout.ts
CHANGED
|
@@ -6,7 +6,9 @@ const MINUTES_PER_HOUR = 60;
|
|
|
6
6
|
// span still occupies a sliver rather than collapsing to nothing.
|
|
7
7
|
const MIN_DURATION_HOURS = 0.25;
|
|
8
8
|
|
|
9
|
+
/** An event placed on a single day's time grid by {@link layoutDayEvents}, with its vertical span and overlap column. */
|
|
9
10
|
export type PositionedEvent<T> = {
|
|
11
|
+
/** The source event for this segment. */
|
|
10
12
|
event: CalendarEvent<T>;
|
|
11
13
|
/** Hours from midnight to the event's segment start on this day (fractional). */
|
|
12
14
|
startHours: number;
|
package/src/utils/monthGrid.ts
CHANGED
|
@@ -6,17 +6,25 @@ import { buildMonthWeeks, getIsToday, isWeekend } from "./dates";
|
|
|
6
6
|
|
|
7
7
|
/** A single day in the grid, with all the state a custom cell needs to render. */
|
|
8
8
|
export interface MonthGridDay {
|
|
9
|
+
/** The day this cell represents. */
|
|
9
10
|
date: Date;
|
|
10
11
|
/** Stable `yyyy-MM-dd` id, handy as a React key. */
|
|
11
12
|
id: string;
|
|
12
13
|
/** Day-of-month, e.g. "1". */
|
|
13
14
|
label: string;
|
|
15
|
+
/** False for days bleeding in from the previous or next month. */
|
|
14
16
|
isCurrentMonth: boolean;
|
|
17
|
+
/** The day is today. */
|
|
15
18
|
isToday: boolean;
|
|
19
|
+
/** The day is a Saturday or Sunday. */
|
|
16
20
|
isWeekend: boolean;
|
|
21
|
+
/** The day fails the min/max/disabled constraints. */
|
|
17
22
|
isDisabled: boolean;
|
|
23
|
+
/** The day is a `selectedDates` day or a range endpoint (and not disabled). */
|
|
18
24
|
isSelected: boolean;
|
|
25
|
+
/** The day is the range's start endpoint. */
|
|
19
26
|
isRangeStart: boolean;
|
|
27
|
+
/** The day is the range's end endpoint. */
|
|
20
28
|
isRangeEnd: boolean;
|
|
21
29
|
/** Inside a complete range (endpoints included). */
|
|
22
30
|
isInRange: boolean;
|
|
@@ -24,21 +32,29 @@ export interface MonthGridDay {
|
|
|
24
32
|
|
|
25
33
|
/** One week row. */
|
|
26
34
|
export interface MonthGridWeek {
|
|
35
|
+
/** Stable id for the week row, handy as a React key. */
|
|
27
36
|
id: string;
|
|
37
|
+
/** The seven days of the row, in display order. */
|
|
28
38
|
days: MonthGridDay[];
|
|
29
39
|
}
|
|
30
40
|
|
|
31
41
|
/** A weekday header cell (e.g. "Mon"). */
|
|
32
42
|
export interface MonthGridWeekday {
|
|
43
|
+
/** A representative date for the column, used to derive the label. */
|
|
33
44
|
date: Date;
|
|
45
|
+
/** The short weekday name (e.g. "Mon"), localised via `locale`. */
|
|
34
46
|
label: string;
|
|
35
47
|
}
|
|
36
48
|
|
|
49
|
+
/** The full grid for one month: the week rows and the weekday header cells. */
|
|
37
50
|
export interface MonthGrid {
|
|
51
|
+
/** The week rows, padded to whole weeks. */
|
|
38
52
|
weeks: MonthGridWeek[];
|
|
53
|
+
/** The weekday header cells, in display order. */
|
|
39
54
|
weekdays: MonthGridWeekday[];
|
|
40
55
|
}
|
|
41
56
|
|
|
57
|
+
/** Options for {@link buildMonthGrid} and {@link useMonthGrid}. */
|
|
42
58
|
export interface UseMonthGridOptions extends DateSelectionConstraints {
|
|
43
59
|
/** First day of the week. Sunday = 0 (default) … Saturday = 6. */
|
|
44
60
|
weekStartsOn?: WeekStartsOn;
|