@super-calendar/dom 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/dist/index.mjs ADDED
@@ -0,0 +1,1319 @@
1
+ import { LegendList } from "@legendapp/list/react";
2
+ import { addDays, addMinutes, addMonths, endOfMonth, format, isSameDay, isSameMonth, startOfDay, startOfMonth } from "date-fns";
3
+ import { useEffect, useMemo, useRef, useState } from "react";
4
+ import { bandRounding, buildMonthGrid, buildMonthGrid as buildMonthGrid$1, cellRangeFromDrag, closedHourBands, compareDayEvents, darkColors, dayBadgeKind, daySelectionState, eventAccessibilityLabel, eventChipLayout, eventTimeLabel, formatHour, getIsToday, getViewDays, getViewDays as getViewDays$1, groupEventsByDay, isAllDayEvent, isAllDayEvent as isAllDayEvent$1, isDateSelectable, isRangeEndpoint, isSameCalendarDay, isWithinDateRange, layoutDayEvents, layoutDayEvents as layoutDayEvents$1, lightColors, monthVisibleCount, nextDateRange, rangeBandKind, titleNumberOfLines, useDateRange, useMonthGrid } from "@super-calendar/core";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ //#region src/theme.ts
7
+ const METRICS = {
8
+ cellHeight: 48,
9
+ dayBadgeSize: 34,
10
+ rangeBandHeight: 32,
11
+ fontFamily: "-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif"
12
+ };
13
+ const defaultDomTheme = {
14
+ ...lightColors,
15
+ ...METRICS
16
+ };
17
+ const darkDomTheme = {
18
+ ...darkColors,
19
+ ...METRICS
20
+ };
21
+ /** Merge a partial override onto a base theme (defaults to {@link defaultDomTheme}). */
22
+ function mergeDomTheme(overrides, base = defaultDomTheme) {
23
+ return overrides ? {
24
+ ...base,
25
+ ...overrides
26
+ } : base;
27
+ }
28
+ //#endregion
29
+ //#region src/Agenda.tsx
30
+ function DefaultAgendaRow({ event, isAllDay, ampm = false, theme }) {
31
+ const time = eventTimeLabel({
32
+ mode: "schedule",
33
+ isAllDay,
34
+ start: event.start,
35
+ end: event.end,
36
+ ampm,
37
+ showTime: true
38
+ }) ?? "";
39
+ return /* @__PURE__ */ jsxs("div", {
40
+ style: {
41
+ padding: "6px 10px",
42
+ borderRadius: 8,
43
+ background: theme.eventBackground,
44
+ color: theme.eventText
45
+ },
46
+ children: [/* @__PURE__ */ jsx("div", {
47
+ style: {
48
+ fontWeight: 600,
49
+ fontSize: 14
50
+ },
51
+ children: event.title
52
+ }), time ? /* @__PURE__ */ jsx("div", {
53
+ style: {
54
+ fontSize: 13,
55
+ opacity: .75
56
+ },
57
+ children: time
58
+ }) : null]
59
+ });
60
+ }
61
+ /**
62
+ * A vertical, day-grouped list of events: the schedule view, with plain DOM
63
+ * elements. Events are sorted by start and grouped under a date header per day;
64
+ * the consumer controls which events (and therefore which dates) are shown. The
65
+ * react-dom counterpart of the React Native `Agenda`.
66
+ */
67
+ function Agenda({ events, locale, ampm = false, activeDate, theme: themeOverrides, height = 480, renderEvent, onPressEvent, onPressDay, className, style }) {
68
+ const theme = useMemo(() => mergeDomTheme(themeOverrides), [themeOverrides]);
69
+ const Renderer = renderEvent;
70
+ const rows = useMemo(() => {
71
+ const sorted = [...events].sort((a, b) => a.start.getTime() - b.start.getTime());
72
+ const out = [];
73
+ let currentDay = null;
74
+ sorted.forEach((event, index) => {
75
+ if (!currentDay || !isSameDay(event.start, currentDay)) {
76
+ currentDay = startOfDay(event.start);
77
+ out.push({
78
+ kind: "header",
79
+ date: currentDay,
80
+ key: `h-${currentDay.toISOString()}`
81
+ });
82
+ }
83
+ out.push({
84
+ kind: "event",
85
+ event,
86
+ key: `e-${event.start.toISOString()}-${index}`
87
+ });
88
+ });
89
+ return out;
90
+ }, [events]);
91
+ return /* @__PURE__ */ jsxs("div", {
92
+ className,
93
+ style: {
94
+ fontFamily: theme.fontFamily,
95
+ color: theme.text,
96
+ ...style
97
+ },
98
+ children: [/* @__PURE__ */ jsx(LegendList, {
99
+ data: rows,
100
+ keyExtractor: (row) => row.key,
101
+ recycleItems: false,
102
+ estimatedItemSize: 44,
103
+ style: {
104
+ height,
105
+ overflowY: "auto"
106
+ },
107
+ renderItem: ({ item }) => {
108
+ if (item.kind === "header") {
109
+ const highlighted = activeDate ? isSameDay(item.date, activeDate) : getIsToday(item.date);
110
+ const label = format(item.date, "EEEE, d LLLL", locale ? { locale } : void 0);
111
+ const color = highlighted ? theme.todayBackground : theme.textMuted;
112
+ return onPressDay ? /* @__PURE__ */ jsx("button", {
113
+ type: "button",
114
+ onClick: () => onPressDay(item.date),
115
+ style: {
116
+ ...headerStyle,
117
+ color,
118
+ border: "none",
119
+ background: "transparent",
120
+ cursor: "pointer",
121
+ font: "inherit",
122
+ textAlign: "left"
123
+ },
124
+ children: label
125
+ }) : /* @__PURE__ */ jsx("div", {
126
+ style: {
127
+ ...headerStyle,
128
+ color
129
+ },
130
+ children: label
131
+ });
132
+ }
133
+ const event = item.event;
134
+ const isAllDay = isAllDayEvent$1(event);
135
+ const onPress = () => onPressEvent?.(event);
136
+ const args = {
137
+ event,
138
+ mode: "schedule",
139
+ isAllDay,
140
+ ampm,
141
+ onPress
142
+ };
143
+ return /* @__PURE__ */ jsx("button", {
144
+ type: "button",
145
+ onClick: onPress,
146
+ style: {
147
+ display: "block",
148
+ width: "100%",
149
+ textAlign: "left",
150
+ border: "none",
151
+ background: "transparent",
152
+ cursor: "pointer",
153
+ font: "inherit",
154
+ padding: "2px 12px"
155
+ },
156
+ children: Renderer ? /* @__PURE__ */ jsx(Renderer, { ...args }) : /* @__PURE__ */ jsx(DefaultAgendaRow, {
157
+ ...args,
158
+ theme
159
+ })
160
+ });
161
+ }
162
+ }), rows.length === 0 ? /* @__PURE__ */ jsx("div", {
163
+ style: {
164
+ padding: "16px 12px",
165
+ color: theme.textMuted,
166
+ fontSize: 14
167
+ },
168
+ children: "No events"
169
+ }) : null]
170
+ });
171
+ }
172
+ const headerStyle = {
173
+ display: "block",
174
+ width: "100%",
175
+ padding: "12px 12px 4px",
176
+ fontSize: 13,
177
+ fontWeight: 600
178
+ };
179
+ //#endregion
180
+ //#region src/MonthView.tsx
181
+ const DATE_ROW = 24;
182
+ const CHIP_HEIGHT = 18;
183
+ const CHIP_GAP = 2;
184
+ const CELL_PAD = 4;
185
+ function dayCellStyle(day, theme) {
186
+ return {
187
+ position: "relative",
188
+ height: theme.cellHeight,
189
+ border: "none",
190
+ background: day.isWeekend && !day.isInRange ? theme.weekendBackground : "transparent",
191
+ font: "inherit",
192
+ fontSize: 15,
193
+ color: day.isDisabled ? theme.textDisabled : theme.text,
194
+ cursor: day.isDisabled ? "default" : "pointer",
195
+ display: "flex",
196
+ alignItems: "center",
197
+ justifyContent: "center",
198
+ padding: 0,
199
+ WebkitTapHighlightColor: "transparent"
200
+ };
201
+ }
202
+ /**
203
+ * The range band behind a day, rendered as its own layer so it can be a centered
204
+ * rounded strip (the default) or fill the whole cell (`fillCell`). Returns null
205
+ * for days with no band. Endpoints get the leading/trailing pill rounding.
206
+ */
207
+ function rangeBandStyle(day, theme, fillCell) {
208
+ const kind = rangeBandKind(day, fillCell);
209
+ if (kind === "none") return null;
210
+ const fill = kind === "fill";
211
+ const inset = fill ? 0 : Math.max(0, (theme.cellHeight - theme.rangeBandHeight) / 2);
212
+ const radius = fill ? 0 : theme.rangeBandHeight / 2;
213
+ const rounding = bandRounding(kind);
214
+ const cap = `calc(50% - ${theme.dayBadgeSize / 2}px)`;
215
+ const style = {
216
+ position: "absolute",
217
+ left: rounding.start ? cap : 0,
218
+ right: rounding.end ? cap : 0,
219
+ top: inset,
220
+ bottom: inset,
221
+ background: theme.rangeBackground,
222
+ zIndex: 0
223
+ };
224
+ if (rounding.start) {
225
+ style.borderTopLeftRadius = radius;
226
+ style.borderBottomLeftRadius = radius;
227
+ }
228
+ if (rounding.end) {
229
+ style.borderTopRightRadius = radius;
230
+ style.borderBottomRightRadius = radius;
231
+ }
232
+ return style;
233
+ }
234
+ function badgeStyle(day, theme, hovered) {
235
+ const badge = dayBadgeKind(day, day.isToday);
236
+ const filled = badge !== "none";
237
+ const background = filled ? badge === "today" ? theme.todayBackground : theme.selectedBackground : hovered ? theme.hoverBackground : "transparent";
238
+ return {
239
+ position: "relative",
240
+ zIndex: 1,
241
+ width: theme.dayBadgeSize,
242
+ height: theme.dayBadgeSize,
243
+ borderRadius: "50%",
244
+ display: "flex",
245
+ alignItems: "center",
246
+ justifyContent: "center",
247
+ background,
248
+ color: filled ? day.isToday ? theme.todayText : theme.selectedText : "inherit"
249
+ };
250
+ }
251
+ function eventCellStyle(day, theme, height) {
252
+ return {
253
+ position: "relative",
254
+ minHeight: height,
255
+ minWidth: 0,
256
+ border: "none",
257
+ borderTop: `1px solid ${theme.gridLine}`,
258
+ background: day.isWeekend && !day.isInRange ? theme.weekendBackground : "transparent",
259
+ color: day.isDisabled ? theme.textDisabled : theme.text,
260
+ cursor: day.isDisabled ? "default" : "pointer",
261
+ display: "flex",
262
+ flexDirection: "column",
263
+ gap: CHIP_GAP,
264
+ padding: CELL_PAD,
265
+ boxSizing: "border-box",
266
+ textAlign: "left",
267
+ WebkitTapHighlightColor: "transparent"
268
+ };
269
+ }
270
+ function compactBadgeStyle(day, theme) {
271
+ const badge = dayBadgeKind(day, day.isToday);
272
+ const filled = badge !== "none";
273
+ return {
274
+ position: "relative",
275
+ zIndex: 1,
276
+ alignSelf: "flex-end",
277
+ width: DATE_ROW - 2,
278
+ height: DATE_ROW - 2,
279
+ borderRadius: "50%",
280
+ display: "flex",
281
+ alignItems: "center",
282
+ justifyContent: "center",
283
+ fontSize: 13,
284
+ background: filled ? badge === "today" ? theme.todayBackground : theme.selectedBackground : "transparent",
285
+ color: filled ? day.isToday ? theme.todayText : theme.selectedText : "inherit"
286
+ };
287
+ }
288
+ const chipButtonStyle = {
289
+ position: "relative",
290
+ zIndex: 1,
291
+ border: "none",
292
+ padding: 0,
293
+ margin: 0,
294
+ background: "transparent",
295
+ cursor: "pointer",
296
+ textAlign: "left",
297
+ fontFamily: "inherit"
298
+ };
299
+ function chipStyle(theme) {
300
+ return {
301
+ display: "block",
302
+ height: CHIP_HEIGHT,
303
+ lineHeight: `${CHIP_HEIGHT}px`,
304
+ padding: "0 6px",
305
+ borderRadius: 4,
306
+ background: theme.eventBackground,
307
+ color: theme.eventText,
308
+ fontSize: 11,
309
+ fontWeight: 600,
310
+ whiteSpace: "nowrap",
311
+ overflow: "hidden",
312
+ textOverflow: "ellipsis"
313
+ };
314
+ }
315
+ function moreButtonStyle(theme) {
316
+ return {
317
+ position: "relative",
318
+ zIndex: 1,
319
+ border: "none",
320
+ background: "transparent",
321
+ cursor: "pointer",
322
+ textAlign: "left",
323
+ padding: "0 6px",
324
+ height: CHIP_HEIGHT,
325
+ lineHeight: `${CHIP_HEIGHT}px`,
326
+ fontSize: 11,
327
+ fontWeight: 600,
328
+ fontFamily: "inherit",
329
+ color: theme.textMuted
330
+ };
331
+ }
332
+ /** A single static month grid, rendered with plain DOM elements. */
333
+ function MonthView({ date, weekStartsOn = 0, events, eventsByDay: eventsByDayProp, renderEvent, maxVisibleEventCount = 3, moreLabel = "{moreCount} More", onPressEvent, onPressMore, selectedRange, selectedDates, showAdjacentMonths = true, fillCellOnSelection = false, showTitle = true, showWeekdays = true, locale, theme: themeOverrides, minDate, maxDate, isDateDisabled, onPressDay, keyboardDayNavigation = false, className, style }) {
334
+ const theme = useMemo(() => mergeDomTheme(themeOverrides), [themeOverrides]);
335
+ const eventsMode = events !== void 0;
336
+ const dayRoving = !eventsMode || keyboardDayNavigation;
337
+ const eventsByDay = useMemo(() => {
338
+ if (eventsByDayProp) return eventsByDayProp;
339
+ const map = groupEventsByDay(events ?? []);
340
+ for (const list of map.values()) list.sort(compareDayEvents);
341
+ return map;
342
+ }, [eventsByDayProp, events]);
343
+ const Chip = renderEvent;
344
+ const eventRowHeight = 32 + maxVisibleEventCount * 20;
345
+ const { weeks, weekdays } = useMemo(() => buildMonthGrid$1(date, {
346
+ weekStartsOn,
347
+ selectedRange,
348
+ selectedDates,
349
+ minDate,
350
+ maxDate,
351
+ isDateDisabled,
352
+ locale
353
+ }), [
354
+ date,
355
+ weekStartsOn,
356
+ selectedRange,
357
+ selectedDates,
358
+ minDate,
359
+ maxDate,
360
+ isDateDisabled,
361
+ locale
362
+ ]);
363
+ const initialFocus = useMemo(() => {
364
+ const inMonth = (d) => !!d && isSameMonth(d, date);
365
+ if (inMonth(selectedRange?.start)) return startOfDay(selectedRange.start);
366
+ const picked = selectedDates?.find(inMonth);
367
+ if (picked) return startOfDay(picked);
368
+ const today = /* @__PURE__ */ new Date();
369
+ return isSameMonth(today, date) ? startOfDay(today) : startOfMonth(date);
370
+ }, [
371
+ date,
372
+ selectedRange,
373
+ selectedDates
374
+ ]);
375
+ const [focusedDate, setFocusedDate] = useState(initialFocus);
376
+ const initialFocusRef = useRef(initialFocus);
377
+ initialFocusRef.current = initialFocus;
378
+ useEffect(() => setFocusedDate(initialFocusRef.current), [format(date, "yyyy-MM")]);
379
+ const [hoveredKey, setHoveredKey] = useState(null);
380
+ const gridRef = useRef(null);
381
+ const focusKey = format(focusedDate, "yyyy-MM-dd");
382
+ const onKeyDown = (e) => {
383
+ let next = null;
384
+ if (e.key === "ArrowLeft") next = addDays(focusedDate, -1);
385
+ else if (e.key === "ArrowRight") next = addDays(focusedDate, 1);
386
+ else if (e.key === "ArrowUp") next = addDays(focusedDate, -7);
387
+ else if (e.key === "ArrowDown") next = addDays(focusedDate, 7);
388
+ else if (e.key === "Home") next = startOfMonth(date);
389
+ else if (e.key === "End") next = endOfMonth(date);
390
+ else return;
391
+ e.preventDefault();
392
+ if (!isSameMonth(next, date)) return;
393
+ setFocusedDate(next);
394
+ const key = format(next, "yyyy-MM-dd");
395
+ gridRef.current?.querySelector(`[data-day="${key}"]`)?.focus();
396
+ };
397
+ return /* @__PURE__ */ jsxs("div", {
398
+ className,
399
+ style: {
400
+ fontFamily: theme.fontFamily,
401
+ color: theme.text,
402
+ ...style
403
+ },
404
+ children: [
405
+ showTitle ? /* @__PURE__ */ jsx("div", {
406
+ style: {
407
+ fontSize: 17,
408
+ fontWeight: 700,
409
+ padding: "10px 14px 6px"
410
+ },
411
+ children: format(date, "MMMM yyyy", locale ? { locale } : void 0)
412
+ }) : null,
413
+ showWeekdays ? /* @__PURE__ */ jsx("div", {
414
+ style: {
415
+ display: "grid",
416
+ gridTemplateColumns: "repeat(7, minmax(0, 1fr))",
417
+ borderBottom: `1px solid ${theme.gridLine}`,
418
+ padding: "6px 0"
419
+ },
420
+ children: weekdays.map((wd) => /* @__PURE__ */ jsx("span", {
421
+ style: {
422
+ textAlign: "center",
423
+ fontSize: 12,
424
+ fontWeight: 600,
425
+ color: theme.textMuted
426
+ },
427
+ children: wd.label
428
+ }, wd.label))
429
+ }) : null,
430
+ /* @__PURE__ */ jsx("div", {
431
+ ref: gridRef,
432
+ role: "grid",
433
+ "aria-label": format(date, "MMMM yyyy", locale ? { locale } : void 0),
434
+ onKeyDown: dayRoving ? onKeyDown : void 0,
435
+ children: weeks.map((week) => /* @__PURE__ */ jsx("div", {
436
+ role: "row",
437
+ style: {
438
+ display: "grid",
439
+ gridTemplateColumns: "repeat(7, minmax(0, 1fr))"
440
+ },
441
+ children: week.days.map((day) => {
442
+ const hidden = !showAdjacentMonths && !day.isCurrentMonth;
443
+ const cellHeight = eventsMode ? eventRowHeight : theme.cellHeight;
444
+ if (hidden) return /* @__PURE__ */ jsx("div", {
445
+ role: "gridcell",
446
+ "aria-hidden": true,
447
+ style: { height: cellHeight }
448
+ }, day.id);
449
+ const band = rangeBandStyle(day, theme, fillCellOnSelection);
450
+ const label = format(day.date, "EEEE, d MMMM yyyy", locale ? { locale } : void 0);
451
+ if (eventsMode) {
452
+ const dayEvents = eventsByDay.get(startOfDay(day.date).toISOString()) ?? [];
453
+ const visible = monthVisibleCount(dayEvents.length, {
454
+ full: maxVisibleEventCount,
455
+ withMore: Math.max(maxVisibleEventCount - 1, 1)
456
+ });
457
+ const shown = dayEvents.slice(0, visible);
458
+ const rest = dayEvents.slice(visible);
459
+ const overflow = rest.length > 0;
460
+ const dayLabel = format(day.date, "d MMMM", locale ? { locale } : void 0);
461
+ return /* @__PURE__ */ jsxs("div", {
462
+ role: "gridcell",
463
+ "data-day": day.id,
464
+ tabIndex: keyboardDayNavigation ? day.id === focusKey ? 0 : -1 : void 0,
465
+ "aria-disabled": day.isDisabled || void 0,
466
+ "aria-label": label,
467
+ style: eventCellStyle(day, theme, cellHeight),
468
+ onClick: day.isDisabled ? void 0 : () => onPressDay?.(day.date),
469
+ onKeyDown: keyboardDayNavigation && !day.isDisabled ? (e) => {
470
+ if (e.target !== e.currentTarget) return;
471
+ if (e.key === "Enter" || e.key === " ") {
472
+ e.preventDefault();
473
+ onPressDay?.(day.date);
474
+ }
475
+ } : void 0,
476
+ children: [
477
+ band ? /* @__PURE__ */ jsx("span", {
478
+ "data-band": true,
479
+ "aria-hidden": true,
480
+ style: band
481
+ }) : null,
482
+ /* @__PURE__ */ jsx("span", {
483
+ style: compactBadgeStyle(day, theme),
484
+ children: day.label
485
+ }),
486
+ /* @__PURE__ */ jsxs("div", {
487
+ style: {
488
+ display: "flex",
489
+ flexDirection: "column",
490
+ gap: CHIP_GAP
491
+ },
492
+ children: [shown.map((event) => {
493
+ const onPress = () => onPressEvent?.(event);
494
+ return /* @__PURE__ */ jsx("button", {
495
+ type: "button",
496
+ onClick: (e) => {
497
+ e.stopPropagation();
498
+ onPress();
499
+ },
500
+ style: chipButtonStyle,
501
+ title: event.title,
502
+ "aria-label": `${event.title}, ${dayLabel}`,
503
+ children: Chip ? /* @__PURE__ */ jsx(Chip, {
504
+ event,
505
+ onPress
506
+ }) : /* @__PURE__ */ jsx("span", {
507
+ style: chipStyle(theme),
508
+ children: event.title
509
+ })
510
+ }, `${event.start.toISOString()}:${event.title}`);
511
+ }), overflow ? /* @__PURE__ */ jsx("button", {
512
+ type: "button",
513
+ onClick: (e) => {
514
+ e.stopPropagation();
515
+ onPressMore?.(rest, day.date);
516
+ },
517
+ style: moreButtonStyle(theme),
518
+ "aria-label": `${rest.length} more events, ${dayLabel}`,
519
+ children: moreLabel.replace("{moreCount}", String(rest.length))
520
+ }) : null]
521
+ })
522
+ ]
523
+ }, day.id);
524
+ }
525
+ return /* @__PURE__ */ jsxs("button", {
526
+ type: "button",
527
+ role: "gridcell",
528
+ "data-day": day.id,
529
+ tabIndex: day.id === focusKey ? 0 : -1,
530
+ "aria-disabled": day.isDisabled || void 0,
531
+ "aria-label": label,
532
+ "aria-pressed": day.isSelected || day.isInRange,
533
+ style: dayCellStyle(day, theme),
534
+ onClick: day.isDisabled ? void 0 : () => onPressDay?.(day.date),
535
+ onMouseEnter: day.isDisabled ? void 0 : () => setHoveredKey(day.id),
536
+ onMouseLeave: () => setHoveredKey((k) => k === day.id ? null : k),
537
+ children: [band ? /* @__PURE__ */ jsx("span", {
538
+ "data-band": true,
539
+ "aria-hidden": true,
540
+ style: band
541
+ }) : null, /* @__PURE__ */ jsx("span", {
542
+ style: badgeStyle(day, theme, hoveredKey === day.id),
543
+ children: day.label
544
+ })]
545
+ }, day.id);
546
+ })
547
+ }, week.id))
548
+ })
549
+ ]
550
+ });
551
+ }
552
+ //#endregion
553
+ //#region src/TimeGrid.tsx
554
+ const HOURS = Array.from({ length: 24 }, (_, h) => h);
555
+ const GUTTER_WIDTH = 56;
556
+ const clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
557
+ const DOM_TITLE_LINE_HEIGHT = 16;
558
+ const DOM_TIME_LINE_HEIGHT = 30;
559
+ const DOM_BOX_PADDING_V = 2;
560
+ function DefaultDomEvent({ event, mode, isAllDay, boxHeight, ampm = false, theme }) {
561
+ const timeLabel = eventTimeLabel({
562
+ mode,
563
+ isAllDay,
564
+ start: event.start,
565
+ end: event.end,
566
+ ampm,
567
+ showTime: true
568
+ });
569
+ const { titleMaxLines, showTime } = eventChipLayout({
570
+ boxHeightPx: boxHeight,
571
+ mode,
572
+ hasTime: !isAllDay && timeLabel != null,
573
+ titleLineHeightPx: DOM_TITLE_LINE_HEIGHT,
574
+ timeLineHeightPx: DOM_TIME_LINE_HEIGHT,
575
+ paddingYPx: DOM_BOX_PADDING_V
576
+ });
577
+ const oneLine = titleNumberOfLines(mode, isAllDay) === 1;
578
+ return /* @__PURE__ */ jsxs("div", {
579
+ style: {
580
+ height: "100%",
581
+ boxSizing: "border-box",
582
+ overflow: "hidden",
583
+ padding: `${DOM_BOX_PADDING_V}px 6px`,
584
+ borderRadius: 6,
585
+ background: theme.eventBackground,
586
+ color: theme.eventText,
587
+ fontSize: 12,
588
+ lineHeight: `${DOM_TITLE_LINE_HEIGHT}px`
589
+ },
590
+ children: [/* @__PURE__ */ jsx("div", {
591
+ style: {
592
+ fontWeight: 600,
593
+ overflow: "hidden",
594
+ ...oneLine ? { whiteSpace: "nowrap" } : {
595
+ wordBreak: "break-word",
596
+ ...titleMaxLines > 0 ? { maxHeight: titleMaxLines * DOM_TITLE_LINE_HEIGHT } : null
597
+ }
598
+ },
599
+ children: event.title
600
+ }), showTime ? /* @__PURE__ */ jsx("div", {
601
+ style: {
602
+ opacity: .75,
603
+ overflow: "hidden",
604
+ maxHeight: DOM_TIME_LINE_HEIGHT
605
+ },
606
+ children: timeLabel
607
+ }) : null]
608
+ });
609
+ }
610
+ /**
611
+ * A day / week / N-day time grid rendered with plain DOM elements. Events are
612
+ * positioned with the library's pure `layoutDayEvents`, so overlap columns and
613
+ * multi-day clipping match the React Native renderer. Supports Ctrl/⌘-scroll and
614
+ * two-finger pinch to zoom, and pointer drag to move / resize events.
615
+ */
616
+ function TimeGrid({ date, events = [], mode = "day", numberOfDays = 1, weekStartsOn = 0, hourHeight: initialHourHeight = 48, scrollOffsetMinutes = 480, zoomable = true, minHourHeight = 24, maxHourHeight = 160, dragStepMinutes = 15, ampm = false, timeslots = 1, businessHours, showNowIndicator = true, showAllDayEventCell = true, locale, theme: themeOverrides, height = 600, renderEvent, hourComponent, onPressEvent, onPressDateHeader, onPressCell, onCreateEvent, onDragStart, onDragEvent, className, style }) {
617
+ const theme = useMemo(() => mergeDomTheme(themeOverrides), [themeOverrides]);
618
+ const scrollRef = useRef(null);
619
+ const dfns = locale ? { locale } : void 0;
620
+ const snapHours = dragStepMinutes / 60;
621
+ const [hourHeight, setHourHeight] = useState(initialHourHeight);
622
+ useEffect(() => setHourHeight(initialHourHeight), [initialHourHeight]);
623
+ const hourHeightRef = useRef(hourHeight);
624
+ hourHeightRef.current = hourHeight;
625
+ const [drag, setDrag] = useState(null);
626
+ const dragRef = useRef(null);
627
+ const dragOrigin = useRef(null);
628
+ const applyDrag = (next) => {
629
+ dragRef.current = next;
630
+ setDrag(next);
631
+ };
632
+ const [createBox, setCreateBox] = useState(null);
633
+ const createOrigin = useRef(null);
634
+ const cellEnabled = !!onPressCell || !!onCreateEvent;
635
+ const days = useMemo(() => getViewDays$1(mode, date, weekStartsOn, numberOfDays), [
636
+ mode,
637
+ date,
638
+ weekStartsOn,
639
+ numberOfDays
640
+ ]);
641
+ const allDayByDay = useMemo(() => days.map((day) => {
642
+ const dayStart = startOfDay(day);
643
+ const dayEnd = addDays(dayStart, 1);
644
+ return events.filter((e) => isAllDayEvent$1(e) && e.start < dayEnd && e.end > dayStart);
645
+ }), [days, events]);
646
+ const hasAllDay = allDayByDay.some((list) => list.length > 0);
647
+ useEffect(() => {
648
+ if (scrollRef.current) scrollRef.current.scrollTop = scrollOffsetMinutes / 60 * hourHeightRef.current;
649
+ }, [scrollOffsetMinutes]);
650
+ useEffect(() => {
651
+ const el = scrollRef.current;
652
+ if (!el || !zoomable) return;
653
+ const onWheel = (e) => {
654
+ if (!e.ctrlKey && !e.metaKey) return;
655
+ e.preventDefault();
656
+ setHourHeight((h) => clamp(h * (1 - e.deltaY * .0015), minHourHeight, maxHourHeight));
657
+ };
658
+ el.addEventListener("wheel", onWheel, { passive: false });
659
+ return () => el.removeEventListener("wheel", onWheel);
660
+ }, [
661
+ zoomable,
662
+ minHourHeight,
663
+ maxHourHeight
664
+ ]);
665
+ const pinch = useRef(/* @__PURE__ */ new Map());
666
+ const pinchBase = useRef(null);
667
+ const onBodyPointerDown = (e) => {
668
+ if (!zoomable || e.pointerType !== "touch") return;
669
+ pinch.current.set(e.pointerId, e.clientY);
670
+ if (pinch.current.size === 2) {
671
+ const ys = [...pinch.current.values()];
672
+ pinchBase.current = {
673
+ dist: Math.abs(ys[0] - ys[1]),
674
+ height: hourHeight
675
+ };
676
+ }
677
+ };
678
+ const onBodyPointerMove = (e) => {
679
+ if (!pinch.current.has(e.pointerId)) return;
680
+ pinch.current.set(e.pointerId, e.clientY);
681
+ if (pinch.current.size === 2 && pinchBase.current) {
682
+ const ys = [...pinch.current.values()];
683
+ const ratio = Math.abs(ys[0] - ys[1]) / (pinchBase.current.dist || 1);
684
+ setHourHeight(clamp(pinchBase.current.height * ratio, minHourHeight, maxHourHeight));
685
+ }
686
+ };
687
+ const onBodyPointerUp = (e) => {
688
+ pinch.current.delete(e.pointerId);
689
+ if (pinch.current.size < 2) pinchBase.current = null;
690
+ };
691
+ const Renderer = renderEvent;
692
+ const totalHeight = 24 * hourHeight;
693
+ const beginDrag = (e, event, key, kind, startHours, durationHours) => {
694
+ if (!onDragEvent) return;
695
+ e.stopPropagation();
696
+ try {
697
+ e.target.setPointerCapture?.(e.pointerId);
698
+ } catch {}
699
+ dragOrigin.current = {
700
+ pointerY: e.clientY,
701
+ startHours,
702
+ durationHours
703
+ };
704
+ applyDrag({
705
+ key,
706
+ kind,
707
+ startHours,
708
+ durationHours,
709
+ moved: false
710
+ });
711
+ onDragStart?.(event);
712
+ };
713
+ const cancelDrag = () => {
714
+ applyDrag(null);
715
+ dragOrigin.current = null;
716
+ };
717
+ const moveDrag = (e) => {
718
+ const d = dragRef.current;
719
+ if (!d || !dragOrigin.current) return;
720
+ const dHours = (e.clientY - dragOrigin.current.pointerY) / hourHeightRef.current;
721
+ const snap = (v) => Math.round(v / snapHours) * snapHours;
722
+ if (d.kind === "move") {
723
+ const startHours = clamp(snap(dragOrigin.current.startHours + dHours), 0, 24 - d.durationHours);
724
+ applyDrag({
725
+ ...d,
726
+ startHours,
727
+ moved: true
728
+ });
729
+ } else {
730
+ const durationHours = clamp(snap(dragOrigin.current.durationHours + dHours), snapHours, 24 - d.startHours);
731
+ applyDrag({
732
+ ...d,
733
+ durationHours,
734
+ moved: true
735
+ });
736
+ }
737
+ };
738
+ const endDrag = (e, day, event, onPress) => {
739
+ const d = dragRef.current;
740
+ if (!d) return;
741
+ try {
742
+ e.target.releasePointerCapture?.(e.pointerId);
743
+ } catch {}
744
+ if (!d.moved) {
745
+ applyDrag(null);
746
+ onPress();
747
+ return;
748
+ }
749
+ const base = startOfDay(day);
750
+ const start = addMinutes(base, Math.round(d.startHours * 60));
751
+ const end = addMinutes(base, Math.round((d.startHours + d.durationHours) * 60));
752
+ onDragEvent?.(event, start, end);
753
+ applyDrag(null);
754
+ dragOrigin.current = null;
755
+ };
756
+ const pxFromTop = (el, clientY) => clientY - el.getBoundingClientRect().top;
757
+ const beginCreate = (e, dayIndex) => {
758
+ if (!cellEnabled || e.pointerType === "touch") return;
759
+ if (e.target !== e.currentTarget || e.button > 0) return;
760
+ const el = e.currentTarget;
761
+ const startPx = pxFromTop(el, e.clientY);
762
+ try {
763
+ el.setPointerCapture?.(e.pointerId);
764
+ } catch {}
765
+ createOrigin.current = {
766
+ el,
767
+ dayIndex,
768
+ startPx
769
+ };
770
+ setCreateBox({
771
+ dayIndex,
772
+ topPx: startPx,
773
+ heightPx: 0
774
+ });
775
+ };
776
+ const moveCreate = (e) => {
777
+ const o = createOrigin.current;
778
+ if (!o) return;
779
+ const cur = pxFromTop(o.el, e.clientY);
780
+ setCreateBox({
781
+ dayIndex: o.dayIndex,
782
+ topPx: Math.min(o.startPx, cur),
783
+ heightPx: Math.abs(cur - o.startPx)
784
+ });
785
+ };
786
+ const endCreate = (e) => {
787
+ const o = createOrigin.current;
788
+ if (!o) return;
789
+ try {
790
+ o.el.releasePointerCapture?.(e.pointerId);
791
+ } catch {}
792
+ const endPx = pxFromTop(o.el, e.clientY);
793
+ const day = days[o.dayIndex];
794
+ const moved = Math.abs(endPx - o.startPx) > 4;
795
+ const h = hourHeightRef.current;
796
+ if (moved && onCreateEvent) {
797
+ const range = cellRangeFromDrag(day, o.startPx, endPx, h, 0, dragStepMinutes);
798
+ if (range) onCreateEvent(range.start, range.end);
799
+ } else if (onPressCell) {
800
+ const at = cellRangeFromDrag(day, o.startPx, o.startPx, h, 0, dragStepMinutes);
801
+ if (at) onPressCell(at.start);
802
+ }
803
+ createOrigin.current = null;
804
+ setCreateBox(null);
805
+ };
806
+ const cancelCreate = () => {
807
+ createOrigin.current = null;
808
+ setCreateBox(null);
809
+ };
810
+ const positionedByDay = useMemo(() => days.map((day) => layoutDayEvents$1(events, day)), [days, events]);
811
+ const bandsByDay = useMemo(() => days.map((day) => closedHourBands(day, businessHours)), [days, businessHours]);
812
+ const gridLines = useMemo(() => {
813
+ const hourLines = `repeating-linear-gradient(to bottom, transparent 0, transparent ${hourHeight - 1}px, ${theme.gridLine} ${hourHeight - 1}px, ${theme.gridLine} ${hourHeight}px)`;
814
+ if (timeslots <= 1) return hourLines;
815
+ const slotHeight = hourHeight / timeslots;
816
+ return `${hourLines}, repeating-linear-gradient(to bottom, transparent 0, transparent ${slotHeight - 1}px, ${theme.gridLine}80 ${slotHeight - 1}px, ${theme.gridLine}80 ${slotHeight}px)`;
817
+ }, [
818
+ hourHeight,
819
+ timeslots,
820
+ theme.gridLine
821
+ ]);
822
+ return /* @__PURE__ */ jsxs("div", {
823
+ className,
824
+ style: {
825
+ fontFamily: theme.fontFamily,
826
+ color: theme.text,
827
+ display: "flex",
828
+ flexDirection: "column",
829
+ ...style
830
+ },
831
+ children: [
832
+ /* @__PURE__ */ jsxs("div", {
833
+ style: {
834
+ display: "flex",
835
+ borderBottom: `1px solid ${theme.gridLine}`
836
+ },
837
+ children: [/* @__PURE__ */ jsx("div", { style: {
838
+ width: GUTTER_WIDTH,
839
+ flex: "none"
840
+ } }), days.map((day) => {
841
+ const today = getIsToday(day);
842
+ return /* @__PURE__ */ jsxs("button", {
843
+ type: "button",
844
+ tabIndex: onPressDateHeader ? 0 : -1,
845
+ "aria-hidden": onPressDateHeader ? void 0 : true,
846
+ onClick: onPressDateHeader ? () => onPressDateHeader(day) : void 0,
847
+ style: {
848
+ flex: 1,
849
+ border: "none",
850
+ background: "transparent",
851
+ font: "inherit",
852
+ color: theme.textMuted,
853
+ cursor: onPressDateHeader ? "pointer" : "default",
854
+ padding: "6px 0",
855
+ display: "flex",
856
+ flexDirection: "column",
857
+ alignItems: "center",
858
+ gap: 2
859
+ },
860
+ children: [/* @__PURE__ */ jsx("span", {
861
+ style: {
862
+ fontSize: 11,
863
+ fontWeight: 600
864
+ },
865
+ children: format(day, "EEE", dfns)
866
+ }), /* @__PURE__ */ jsx("span", {
867
+ style: {
868
+ width: 28,
869
+ height: 28,
870
+ borderRadius: "50%",
871
+ display: "flex",
872
+ alignItems: "center",
873
+ justifyContent: "center",
874
+ fontSize: 15,
875
+ fontWeight: 600,
876
+ background: today ? theme.todayBackground : "transparent",
877
+ color: today ? theme.todayText : theme.text
878
+ },
879
+ children: format(day, "d", dfns)
880
+ })]
881
+ }, day.toISOString());
882
+ })]
883
+ }),
884
+ showAllDayEventCell && hasAllDay ? /* @__PURE__ */ jsxs("div", {
885
+ style: {
886
+ display: "flex",
887
+ borderBottom: `1px solid ${theme.gridLine}`
888
+ },
889
+ children: [/* @__PURE__ */ jsx("div", {
890
+ style: {
891
+ width: GUTTER_WIDTH,
892
+ flex: "none",
893
+ fontSize: 10,
894
+ color: theme.textMuted,
895
+ textAlign: "right",
896
+ padding: "4px 6px 0 0"
897
+ },
898
+ children: "all-day"
899
+ }), allDayByDay.map((list, i) => {
900
+ const dayStart = startOfDay(days[i]);
901
+ const dayEnd = addDays(dayStart, 1);
902
+ return /* @__PURE__ */ jsx("div", {
903
+ style: {
904
+ flex: 1,
905
+ minWidth: 0,
906
+ borderLeft: "1px solid transparent",
907
+ padding: "2px 1px",
908
+ display: "flex",
909
+ flexDirection: "column",
910
+ gap: 2
911
+ },
912
+ children: list.map((event) => {
913
+ const args = {
914
+ event,
915
+ mode,
916
+ isAllDay: true,
917
+ continuesBefore: event.start < dayStart,
918
+ continuesAfter: event.end > dayEnd,
919
+ ampm,
920
+ onPress: () => onPressEvent?.(event)
921
+ };
922
+ return /* @__PURE__ */ jsx("button", {
923
+ type: "button",
924
+ onClick: () => onPressEvent?.(event),
925
+ "aria-label": eventAccessibilityLabel({
926
+ title: event.title,
927
+ isAllDay: true,
928
+ start: event.start,
929
+ end: event.end,
930
+ ampm
931
+ }),
932
+ style: {
933
+ border: "none",
934
+ padding: 0,
935
+ background: "transparent",
936
+ cursor: "pointer",
937
+ textAlign: "left",
938
+ height: 22
939
+ },
940
+ children: Renderer ? /* @__PURE__ */ jsx(Renderer, { ...args }) : /* @__PURE__ */ jsx(DefaultDomEvent, {
941
+ ...args,
942
+ theme
943
+ })
944
+ }, `${event.start.toISOString()}:${event.title}`);
945
+ })
946
+ }, days[i].toISOString());
947
+ })]
948
+ }) : null,
949
+ /* @__PURE__ */ jsx("div", {
950
+ ref: scrollRef,
951
+ onPointerDown: onBodyPointerDown,
952
+ onPointerMove: onBodyPointerMove,
953
+ onPointerUp: onBodyPointerUp,
954
+ onPointerCancel: onBodyPointerUp,
955
+ style: {
956
+ overflowY: "auto",
957
+ height,
958
+ position: "relative",
959
+ touchAction: zoomable ? "pan-y" : "auto"
960
+ },
961
+ children: /* @__PURE__ */ jsxs("div", {
962
+ style: {
963
+ display: "flex",
964
+ height: totalHeight,
965
+ position: "relative"
966
+ },
967
+ children: [/* @__PURE__ */ jsx("div", {
968
+ style: {
969
+ width: GUTTER_WIDTH,
970
+ flex: "none",
971
+ position: "relative"
972
+ },
973
+ children: HOURS.map((h) => /* @__PURE__ */ jsx("div", {
974
+ style: {
975
+ position: "absolute",
976
+ top: h * hourHeight - 6,
977
+ right: 6,
978
+ fontSize: 10,
979
+ color: theme.textMuted
980
+ },
981
+ children: hourComponent ? hourComponent(h, ampm) : h === 0 ? "" : formatHour(h, { ampm })
982
+ }, h))
983
+ }), days.map((day, dayIndex) => {
984
+ const positioned = positionedByDay[dayIndex];
985
+ const showNow = showNowIndicator && isSameCalendarDay(day, /* @__PURE__ */ new Date());
986
+ const nowDate = /* @__PURE__ */ new Date();
987
+ const nowTop = (nowDate.getHours() * 60 + nowDate.getMinutes()) / 60 * hourHeight;
988
+ const bands = bandsByDay[dayIndex];
989
+ const ghost = createBox?.dayIndex === dayIndex ? createBox : null;
990
+ return /* @__PURE__ */ jsxs("div", {
991
+ onPointerDown: cellEnabled ? (e) => beginCreate(e, dayIndex) : void 0,
992
+ onPointerMove: cellEnabled ? moveCreate : void 0,
993
+ onPointerUp: cellEnabled ? endCreate : void 0,
994
+ onPointerCancel: cellEnabled ? cancelCreate : void 0,
995
+ style: {
996
+ flex: 1,
997
+ position: "relative",
998
+ borderLeft: `1px solid ${theme.gridLine}`
999
+ },
1000
+ children: [
1001
+ bands.map((b) => /* @__PURE__ */ jsx("div", {
1002
+ "aria-hidden": true,
1003
+ style: {
1004
+ position: "absolute",
1005
+ left: 0,
1006
+ right: 0,
1007
+ top: b.start * hourHeight,
1008
+ height: (b.end - b.start) * hourHeight,
1009
+ background: theme.outsideHoursBackground,
1010
+ pointerEvents: "none",
1011
+ zIndex: 0
1012
+ }
1013
+ }, b.start)),
1014
+ /* @__PURE__ */ jsx("div", {
1015
+ "aria-hidden": true,
1016
+ style: {
1017
+ position: "absolute",
1018
+ inset: 0,
1019
+ pointerEvents: "none",
1020
+ zIndex: 0,
1021
+ backgroundImage: gridLines
1022
+ }
1023
+ }),
1024
+ positioned.map((pe, idx) => {
1025
+ const key = `${dayIndex}:${idx}`;
1026
+ const active = drag?.key === key ? drag : null;
1027
+ const startHours = active ? active.startHours : pe.startHours;
1028
+ const durationHours = active ? active.durationHours : pe.durationHours;
1029
+ const top = startHours * hourHeight;
1030
+ const boxHeight = Math.max(durationHours * hourHeight, 14);
1031
+ const widthPct = 100 / pe.columns;
1032
+ const onPress = () => onPressEvent?.(pe.event);
1033
+ const args = {
1034
+ event: pe.event,
1035
+ mode,
1036
+ isAllDay: false,
1037
+ boxHeight,
1038
+ continuesBefore: pe.continuesBefore,
1039
+ continuesAfter: pe.continuesAfter,
1040
+ ampm,
1041
+ onPress
1042
+ };
1043
+ const draggable = !!onDragEvent;
1044
+ return /* @__PURE__ */ jsxs("div", {
1045
+ role: "button",
1046
+ tabIndex: 0,
1047
+ "aria-label": eventAccessibilityLabel({
1048
+ title: pe.event.title,
1049
+ isAllDay: false,
1050
+ start: pe.event.start,
1051
+ end: pe.event.end,
1052
+ ampm
1053
+ }),
1054
+ onKeyDown: (e) => {
1055
+ if (e.key === "Enter" || e.key === " ") {
1056
+ e.preventDefault();
1057
+ onPress();
1058
+ }
1059
+ },
1060
+ onPointerDown: draggable ? (e) => beginDrag(e, pe.event, key, "move", pe.startHours, pe.durationHours) : void 0,
1061
+ onPointerMove: draggable ? moveDrag : void 0,
1062
+ onPointerUp: draggable ? (e) => endDrag(e, day, pe.event, onPress) : void 0,
1063
+ onPointerCancel: draggable ? cancelDrag : void 0,
1064
+ onClick: draggable ? void 0 : onPress,
1065
+ style: {
1066
+ position: "absolute",
1067
+ top,
1068
+ height: boxHeight,
1069
+ left: `calc(${pe.column * widthPct}% + 1px)`,
1070
+ width: `calc(${widthPct}% - 2px)`,
1071
+ cursor: draggable ? "grab" : "pointer",
1072
+ touchAction: draggable ? "none" : "auto",
1073
+ zIndex: active ? 3 : 1,
1074
+ opacity: active ? .85 : 1
1075
+ },
1076
+ children: [Renderer ? /* @__PURE__ */ jsx(Renderer, { ...args }) : /* @__PURE__ */ jsx(DefaultDomEvent, {
1077
+ ...args,
1078
+ theme
1079
+ }), draggable ? /* @__PURE__ */ jsx("div", {
1080
+ onPointerDown: (e) => {
1081
+ e.stopPropagation();
1082
+ beginDrag(e, pe.event, key, "resize", pe.startHours, pe.durationHours);
1083
+ },
1084
+ style: {
1085
+ position: "absolute",
1086
+ left: 0,
1087
+ right: 0,
1088
+ bottom: 0,
1089
+ height: 8,
1090
+ cursor: "ns-resize",
1091
+ touchAction: "none"
1092
+ }
1093
+ }) : null]
1094
+ }, idx);
1095
+ }),
1096
+ showNow ? /* @__PURE__ */ jsxs("div", {
1097
+ style: {
1098
+ position: "absolute",
1099
+ top: nowTop,
1100
+ left: 0,
1101
+ right: 0,
1102
+ height: 0,
1103
+ zIndex: 2,
1104
+ pointerEvents: "none"
1105
+ },
1106
+ children: [/* @__PURE__ */ jsx("div", { style: {
1107
+ height: 2,
1108
+ background: theme.nowIndicator
1109
+ } }), /* @__PURE__ */ jsx("div", { style: {
1110
+ position: "absolute",
1111
+ left: -3,
1112
+ top: -3,
1113
+ width: 8,
1114
+ height: 8,
1115
+ borderRadius: "50%",
1116
+ background: theme.nowIndicator
1117
+ } })]
1118
+ }) : null,
1119
+ ghost ? /* @__PURE__ */ jsx("div", {
1120
+ "aria-hidden": true,
1121
+ style: {
1122
+ position: "absolute",
1123
+ left: 1,
1124
+ right: 1,
1125
+ top: ghost.topPx,
1126
+ height: Math.max(ghost.heightPx, 2),
1127
+ background: theme.rangeBackground,
1128
+ border: `1px solid ${theme.selectedBackground}`,
1129
+ borderRadius: 6,
1130
+ opacity: .7,
1131
+ pointerEvents: "none",
1132
+ zIndex: 2
1133
+ }
1134
+ }) : null
1135
+ ]
1136
+ }, day.toISOString());
1137
+ })]
1138
+ })
1139
+ })
1140
+ ]
1141
+ });
1142
+ }
1143
+ //#endregion
1144
+ //#region src/Calendar.tsx
1145
+ /**
1146
+ * Batteries-included entry point for the react-dom renderer: it picks the right
1147
+ * view for `mode`. `month` renders a single {@link MonthView}; `schedule` renders
1148
+ * an {@link Agenda}; the time-grid modes render a {@link TimeGrid}. For a
1149
+ * scrolling month picker, use {@link MonthList} directly.
1150
+ */
1151
+ function Calendar({ mode = "week", date, events, weekStartsOn = 0, numberOfDays, locale, theme, height, className, style, onPressEvent, ampm, hourHeight, scrollOffsetMinutes, timeslots, businessHours, showNowIndicator, showAllDayEventCell, dragStepMinutes, onPressCell, onCreateEvent, onDragStart, onDragEvent, onPressDateHeader, renderTimeEvent, hourComponent, maxVisibleEventCount, moreLabel, showAdjacentMonths, fillCellOnSelection, selectedRange, selectedDates, minDate, maxDate, isDateDisabled, keyboardDayNavigation, onPressDay, onPressMore, renderMonthEvent, renderScheduleEvent }) {
1152
+ if (mode === "schedule") return /* @__PURE__ */ jsx(Agenda, {
1153
+ events: events ?? [],
1154
+ locale,
1155
+ ampm,
1156
+ theme,
1157
+ height,
1158
+ activeDate: date,
1159
+ className,
1160
+ style,
1161
+ renderEvent: renderScheduleEvent,
1162
+ onPressEvent,
1163
+ onPressDay
1164
+ });
1165
+ if (mode === "month") return /* @__PURE__ */ jsx(MonthView, {
1166
+ date,
1167
+ events: events ?? [],
1168
+ weekStartsOn,
1169
+ locale,
1170
+ theme,
1171
+ className,
1172
+ style,
1173
+ maxVisibleEventCount,
1174
+ moreLabel,
1175
+ showAdjacentMonths,
1176
+ fillCellOnSelection,
1177
+ selectedRange,
1178
+ selectedDates,
1179
+ minDate,
1180
+ maxDate,
1181
+ isDateDisabled,
1182
+ keyboardDayNavigation,
1183
+ onPressDay,
1184
+ onPressEvent,
1185
+ onPressMore,
1186
+ renderEvent: renderMonthEvent
1187
+ });
1188
+ return /* @__PURE__ */ jsx(TimeGrid, {
1189
+ date,
1190
+ mode,
1191
+ events,
1192
+ weekStartsOn,
1193
+ numberOfDays,
1194
+ locale,
1195
+ theme,
1196
+ height,
1197
+ className,
1198
+ style,
1199
+ ampm,
1200
+ hourHeight,
1201
+ scrollOffsetMinutes,
1202
+ timeslots,
1203
+ businessHours,
1204
+ showNowIndicator,
1205
+ showAllDayEventCell,
1206
+ dragStepMinutes,
1207
+ onPressEvent,
1208
+ onPressCell,
1209
+ onCreateEvent,
1210
+ onDragStart,
1211
+ onDragEvent,
1212
+ onPressDateHeader,
1213
+ renderEvent: renderTimeEvent,
1214
+ hourComponent
1215
+ });
1216
+ }
1217
+ //#endregion
1218
+ //#region src/MonthList.tsx
1219
+ /**
1220
+ * A vertically scrolling, virtualized list of months: the date picker. Built on
1221
+ * Legend List's DOM renderer and the library's headless grid logic. Selection is
1222
+ * controlled, pass `selectedRange` (or `selectedDates`) and handle `onPressDay`.
1223
+ */
1224
+ function MonthList({ date, pastMonths = 1, futureMonths = 12, weekStartsOn = 0, events, renderEvent, maxVisibleEventCount, moreLabel, onPressEvent, onPressMore, selectedRange, selectedDates, fillCellOnSelection = false, locale, theme: themeOverrides, height = 480, minDate, maxDate, isDateDisabled, keyboardDayNavigation, onPressDay, className, style }) {
1225
+ const theme = useMemo(() => mergeDomTheme(themeOverrides), [themeOverrides]);
1226
+ const months = useMemo(() => {
1227
+ const first = startOfMonth(addMonths(date, -pastMonths));
1228
+ const count = pastMonths + futureMonths + 1;
1229
+ return Array.from({ length: count }, (_, i) => addMonths(first, i));
1230
+ }, [
1231
+ date,
1232
+ pastMonths,
1233
+ futureMonths
1234
+ ]);
1235
+ const weekdays = useMemo(() => buildMonthGrid$1(date, {
1236
+ weekStartsOn,
1237
+ locale
1238
+ }).weekdays, [
1239
+ date,
1240
+ weekStartsOn,
1241
+ locale
1242
+ ]);
1243
+ const eventsByDay = useMemo(() => {
1244
+ if (!events) return void 0;
1245
+ const map = groupEventsByDay(events);
1246
+ for (const list of map.values()) list.sort(compareDayEvents);
1247
+ return map;
1248
+ }, [events]);
1249
+ const extraData = useMemo(() => [
1250
+ selectedRange,
1251
+ selectedDates,
1252
+ events
1253
+ ], [
1254
+ selectedRange,
1255
+ selectedDates,
1256
+ events
1257
+ ]);
1258
+ return /* @__PURE__ */ jsxs("div", {
1259
+ className,
1260
+ style: {
1261
+ fontFamily: theme.fontFamily,
1262
+ color: theme.text,
1263
+ ...style
1264
+ },
1265
+ children: [/* @__PURE__ */ jsx("div", {
1266
+ style: {
1267
+ display: "grid",
1268
+ gridTemplateColumns: "repeat(7, minmax(0, 1fr))",
1269
+ borderBottom: `1px solid ${theme.gridLine}`,
1270
+ padding: "8px 0"
1271
+ },
1272
+ children: weekdays.map((wd) => /* @__PURE__ */ jsx("span", {
1273
+ style: {
1274
+ textAlign: "center",
1275
+ fontSize: 12,
1276
+ fontWeight: 600,
1277
+ color: theme.textMuted
1278
+ },
1279
+ children: wd.label
1280
+ }, wd.label))
1281
+ }), /* @__PURE__ */ jsx(LegendList, {
1282
+ data: months,
1283
+ extraData,
1284
+ keyExtractor: (m) => m.toISOString(),
1285
+ recycleItems: false,
1286
+ estimatedItemSize: theme.cellHeight * 7 + 40,
1287
+ initialScrollIndex: pastMonths,
1288
+ style: {
1289
+ height,
1290
+ overflowY: "auto"
1291
+ },
1292
+ renderItem: ({ item }) => /* @__PURE__ */ jsx(MonthView, {
1293
+ date: item,
1294
+ weekStartsOn,
1295
+ events,
1296
+ eventsByDay,
1297
+ renderEvent,
1298
+ maxVisibleEventCount,
1299
+ moreLabel,
1300
+ onPressEvent,
1301
+ onPressMore,
1302
+ selectedRange,
1303
+ selectedDates,
1304
+ fillCellOnSelection,
1305
+ showAdjacentMonths: false,
1306
+ showWeekdays: false,
1307
+ locale,
1308
+ theme: themeOverrides,
1309
+ minDate,
1310
+ maxDate,
1311
+ isDateDisabled,
1312
+ keyboardDayNavigation,
1313
+ onPressDay
1314
+ })
1315
+ })]
1316
+ });
1317
+ }
1318
+ //#endregion
1319
+ export { Agenda, Calendar, MonthList, MonthView, TimeGrid, buildMonthGrid, darkDomTheme, daySelectionState, defaultDomTheme, getViewDays, isAllDayEvent, isDateSelectable, isRangeEndpoint, isWithinDateRange, layoutDayEvents, mergeDomTheme, nextDateRange, useDateRange, useMonthGrid };