react-big-schedule 8.1.0 → 9.0.1

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.
Files changed (109) hide show
  1. package/README.md +24 -1129
  2. package/dist/calendar/Calendar.d.ts +28 -0
  3. package/dist/calendar/Calendar.js +95 -0
  4. package/dist/calendar/Toolbar.d.ts +18 -0
  5. package/dist/calendar/Toolbar.js +6 -0
  6. package/dist/calendar/recurrence.check.d.ts +1 -0
  7. package/dist/calendar/recurrence.check.js +49 -0
  8. package/dist/calendar/recurrence.d.ts +8 -0
  9. package/dist/calendar/recurrence.js +90 -0
  10. package/dist/calendar/timezone.d.ts +6 -0
  11. package/dist/calendar/timezone.js +14 -0
  12. package/dist/calendar/types.d.ts +187 -0
  13. package/dist/calendar/types.js +1 -0
  14. package/dist/calendar/useEventSources.d.ts +11 -0
  15. package/dist/calendar/useEventSources.js +89 -0
  16. package/dist/calendar/useRecurrence.d.ts +4 -0
  17. package/dist/calendar/useRecurrence.js +15 -0
  18. package/dist/calendar/utils.d.ts +38 -0
  19. package/dist/calendar/utils.js +143 -0
  20. package/dist/calendar/views/DayGridView.d.ts +3 -0
  21. package/dist/calendar/views/DayGridView.js +375 -0
  22. package/dist/calendar/views/ListView.d.ts +4 -0
  23. package/dist/calendar/views/ListView.js +56 -0
  24. package/dist/calendar/views/MultiMonthView.d.ts +4 -0
  25. package/dist/calendar/views/MultiMonthView.js +36 -0
  26. package/dist/calendar/views/TimeGridView.d.ts +3 -0
  27. package/dist/calendar/views/TimeGridView.js +334 -0
  28. package/dist/calendar/views/daygrid/DayCell.d.ts +19 -0
  29. package/dist/calendar/views/daygrid/DayCell.js +17 -0
  30. package/dist/calendar/views/daygrid/EventChip.d.ts +19 -0
  31. package/dist/calendar/views/daygrid/EventChip.js +40 -0
  32. package/dist/calendar/views/daygrid/layout.d.ts +30 -0
  33. package/dist/calendar/views/daygrid/layout.js +63 -0
  34. package/dist/calendar/views/list/bucket.d.ts +8 -0
  35. package/dist/calendar/views/list/bucket.js +37 -0
  36. package/dist/calendar/views/multimonth/Cell.d.ts +12 -0
  37. package/dist/calendar/views/multimonth/Cell.js +44 -0
  38. package/dist/calendar/views/multimonth/Continuous.d.ts +8 -0
  39. package/dist/calendar/views/multimonth/Continuous.js +28 -0
  40. package/dist/calendar/views/multimonth/MonthGrid.d.ts +10 -0
  41. package/dist/calendar/views/multimonth/MonthGrid.js +36 -0
  42. package/dist/calendar/views/multimonth/shared.d.ts +27 -0
  43. package/dist/calendar/views/multimonth/shared.js +40 -0
  44. package/dist/calendar/views/timegrid/DayColumn.d.ts +49 -0
  45. package/dist/calendar/views/timegrid/DayColumn.js +86 -0
  46. package/dist/calendar/views/timegrid/model.d.ts +55 -0
  47. package/dist/calendar/views/timegrid/model.js +102 -0
  48. package/dist/components/AddMore.d.ts +13 -0
  49. package/dist/components/AddMore.js +9 -42
  50. package/dist/components/AddMorePopover.d.ts +13 -0
  51. package/dist/components/AddMorePopover.js +24 -95
  52. package/dist/components/AgendaEventItem.d.ts +10 -0
  53. package/dist/components/AgendaEventItem.js +33 -81
  54. package/dist/components/AgendaResourceEvents.d.ts +10 -0
  55. package/dist/components/AgendaResourceEvents.js +37 -91
  56. package/dist/components/AgendaView.d.ts +9 -0
  57. package/dist/components/AgendaView.js +10 -69
  58. package/dist/components/BodyView.d.ts +11 -0
  59. package/dist/components/BodyView.js +36 -67
  60. package/dist/components/DnDContext.d.ts +48 -0
  61. package/dist/components/DnDContext.js +200 -221
  62. package/dist/components/DnDSource.d.ts +49 -0
  63. package/dist/components/DnDSource.js +125 -120
  64. package/dist/components/EventItem.d.ts +41 -0
  65. package/dist/components/EventItem.js +585 -787
  66. package/dist/components/EventItemPopover.d.ts +12 -0
  67. package/dist/components/EventItemPopover.js +19 -143
  68. package/dist/components/HeaderView.d.ts +14 -0
  69. package/dist/components/HeaderView.js +123 -186
  70. package/dist/components/ResourceEvents.d.ts +54 -0
  71. package/dist/components/ResourceEvents.js +534 -703
  72. package/dist/components/ResourceView.d.ts +26 -0
  73. package/dist/components/ResourceView.js +64 -184
  74. package/dist/components/SchedulerData.d.ts +136 -0
  75. package/dist/components/SchedulerData.js +1296 -1226
  76. package/dist/components/SchedulerHeader.d.ts +25 -0
  77. package/dist/components/SchedulerHeader.js +50 -150
  78. package/dist/components/SelectedArea.d.ts +8 -0
  79. package/dist/components/SelectedArea.js +11 -26
  80. package/dist/components/Summary.d.ts +11 -0
  81. package/dist/components/Summary.js +21 -50
  82. package/dist/components/WrapperFun.d.ts +2 -0
  83. package/dist/components/WrapperFun.js +5 -10
  84. package/dist/components/index.d.ts +44 -0
  85. package/dist/components/index.js +428 -654
  86. package/dist/components/ui/Icons.d.ts +8 -0
  87. package/dist/components/ui/Icons.js +7 -0
  88. package/dist/components/ui/MiniCalendar.d.ts +9 -0
  89. package/dist/components/ui/MiniCalendar.js +17 -0
  90. package/dist/components/ui/Popover.d.ts +13 -0
  91. package/dist/components/ui/Popover.js +80 -0
  92. package/dist/config/default.d.ts +36 -0
  93. package/dist/config/default.js +17 -26
  94. package/dist/config/i18n.d.ts +27 -0
  95. package/dist/config/i18n.js +21 -37
  96. package/dist/config/scheduler.d.ts +12 -0
  97. package/dist/config/scheduler.js +126 -119
  98. package/dist/css/style.css +2 -316
  99. package/dist/helper/behaviors.d.ts +14 -0
  100. package/dist/helper/behaviors.js +75 -75
  101. package/dist/helper/utility.d.ts +26 -0
  102. package/dist/helper/utility.js +23 -30
  103. package/dist/index.d.ts +6 -477
  104. package/dist/index.js +4 -2
  105. package/dist/sample-data/sample1.d.ts +11 -0
  106. package/dist/sample-data/sample1.js +427 -372
  107. package/dist/types.d.ts +267 -0
  108. package/dist/types.js +1 -0
  109. package/package.json +30 -27
@@ -0,0 +1,38 @@
1
+ import type { Dayjs } from 'dayjs';
2
+ import type { LocaleDayjs } from '../types';
3
+ import type { CalendarEvent, CalendarViewName, DateLike, DateRange } from './types';
4
+ /** An event with parsed dates. `end` is exclusive (an all-day event on Mar 3 ends Mar 4 00:00). */
5
+ export interface NormalizedEvent {
6
+ source: CalendarEvent;
7
+ id: CalendarEvent['id'];
8
+ start: Dayjs;
9
+ end: Dayjs;
10
+ allDay: boolean;
11
+ }
12
+ export declare const toDayjs: (dayjs: LocaleDayjs, value: DateLike) => Dayjs;
13
+ export declare function normalizeEvent(dayjs: LocaleDayjs, event: CalendarEvent): NormalizedEvent;
14
+ /** Events that overlap [range.start, range.end). */
15
+ export declare function eventsInRange(events: NormalizedEvent[], range: DateRange): NormalizedEvent[];
16
+ export declare function getViewRange(view: CalendarViewName, date: Dayjs, firstDay?: number): DateRange;
17
+ /** Step used by prev/next for a given view. */
18
+ export declare function stepDate(view: CalendarViewName, date: Dayjs, dir: 1 | -1): Dayjs;
19
+ export declare function getViewTitle(view: CalendarViewName, date: Dayjs, range: DateRange): string;
20
+ /** Parse 'HH:mm' (or 'HH:mm:ss') into minutes since midnight. */
21
+ export declare function parseTime(value: string): number;
22
+ export declare const isSameDay: (a: Dayjs, b: Dayjs) => boolean;
23
+ /** Days of a range, optionally dropping weekends. */
24
+ export declare function listDays(range: DateRange, weekends?: boolean): Dayjs[];
25
+ /**
26
+ * Stack events into columns so overlapping ones sit side by side (time grid) or on separate rows (day grid).
27
+ * Returns, for each event, its lane index and the number of lanes in its overlap cluster.
28
+ */
29
+ export declare function layoutLanes<T extends {
30
+ start: Dayjs;
31
+ end: Dayjs;
32
+ }>(items: T[]): {
33
+ item: T;
34
+ lane: number;
35
+ lanes: number;
36
+ }[];
37
+ /** Hover-card text for an event: the title, then when it happens (shown by the Calendar's delegated tooltip). */
38
+ export declare function tipText(e: NormalizedEvent): string;
@@ -0,0 +1,143 @@
1
+ const DATE_ONLY = /^\d{4}-\d{2}-\d{2}$/;
2
+ export const toDayjs = (dayjs, value) => dayjs(value);
3
+ export function normalizeEvent(dayjs, event) {
4
+ const allDay = event.allDay ?? (typeof event.start === 'string' && DATE_ONLY.test(event.start));
5
+ let start = toDayjs(dayjs, event.start);
6
+ let end = event.end === undefined ? undefined : toDayjs(dayjs, event.end);
7
+ if (allDay) {
8
+ start = start.startOf('day');
9
+ // all-day end is exclusive; a missing end means a single day
10
+ end = end ? end.startOf('day') : start.add(1, 'day');
11
+ if (!end.isAfter(start))
12
+ end = start.add(1, 'day');
13
+ }
14
+ else if (!end?.isAfter(start)) {
15
+ end = start.add(end ? 0 : 1, 'hour');
16
+ }
17
+ return { source: event, id: event.id, start, end, allDay };
18
+ }
19
+ /** Events that overlap [range.start, range.end). */
20
+ export function eventsInRange(events, range) {
21
+ return events.filter(e => e.start.isBefore(range.end) && e.end.isAfter(range.start));
22
+ }
23
+ export function getViewRange(view, date, firstDay = 0) {
24
+ const weekStart = (d) => d.subtract((d.day() - firstDay + 7) % 7, 'day').startOf('day');
25
+ switch (view) {
26
+ case 'dayGridMonth': {
27
+ const first = weekStart(date.startOf('month'));
28
+ const last = weekStart(date.endOf('month')).add(7, 'day');
29
+ return { start: first, end: last };
30
+ }
31
+ case 'dayGridWeek':
32
+ case 'timeGridWeek':
33
+ case 'listWeek':
34
+ return { start: weekStart(date), end: weekStart(date).add(7, 'day') };
35
+ case 'dayGridDay':
36
+ case 'timeGridDay':
37
+ case 'listDay':
38
+ return { start: date.startOf('day'), end: date.startOf('day').add(1, 'day') };
39
+ case 'listMonth':
40
+ return { start: date.startOf('month'), end: date.startOf('month').add(1, 'month') };
41
+ case 'listYear':
42
+ case 'multiMonthYear':
43
+ case 'multiMonthStack':
44
+ return { start: date.startOf('year'), end: date.startOf('year').add(1, 'year') };
45
+ case 'multiMonthContinuous':
46
+ return { start: weekStart(date.startOf('year')), end: weekStart(date.endOf('year')).add(7, 'day') };
47
+ }
48
+ }
49
+ /** Step used by prev/next for a given view. */
50
+ export function stepDate(view, date, dir) {
51
+ if (view.endsWith('Month') || view === 'dayGridMonth')
52
+ return date.add(dir, 'month');
53
+ if (view.endsWith('Week'))
54
+ return date.add(dir, 'week');
55
+ if (view.endsWith('Day'))
56
+ return date.add(dir, 'day');
57
+ return date.add(dir, 'year');
58
+ }
59
+ export function getViewTitle(view, date, range) {
60
+ switch (view) {
61
+ case 'dayGridMonth':
62
+ case 'listMonth':
63
+ return date.format('MMMM YYYY');
64
+ case 'dayGridWeek':
65
+ case 'timeGridWeek':
66
+ case 'listWeek': {
67
+ const s = range.start;
68
+ const e = range.end.subtract(1, 'day');
69
+ if (s.year() !== e.year())
70
+ return `${s.format('MMM D, YYYY')} – ${e.format('MMM D, YYYY')}`;
71
+ if (s.month() !== e.month())
72
+ return `${s.format('MMM D')} – ${e.format('MMM D, YYYY')}`;
73
+ return `${s.format('MMM D')} – ${e.format('D, YYYY')}`;
74
+ }
75
+ case 'dayGridDay':
76
+ case 'timeGridDay':
77
+ case 'listDay':
78
+ return date.format('dddd, MMMM D, YYYY');
79
+ default:
80
+ return date.format('YYYY');
81
+ }
82
+ }
83
+ /** Parse 'HH:mm' (or 'HH:mm:ss') into minutes since midnight. */
84
+ export function parseTime(value) {
85
+ const [h = '0', m = '0'] = value.split(':');
86
+ return Number(h) * 60 + Number(m);
87
+ }
88
+ export const isSameDay = (a, b) => a.isSame(b, 'day');
89
+ /** Days of a range, optionally dropping weekends. */
90
+ export function listDays(range, weekends = true) {
91
+ const days = [];
92
+ for (let d = range.start; d.isBefore(range.end); d = d.add(1, 'day')) {
93
+ if (weekends || (d.day() !== 0 && d.day() !== 6))
94
+ days.push(d);
95
+ }
96
+ return days;
97
+ }
98
+ /**
99
+ * Stack events into columns so overlapping ones sit side by side (time grid) or on separate rows (day grid).
100
+ * Returns, for each event, its lane index and the number of lanes in its overlap cluster.
101
+ */
102
+ export function layoutLanes(items) {
103
+ const sorted = [...items].sort((a, b) => a.start.valueOf() - b.start.valueOf() || b.end.valueOf() - a.end.valueOf());
104
+ const result = [];
105
+ let cluster = [];
106
+ let laneEnds = [];
107
+ let clusterEnd = -Infinity;
108
+ const flush = () => {
109
+ for (const r of cluster)
110
+ r.lanes = laneEnds.length;
111
+ result.push(...cluster);
112
+ cluster = [];
113
+ laneEnds = [];
114
+ };
115
+ for (const item of sorted) {
116
+ if (item.start.valueOf() >= clusterEnd)
117
+ flush();
118
+ let lane = laneEnds.findIndex(end => end <= item.start.valueOf());
119
+ if (lane === -1) {
120
+ lane = laneEnds.length;
121
+ laneEnds.push(0);
122
+ }
123
+ laneEnds[lane] = item.end.valueOf();
124
+ clusterEnd = Math.max(clusterEnd, item.end.valueOf());
125
+ cluster.push({ item, lane, lanes: 0 });
126
+ }
127
+ flush();
128
+ return result;
129
+ }
130
+ /** Hover-card text for an event: the title, then when it happens (shown by the Calendar's delegated tooltip). */
131
+ export function tipText(e) {
132
+ const last = e.end.subtract(1, 'millisecond');
133
+ let when;
134
+ if (e.allDay)
135
+ when = last.isSame(e.start, 'day')
136
+ ? `${e.start.format('ddd, MMM D')} · All day`
137
+ : `${e.start.format('ddd, MMM D')} – ${last.format('ddd, MMM D')}`;
138
+ else if (e.end.isSame(e.start, 'day'))
139
+ when = `${e.start.format('ddd, MMM D')} · ${e.start.format('HH:mm')} – ${e.end.format('HH:mm')}`;
140
+ else
141
+ when = `${e.start.format('MMM D, HH:mm')} – ${e.end.format('MMM D, HH:mm')}`;
142
+ return `${e.source.title}\n${when}`;
143
+ }
@@ -0,0 +1,3 @@
1
+ import type { CalendarViewProps } from '../types';
2
+ declare function DayGridView(props: CalendarViewProps): import("react").JSX.Element;
3
+ export default DayGridView;
@@ -0,0 +1,375 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
3
+ import Popover from '../../components/ui/Popover';
4
+ import { eventsInRange, getViewRange, normalizeEvent } from '../utils';
5
+ import DayCell from './daygrid/DayCell';
6
+ import EventChip from './daygrid/EventChip';
7
+ import { DAY, HEAD_H, isoWeek, LANE_H, layoutRow, prepare } from './daygrid/layout';
8
+ const KEY = 'YYYY-MM-DD';
9
+ const EMPTY = [];
10
+ const keyAt = (x, y) => {
11
+ for (const el of document.elementsFromPoint(x, y)) {
12
+ if (el instanceof HTMLElement && el.dataset.date)
13
+ return el.dataset.date;
14
+ }
15
+ return undefined;
16
+ };
17
+ /** Write a date back in the same representation the user supplied. */
18
+ function sameKind(orig, d, allDay) {
19
+ if (allDay)
20
+ return d.format(KEY);
21
+ if (orig instanceof Date)
22
+ return d.toDate();
23
+ if (typeof orig === 'number')
24
+ return d.valueOf();
25
+ if (typeof orig === 'string' || orig === undefined)
26
+ return d.format();
27
+ return d;
28
+ }
29
+ function DayGridView(props) {
30
+ const { view, date, events, dayjs, firstDay = 0, goTo, dayMaxEvents, weekNumbers, selectable, editable } = props;
31
+ const isMonth = view === 'dayGridMonth';
32
+ const weekends = props.weekends !== false || view === 'dayGridDay';
33
+ const showOther = props.showNonCurrentDates !== false;
34
+ // ---- date grid
35
+ const range = useMemo(() => getViewRange(view, date, firstDay), [view, date.valueOf(), firstDay]);
36
+ const grid = useMemo(() => {
37
+ const rows = [];
38
+ let row = [];
39
+ let n = 0;
40
+ for (let d = range.start; d.isBefore(range.end); d = d.add(1, 'day')) {
41
+ if (weekends || (d.day() !== 0 && d.day() !== 6))
42
+ row.push(d);
43
+ if (++n % 7 === 0 || !d.add(1, 'day').isBefore(range.end)) {
44
+ if (row.length)
45
+ rows.push(row);
46
+ row = [];
47
+ }
48
+ }
49
+ const byKey = new Map();
50
+ const valByKey = new Map();
51
+ const flat = [];
52
+ for (const r of rows) {
53
+ for (const d of r) {
54
+ const k = d.format(KEY);
55
+ byKey.set(k, d);
56
+ valByKey.set(k, d.valueOf());
57
+ flat.push(k);
58
+ }
59
+ }
60
+ return { rows, byKey, valByKey, flat };
61
+ }, [range, weekends]);
62
+ const cols = grid.rows[0]?.length ?? 7;
63
+ const todayKey = useMemo(() => dayjs().format(KEY), [dayjs, range]);
64
+ const now = useMemo(() => dayjs().valueOf(), [dayjs, events, range]);
65
+ // ---- events (local overrides keep drops visible until the parent supplies new events)
66
+ const [overrides, setOverrides] = useState({});
67
+ useEffect(() => setOverrides(o => (Object.keys(o).length ? {} : o)), [events]);
68
+ const normalized = useMemo(() => events
69
+ .filter(e => e.display !== 'none' && e.display !== 'background')
70
+ .map(e => normalizeEvent(dayjs, overrides[String(e.id)] ?? e)), [events, overrides, dayjs]);
71
+ // background events tint the day cells they cover
72
+ const bgByKey = useMemo(() => {
73
+ const map = new Map();
74
+ for (const e of events) {
75
+ if (e.display !== 'background')
76
+ continue;
77
+ const n = normalizeEvent(dayjs, e);
78
+ for (let d = n.start.startOf('day'); d.isBefore(n.end); d = d.add(1, 'day')) {
79
+ const k = d.format(KEY);
80
+ if (grid.byKey.has(k) && !map.has(k))
81
+ map.set(k, e.color ?? 'var(--rbs-accent)');
82
+ }
83
+ }
84
+ return map;
85
+ }, [events, dayjs, grid]);
86
+ const byId = useMemo(() => new Map(normalized.map(n => [String(n.id), prepare(n)])), [normalized]);
87
+ const visible = useMemo(() => {
88
+ const inRange = new Set(eventsInRange(normalized, range));
89
+ return normalized.filter(n => inRange.has(n)).map(n => byId.get(String(n.id)));
90
+ }, [normalized, range, byId]);
91
+ // ---- "fit to cell height" measurement
92
+ const fit = dayMaxEvents === true && grid.rows.length > 1;
93
+ const firstRow = useRef(null);
94
+ const [rowH, setRowH] = useState(0);
95
+ useLayoutEffect(() => {
96
+ const el = firstRow.current;
97
+ if (!fit || !el)
98
+ return undefined;
99
+ const ro = new ResizeObserver(() => setRowH(Math.round(el.getBoundingClientRect().height)));
100
+ ro.observe(el);
101
+ return () => ro.disconnect();
102
+ }, [fit]);
103
+ const limitFor = useCallback((lanes) => {
104
+ if (fit) {
105
+ if (!rowH)
106
+ return Infinity;
107
+ const cap = Math.floor((rowH - HEAD_H - 4) / LANE_H);
108
+ return lanes <= cap ? Infinity : Math.max(0, cap - 1);
109
+ }
110
+ return typeof dayMaxEvents === 'number' && lanes > dayMaxEvents ? dayMaxEvents : Infinity;
111
+ }, [fit, rowH, dayMaxEvents]);
112
+ const rowLayouts = useMemo(() => grid.rows.map(row => {
113
+ const vals = row.map(d => d.valueOf());
114
+ const active = row.map(d => !isMonth || showOther || d.month() === date.month());
115
+ return { vals, layout: layoutRow(vals, active, visible, limitFor) };
116
+ }), [grid, visible, limitFor, isMonth, showOther, date]);
117
+ // ---- interaction state
118
+ const [focusKey, setFocusKey] = useState();
119
+ const focusable = focusKey && grid.byKey.has(focusKey)
120
+ ? focusKey
121
+ : grid.byKey.has(todayKey)
122
+ ? todayKey
123
+ : (grid.flat.find(k => !isMonth || grid.byKey.get(k)?.month() === date.month()) ?? grid.flat[0]);
124
+ const [mark, setMark] = useState(null);
125
+ const [moreKey, setMoreKey] = useState(null);
126
+ const rootRef = useRef(null);
127
+ const drag = useRef(null);
128
+ const suppress = useRef(false);
129
+ // Escape closes the "+N more" popover and hands focus back to its trigger
130
+ useEffect(() => {
131
+ if (!moreKey)
132
+ return undefined;
133
+ const onKey = (e) => {
134
+ if (e.key !== 'Escape')
135
+ return;
136
+ setMoreKey(null);
137
+ for (const el of rootRef.current?.querySelectorAll('[data-more]') ?? []) {
138
+ if (el.dataset.more === moreKey)
139
+ el.focus();
140
+ }
141
+ };
142
+ document.addEventListener('keydown', onKey);
143
+ return () => document.removeEventListener('keydown', onKey);
144
+ }, [moreKey]);
145
+ const canMove = (e) => e.startEditable ?? e.editable ?? editable ?? false;
146
+ const canResize = (e) => e.durationEditable ?? e.editable ?? editable ?? false;
147
+ // always-fresh handles for window-level listeners
148
+ const latest = useRef({ props, grid, byId });
149
+ latest.current = { props, grid, byId };
150
+ const commit = useCallback((d) => {
151
+ const { props: p, grid: g } = latest.current;
152
+ const h = g.valByKey.get(d.hover);
153
+ const a = g.valByKey.get(d.anchor);
154
+ if (h === undefined || a === undefined)
155
+ return;
156
+ if (d.kind === 'select') {
157
+ const [lo, hi] = h < a ? [h, a] : [a, h];
158
+ p.onSelect?.({ start: p.dayjs(lo), end: p.dayjs(hi).add(1, 'day'), allDay: true });
159
+ return;
160
+ }
161
+ const ev = d.ev;
162
+ if (!ev)
163
+ return;
164
+ const src = ev.ne.source;
165
+ const delta = Math.round((d.kind === 'move' ? h - a : Math.max(h, ev.sVal) - ev.eVal) / DAY);
166
+ if (!delta)
167
+ return;
168
+ const oldEvent = src;
169
+ let start = ev.ne.start;
170
+ let end = ev.ne.end;
171
+ if (d.kind === 'move')
172
+ start = start.add(delta, 'day');
173
+ end = end.add(delta, 'day');
174
+ if (!end.isAfter(start))
175
+ return;
176
+ const event = {
177
+ ...src,
178
+ start: sameKind(src.start, start, ev.ne.allDay),
179
+ end: sameKind(src.end, end, ev.ne.allDay),
180
+ };
181
+ const id = String(src.id);
182
+ setOverrides(o => ({ ...o, [id]: event }));
183
+ const revert = () => setOverrides(o => {
184
+ const next = { ...o };
185
+ delete next[id];
186
+ return next;
187
+ });
188
+ (d.kind === 'move' ? p.onEventDrop : p.onEventResize)?.({ event, oldEvent, revert });
189
+ }, []);
190
+ const onPointerDown = (e) => {
191
+ if (e.button !== 0)
192
+ return;
193
+ const t = e.target;
194
+ setMark(null);
195
+ if (t.closest('[data-nav]'))
196
+ return;
197
+ let d = null;
198
+ const chip = t.closest('[data-drag]');
199
+ const start = keyAt(e.clientX, e.clientY);
200
+ if (chip) {
201
+ const ev = byId.get(chip.dataset.eid ?? '');
202
+ const resize = !!t.closest('[data-resize]');
203
+ if (ev && start && (resize ? canResize(ev.ne.source) : canMove(ev.ne.source))) {
204
+ d = {
205
+ kind: resize ? 'resize' : 'move',
206
+ ev,
207
+ anchor: start,
208
+ hover: start,
209
+ x: e.clientX,
210
+ y: e.clientY,
211
+ active: false,
212
+ };
213
+ }
214
+ }
215
+ else if (selectable && t.closest('[data-date]') && start) {
216
+ d = { kind: 'select', anchor: start, hover: start, x: e.clientX, y: e.clientY, active: true };
217
+ setMark({ kind: 'sel', a: grid.valByKey.get(start) ?? 0, b: grid.valByKey.get(start) ?? 0 });
218
+ }
219
+ if (!d)
220
+ return;
221
+ drag.current = d;
222
+ const preview = (s) => {
223
+ const g = latest.current.grid;
224
+ const h = g.valByKey.get(s.hover) ?? 0;
225
+ const a = g.valByKey.get(s.anchor) ?? 0;
226
+ if (s.kind === 'select')
227
+ return setMark({ kind: 'sel', a: Math.min(a, h), b: Math.max(a, h) });
228
+ const ev = s.ev;
229
+ const shift = (v, n) => latest.current.props.dayjs(v).add(n, 'day').valueOf();
230
+ if (s.kind === 'move') {
231
+ const n = Math.round((h - a) / DAY);
232
+ return setMark({ kind: 'drop', a: shift(ev.sVal, n), b: shift(ev.eVal, n), id: String(ev.ne.id) });
233
+ }
234
+ return setMark({ kind: 'drop', a: ev.sVal, b: Math.max(h, ev.sVal), id: String(ev.ne.id) });
235
+ };
236
+ const move = (m) => {
237
+ const s = drag.current;
238
+ if (!s)
239
+ return;
240
+ if (!s.active) {
241
+ if (Math.hypot(m.clientX - s.x, m.clientY - s.y) < 4)
242
+ return;
243
+ s.active = true;
244
+ document.body.style.userSelect = 'none';
245
+ }
246
+ const k = keyAt(m.clientX, m.clientY);
247
+ if (k && k !== s.hover) {
248
+ s.hover = k;
249
+ preview(s);
250
+ }
251
+ };
252
+ const end = (m, cancelled = false) => {
253
+ window.removeEventListener('pointermove', move);
254
+ window.removeEventListener('pointerup', up);
255
+ window.removeEventListener('pointercancel', cancel);
256
+ window.removeEventListener('keydown', esc);
257
+ document.body.style.userSelect = '';
258
+ const s = drag.current;
259
+ drag.current = null;
260
+ if (s?.active && !cancelled && m) {
261
+ commit(s);
262
+ if (s.kind !== 'select') {
263
+ suppress.current = true;
264
+ setTimeout(() => {
265
+ suppress.current = false;
266
+ }, 0);
267
+ }
268
+ }
269
+ if (s?.kind !== 'select' || cancelled)
270
+ setMark(null);
271
+ };
272
+ const up = (m) => end(m);
273
+ const cancel = () => end(undefined, true);
274
+ const esc = (k) => k.key === 'Escape' && end(undefined, true);
275
+ window.addEventListener('pointermove', move);
276
+ window.addEventListener('pointerup', up);
277
+ window.addEventListener('pointercancel', cancel);
278
+ window.addEventListener('keydown', esc);
279
+ };
280
+ const onClick = (e) => {
281
+ const t = e.target;
282
+ if (suppress.current || t.closest('[data-more]'))
283
+ return;
284
+ const nav = t.closest('[data-nav]');
285
+ if (nav) {
286
+ const d = grid.byKey.get(nav.dataset.nav ?? '');
287
+ if (d) {
288
+ setMoreKey(null);
289
+ if (props.onNavLinkDayClick)
290
+ props.onNavLinkDayClick(d);
291
+ else
292
+ goTo('dayGridDay', d);
293
+ }
294
+ return;
295
+ }
296
+ const chip = t.closest('[data-eid]');
297
+ if (chip) {
298
+ const ev = byId.get(chip.dataset.eid ?? '');
299
+ if (ev)
300
+ props.onEventClick?.({ event: ev.ne.source, jsEvent: e });
301
+ setMoreKey(null);
302
+ return;
303
+ }
304
+ const cell = t.closest('[data-date]');
305
+ const d = cell && grid.byKey.get(cell.dataset.date ?? '');
306
+ if (d)
307
+ props.onDateClick?.({ date: d, allDay: true, jsEvent: e });
308
+ };
309
+ const onKeyDown = (e) => {
310
+ const cell = e.target;
311
+ const k = cell.dataset.date;
312
+ if (!k)
313
+ return;
314
+ if (e.key === 'Enter' || e.key === ' ') {
315
+ e.preventDefault();
316
+ cell.click();
317
+ return;
318
+ }
319
+ const step = { ArrowRight: 1, ArrowLeft: -1, ArrowDown: cols, ArrowUp: -cols }[e.key];
320
+ if (!step)
321
+ return;
322
+ e.preventDefault();
323
+ const next = grid.flat[grid.flat.indexOf(k) + step];
324
+ if (next)
325
+ rootRef.current?.querySelector(`[data-date="${next}"]`)?.focus();
326
+ };
327
+ const onFocus = (e) => {
328
+ const k = e.target.dataset.date;
329
+ if (k)
330
+ setFocusKey(k);
331
+ };
332
+ // ---- render
333
+ const ctx = {
334
+ eventContent: props.eventContent,
335
+ eventClassNames: props.eventClassNames,
336
+ eventTimeFormat: props.eventTimeFormat,
337
+ };
338
+ const colTpl = `repeat(${cols}, minmax(0, 1fr))`;
339
+ const wrapStyle = {
340
+ '--rbs-dg-head': `${HEAD_H}px`,
341
+ '--rbs-dg-lane': `${LANE_H}px`,
342
+ minHeight: fit ? grid.rows.length * 96 : undefined,
343
+ };
344
+ const headFmt = view === 'dayGridDay' ? 'dddd' : 'ddd';
345
+ const dayEvents = (key) => {
346
+ const v = grid.valByKey.get(key) ?? 0;
347
+ return visible
348
+ .filter(p => p.sVal <= v && v <= p.eVal)
349
+ .sort((a, b) => Number(b.ne.allDay) - Number(a.ne.allDay) || a.ne.start.valueOf() - b.ne.start.valueOf());
350
+ };
351
+ return (_jsxs("div", { ref: rootRef, className: `rbs-dg${fit ? ' rbs-dg--fit' : ''}`, style: wrapStyle, role: "grid", "aria-label": date.format('MMMM YYYY'), onPointerDown: onPointerDown, onClick: onClick, onKeyDown: onKeyDown, onFocus: onFocus, children: [_jsxs("div", { className: "rbs-dg-head", role: "row", children: [weekNumbers && _jsx("div", { className: "rbs-dg-wk" }), _jsx("div", { className: "rbs-dg-heads", style: { gridTemplateColumns: colTpl }, children: (grid.rows[0] ?? []).map(d => (_jsx("div", { className: "rbs-dg-hd", role: "columnheader", children: isMonth ? (d.format(headFmt)) : (_jsxs("button", { type: "button", className: "rbs-dg-hlink", "data-nav": d.format(KEY), tabIndex: -1, children: [d.format(headFmt), view === 'dayGridWeek' && _jsxs("span", { className: "rbs-dg-hnum", children: [" ", d.format('D')] })] })) }, d.day()))) })] }), _jsx("div", { className: "rbs-dg-rows", children: grid.rows.map((row, ri) => {
352
+ const { vals, layout } = rowLayouts[ri];
353
+ const minH = fit ? 64 : Math.max(96, HEAD_H + layout.rows * LANE_H + 8);
354
+ return (_jsxs("div", { ref: ri === 0 ? firstRow : undefined, className: "rbs-dg-row", role: "row", style: { minHeight: minH }, children: [weekNumbers && _jsx("div", { className: "rbs-dg-wk", children: isoWeek(vals[Math.min(vals.length - 1, 3)]) }), _jsxs("div", { className: "rbs-dg-body", children: [_jsx("div", { className: "rbs-dg-cells", style: { gridTemplateColumns: colTpl }, children: row.map((d, ci) => {
355
+ const k = d.format(KEY);
356
+ const v = vals[ci];
357
+ const isOther = isMonth && d.month() !== date.month();
358
+ const isToday = k === todayKey;
359
+ const x = props.dayCellClassNames?.({ date: d, isToday, isOther }) ?? EMPTY;
360
+ return (_jsx(DayCell, { dayKey: k, date: d, label: isMonth && d.date() === 1 ? d.format('MMM D') : d.format('D'), title: d.format('dddd, MMMM D, YYYY'), isToday: isToday, isOther: isOther, hidden: isOther && !showOther, focusable: k === focusable, selected: mark?.kind === 'sel' && v >= mark.a && v <= mark.b, dropping: mark?.kind === 'drop' && v >= mark.a && v <= mark.b, bgColor: bgByKey.get(k), className: Array.isArray(x) ? x.join(' ') : x }, k));
361
+ }) }), _jsxs("div", { className: "rbs-dg-layer", style: { gridTemplateColumns: colTpl }, children: [layout.shown.map(s => {
362
+ const id = String(s.ev.ne.id);
363
+ const src = s.ev.ne.source;
364
+ return (_jsx(EventChip, { ev: s.ev, view: view, isStart: s.isStart, isEnd: s.isEnd, col: s.colStart, span: s.colEnd - s.colStart + 1, lane: s.lane, canMove: canMove(src), canResize: canResize(src), dragging: mark?.kind === 'drop' && mark.id === id, now: now, ...ctx }, `${id}:${s.colStart}`));
365
+ }), layout.more.map((count, ci) => {
366
+ if (!count)
367
+ return null;
368
+ const k = row[ci].format(KEY);
369
+ return (_jsx(Popover
370
+ // biome-ignore lint/suspicious/noArrayIndexKey: column position is the identity
371
+ , { trigger: "click", placement: "bottom", className: "rbs-dg-pop", open: moreKey === k, onOpenChange: o => setMoreKey(o ? k : null), content: moreKey === k ? (_jsxs("div", { className: "rbs-dg-pop-in", children: [_jsx("button", { type: "button", className: "rbs-dg-pop-title", "data-nav": k, children: row[ci].format('dddd, MMM D') }), dayEvents(k).map(p => (_jsx(EventChip, { ev: p, view: view, isStart: true, isEnd: true, now: now, ...ctx }, String(p.ne.id))))] })) : null, children: _jsxs("button", { type: "button", className: "rbs-dg-more", "data-more": k, style: { gridColumn: ci + 1, gridRow: layout.rows }, "aria-haspopup": "dialog", "aria-expanded": moreKey === k, children: ["+", count, " more"] }) }, ci));
372
+ })] })] })] }, vals[0]));
373
+ }) })] }));
374
+ }
375
+ export default DayGridView;
@@ -0,0 +1,4 @@
1
+ import type { CalendarViewProps } from '../types';
2
+ declare function ListView({ view, date, events, dayjs, firstDay, eventTimeFormat, eventContent, eventClassNames, noEventsContent, onEventClick, onNavLinkDayClick, goTo, className, }: CalendarViewProps): import("react").JSX.Element;
3
+ declare const _default: import("react").MemoExoticComponent<typeof ListView>;
4
+ export default _default;
@@ -0,0 +1,56 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { memo, useCallback, useMemo } from 'react';
3
+ import { getViewRange, normalizeEvent, tipText } from '../utils';
4
+ import { bucketByDay, dayKey, timeText } from './list/bucket';
5
+ function onListKey(e) {
6
+ const step = e.key === 'ArrowDown' ? 1 : e.key === 'ArrowUp' ? -1 : 0;
7
+ if (!step)
8
+ return;
9
+ const rows = Array.from(e.currentTarget.querySelectorAll('.rbs-ls-row'));
10
+ const i = rows.indexOf(document.activeElement);
11
+ const next = rows[Math.max(0, Math.min(rows.length - 1, i + step))];
12
+ if (next) {
13
+ e.preventDefault();
14
+ next.focus();
15
+ }
16
+ }
17
+ function ListView({ view, date, events, dayjs, firstDay = 0, eventTimeFormat, eventContent, eventClassNames, noEventsContent, onEventClick, onNavLinkDayClick, goTo, className, }) {
18
+ const range = useMemo(() => getViewRange(view, date, firstDay), [view, date, firstDay]);
19
+ const normalized = useMemo(() => events.filter(e => e.display !== 'background' && e.display !== 'none').map(e => normalizeEvent(dayjs, e)), [events, dayjs]);
20
+ const buckets = useMemo(() => bucketByDay(normalized, range), [normalized, range]);
21
+ const days = useMemo(() => {
22
+ const out = [];
23
+ for (let d = range.start; d.isBefore(range.end); d = d.add(1, 'day'))
24
+ if (buckets.has(dayKey(d)))
25
+ out.push(d);
26
+ return out;
27
+ }, [range, buckets]);
28
+ const now = useMemo(() => dayjs(), [dayjs]);
29
+ const navTo = useCallback((d) => (onNavLinkDayClick ? onNavLinkDayClick(d) : goTo('timeGridDay', d)), [onNavLinkDayClick, goTo]);
30
+ if (days.length === 0) {
31
+ return _jsx("div", { className: "rbs-ls rbs-ls-empty", children: noEventsContent ?? 'No events to display' });
32
+ }
33
+ return (_jsx("div", { className: `rbs-ls ${className ?? ''}`, role: "list", "aria-label": "Events", onKeyDown: onListKey, children: days.map(day => {
34
+ const isToday = day.isSame(now, 'day');
35
+ return (_jsxs("div", { className: "rbs-ls-day", role: "listitem", children: [_jsx("h3", { className: `rbs-ls-heading${isToday ? ' rbs-ls-today' : ''}`, children: _jsx("button", { type: "button", className: "rbs-ls-nav", onClick: () => navTo(day), children: day.format('dddd, MMM D') }) }), _jsx("ul", { className: "rbs-ls-rows", children: (buckets.get(dayKey(day)) ?? []).map(e => {
36
+ const isStart = e.start.isSame(day, 'day');
37
+ const isEnd = !e.end.isAfter(day.add(1, 'day'));
38
+ const isPast = e.end.isBefore(now);
39
+ const tt = timeText(e, day, eventTimeFormat);
40
+ const arg = {
41
+ event: e.source,
42
+ view,
43
+ timeText: tt,
44
+ isStart,
45
+ isEnd,
46
+ isPast,
47
+ isFuture: e.start.isAfter(now),
48
+ isToday,
49
+ };
50
+ const extra = eventClassNames?.(arg);
51
+ const cls = [...(e.source.classNames ?? []), ...(Array.isArray(extra) ? extra : extra ? [extra] : [])];
52
+ return (_jsx("li", { children: _jsx("button", { type: "button", className: `rbs-ls-row${isPast ? ' rbs-ls-past' : ''} ${cls.join(' ')}`, "data-tip": tipText(e), "data-tip-color": e.source.color, onClick: ev => onEventClick?.({ event: e.source, jsEvent: ev }), children: eventContent ? (eventContent(arg)) : (_jsxs(_Fragment, { children: [_jsx("span", { className: "rbs-ls-time", children: tt }), _jsx("span", { className: "rbs-ls-dot", style: { background: e.source.color ?? 'var(--rbs-accent)' } }), _jsxs("span", { className: "rbs-ls-title", children: [e.source.title, !isStart && _jsx("span", { className: "rbs-ls-cont", children: " (cont.)" })] })] })) }) }, `${e.id}-${dayKey(day)}`));
53
+ }) })] }, dayKey(day)));
54
+ }) }));
55
+ }
56
+ export default memo(ListView);
@@ -0,0 +1,4 @@
1
+ import type { CalendarViewProps } from '../types';
2
+ declare function MultiMonthView(props: CalendarViewProps): import("react").JSX.Element;
3
+ declare const _default: import("react").MemoExoticComponent<typeof MultiMonthView>;
4
+ export default _default;
@@ -0,0 +1,36 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { memo, useMemo } from 'react';
3
+ import { getViewRange, normalizeEvent } from '../utils';
4
+ import { bucketByDay } from './list/bucket';
5
+ import Continuous from './multimonth/Continuous';
6
+ import MonthGrid from './multimonth/MonthGrid';
7
+ function MultiMonthView(props) {
8
+ const { view, date, events, dayjs, firstDay = 0, weekends = true, weekNumbers = false, className } = props;
9
+ const range = useMemo(() => getViewRange(view, date, firstDay), [view, date, firstDay]);
10
+ const normalized = useMemo(() => events.map(e => normalizeEvent(dayjs, e)), [events, dayjs]);
11
+ const buckets = useMemo(() => bucketByDay(normalized, range), [normalized, range]);
12
+ const bgBuckets = useMemo(() => bucketByDay(events.filter(e => e.display === 'background').map(e => normalizeEvent(dayjs, { ...e, display: 'auto' })), range), [events, dayjs, range]);
13
+ const today = useMemo(() => dayjs().startOf('day'), [dayjs]);
14
+ const shared = {
15
+ buckets,
16
+ bgBuckets,
17
+ today,
18
+ firstDay,
19
+ weekends,
20
+ weekNumbers,
21
+ compact: view === 'multiMonthYear',
22
+ eventClassNames: props.eventClassNames,
23
+ eventContent: props.eventContent,
24
+ view,
25
+ onDateClick: props.onDateClick,
26
+ onEventClick: props.onEventClick,
27
+ goTo: props.goTo,
28
+ dayCellClassNames: props.dayCellClassNames,
29
+ };
30
+ const months = useMemo(() => {
31
+ const y = date.startOf('year');
32
+ return Array.from({ length: 12 }, (_, i) => y.add(i, 'month'));
33
+ }, [date]);
34
+ return (_jsx("div", { className: `rbs-mm rbs-mm-${view} ${className ?? ''}`, children: view === 'multiMonthContinuous' ? (_jsx(Continuous, { range: range, shared: shared })) : (_jsx("div", { className: "rbs-mm-months", children: months.map(m => (_jsx(MonthGrid, { month: m, shared: shared }, m.valueOf()))) })) }));
35
+ }
36
+ export default memo(MultiMonthView);