@dxos/react-ui-calendar 0.8.4-staging.ac66bdf99f → 0.9.1-main.c7dcc2e112
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +102 -5
- package/dist/lib/browser/index.mjs +881 -107
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/translations.mjs +16 -0
- package/dist/lib/browser/translations.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +881 -107
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/translations.mjs +18 -0
- package/dist/lib/node-esm/translations.mjs.map +7 -0
- package/dist/types/src/components/Calendar/Calendar.d.ts +40 -21
- package/dist/types/src/components/Calendar/Calendar.d.ts.map +1 -1
- package/dist/types/src/components/Calendar/Calendar.stories.d.ts +4 -1
- package/dist/types/src/components/Calendar/Calendar.stories.d.ts.map +1 -1
- package/dist/types/src/components/Calendar/Week.d.ts +30 -0
- package/dist/types/src/components/Calendar/Week.d.ts.map +1 -0
- package/dist/types/src/components/Calendar/Weekdays.d.ts +18 -0
- package/dist/types/src/components/Calendar/Weekdays.d.ts.map +1 -0
- package/dist/types/src/components/Calendar/context.d.ts +40 -0
- package/dist/types/src/components/Calendar/context.d.ts.map +1 -0
- package/dist/types/src/components/Calendar/util.d.ts +47 -0
- package/dist/types/src/components/Calendar/util.d.ts.map +1 -1
- package/dist/types/src/components/Calendar/util.test.d.ts +2 -0
- package/dist/types/src/components/Calendar/util.test.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +1 -1
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +25 -19
- package/src/components/Calendar/Calendar.stories.tsx +63 -3
- package/src/components/Calendar/Calendar.tsx +483 -92
- package/src/components/Calendar/Week.tsx +488 -0
- package/src/components/Calendar/Weekdays.tsx +57 -0
- package/src/components/Calendar/context.ts +60 -0
- package/src/components/Calendar/util.test.ts +90 -0
- package/src/components/Calendar/util.ts +110 -1
|
@@ -1,26 +1,21 @@
|
|
|
1
1
|
// src/components/Calendar/Calendar.tsx
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { addDays as addDays3, format as format2, startOfDay as startOfDay3 } from "date-fns";
|
|
3
|
+
import React3, { forwardRef, useCallback as useCallback2, useEffect as useEffect2, useImperativeHandle, useMemo as useMemo3, useRef as useRef2, useState as useState2 } from "react";
|
|
5
4
|
import { useResizeDetector } from "react-resize-detector";
|
|
6
5
|
import { List } from "react-virtualized";
|
|
7
6
|
import { Event } from "@dxos/async";
|
|
8
7
|
import { IconButton, useTranslation } from "@dxos/react-ui";
|
|
9
|
-
import { composable, composableProps
|
|
8
|
+
import { composable as composable2, composableProps as composableProps2 } from "@dxos/react-ui";
|
|
9
|
+
import { mx as mx3 } from "@dxos/ui-theme";
|
|
10
|
+
import { translationKey } from "#translations";
|
|
10
11
|
|
|
11
|
-
// src/
|
|
12
|
-
|
|
13
|
-
var
|
|
14
|
-
{
|
|
15
|
-
"en-US": {
|
|
16
|
-
[translationKey]: {
|
|
17
|
-
"today.button": "Today"
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
];
|
|
12
|
+
// src/components/Calendar/context.ts
|
|
13
|
+
import { createContext } from "@radix-ui/react-context";
|
|
14
|
+
var [CalendarContextProvider, useCalendarContext] = createContext("Calendar");
|
|
22
15
|
|
|
23
16
|
// src/components/Calendar/util.ts
|
|
17
|
+
import { differenceInCalendarDays, startOfDay } from "date-fns";
|
|
18
|
+
var gridEpoch = /* @__PURE__ */ new Date("1970-01-01");
|
|
24
19
|
var getDate = (start2, weekNumber, dayOfWeek, weekStartsOn) => {
|
|
25
20
|
const result = new Date(start2);
|
|
26
21
|
const startDayOfWeek = start2.getDay();
|
|
@@ -28,49 +23,554 @@ var getDate = (start2, weekNumber, dayOfWeek, weekStartsOn) => {
|
|
|
28
23
|
result.setDate(start2.getDate() - adjustedStartDay + weekNumber * 7 + dayOfWeek);
|
|
29
24
|
return result;
|
|
30
25
|
};
|
|
26
|
+
var getRowIndex = (start2, date, weekStartsOn) => {
|
|
27
|
+
const startDayOfWeek = start2.getDay();
|
|
28
|
+
const adjustedStartDay = (startDayOfWeek === 0 ? 7 : startDayOfWeek) - weekStartsOn;
|
|
29
|
+
const row0Start = new Date(start2);
|
|
30
|
+
row0Start.setDate(start2.getDate() - adjustedStartDay);
|
|
31
|
+
return Math.floor(differenceInCalendarDays(date, row0Start) / 7);
|
|
32
|
+
};
|
|
31
33
|
var isSameDay = (date1, date2) => {
|
|
32
34
|
return !!date2 && date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth() && date1.getDate() === date2.getDate();
|
|
33
35
|
};
|
|
36
|
+
var MINUTES_PER_DAY = 24 * 60;
|
|
37
|
+
var SNAP_MINUTES = 15;
|
|
38
|
+
var minutesOfDay = (date) => (date.getTime() - startOfDay(date).getTime()) / 6e4;
|
|
39
|
+
var setMinutesOfDay = (date, minutes) => new Date(startOfDay(date).getTime() + minutes * 6e4);
|
|
40
|
+
var yToMinutes = (y, hourHeight) => y / hourHeight * 60;
|
|
41
|
+
var minutesToY = (minutes, hourHeight) => minutes / 60 * hourHeight;
|
|
42
|
+
var snapMinutes = (minutes, step = SNAP_MINUTES) => {
|
|
43
|
+
const snapped = Math.round(minutes / step) * step;
|
|
44
|
+
return Math.max(0, Math.min(MINUTES_PER_DAY, snapped));
|
|
45
|
+
};
|
|
46
|
+
var layoutDayEvents = (events) => {
|
|
47
|
+
const layout = /* @__PURE__ */ new Map();
|
|
48
|
+
const ordered = events.map((event, index) => ({
|
|
49
|
+
index,
|
|
50
|
+
start: event.start.getTime(),
|
|
51
|
+
end: event.end.getTime()
|
|
52
|
+
})).sort((a, b) => a.start - b.start);
|
|
53
|
+
let cluster = [];
|
|
54
|
+
let clusterMax = -Infinity;
|
|
55
|
+
const flush = () => {
|
|
56
|
+
if (cluster.length === 0) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const columnEnds = [];
|
|
60
|
+
const assigned = cluster.map(({ index, start: start2, end }) => {
|
|
61
|
+
let column = columnEnds.findIndex((columnEnd) => columnEnd <= start2);
|
|
62
|
+
if (column === -1) {
|
|
63
|
+
column = columnEnds.length;
|
|
64
|
+
}
|
|
65
|
+
columnEnds[column] = end;
|
|
66
|
+
return {
|
|
67
|
+
index,
|
|
68
|
+
column
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
const columnCount = columnEnds.length;
|
|
72
|
+
for (const { index, column } of assigned) {
|
|
73
|
+
layout.set(index, {
|
|
74
|
+
columnIndex: column,
|
|
75
|
+
columnCount
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
cluster = [];
|
|
79
|
+
clusterMax = -Infinity;
|
|
80
|
+
};
|
|
81
|
+
for (const entry of ordered) {
|
|
82
|
+
if (cluster.length > 0 && entry.start >= clusterMax) {
|
|
83
|
+
flush();
|
|
84
|
+
}
|
|
85
|
+
cluster.push(entry);
|
|
86
|
+
clusterMax = Math.max(clusterMax, entry.end);
|
|
87
|
+
}
|
|
88
|
+
flush();
|
|
89
|
+
return layout;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// src/components/Calendar/Week.tsx
|
|
93
|
+
import { addDays as addDays2, startOfDay as startOfDay2, startOfWeek as startOfWeek2 } from "date-fns";
|
|
94
|
+
import React2, { useCallback, useEffect, useLayoutEffect, useMemo as useMemo2, useRef, useState } from "react";
|
|
95
|
+
import { composable, composableProps } from "@dxos/react-ui";
|
|
96
|
+
import { mx as mx2 } from "@dxos/ui-theme";
|
|
97
|
+
|
|
98
|
+
// src/components/Calendar/Weekdays.tsx
|
|
99
|
+
import { addDays, format, startOfWeek } from "date-fns";
|
|
100
|
+
import React, { useMemo } from "react";
|
|
101
|
+
import { mx } from "@dxos/ui-theme";
|
|
102
|
+
var Weekdays = ({ weekStartsOn, columnWidth, gutter, dates }) => {
|
|
103
|
+
const labels = useMemo(() => {
|
|
104
|
+
const weekStart = startOfWeek(/* @__PURE__ */ new Date(), {
|
|
105
|
+
weekStartsOn
|
|
106
|
+
});
|
|
107
|
+
return Array.from({
|
|
108
|
+
length: 7
|
|
109
|
+
}, (_, index) => format(addDays(weekStart, index), "EEE"));
|
|
110
|
+
}, [
|
|
111
|
+
weekStartsOn
|
|
112
|
+
]);
|
|
113
|
+
const today = useMemo(() => /* @__PURE__ */ new Date(), []);
|
|
114
|
+
const columnTemplate = columnWidth ? `repeat(7, ${columnWidth}px)` : "repeat(7, 1fr)";
|
|
115
|
+
return /* @__PURE__ */ React.createElement("div", {
|
|
116
|
+
className: "grid w-full shrink-0",
|
|
117
|
+
style: {
|
|
118
|
+
gridTemplateColumns: gutter ? `${gutter}px ${columnTemplate}` : columnTemplate
|
|
119
|
+
}
|
|
120
|
+
}, gutter != null && /* @__PURE__ */ React.createElement("div", {
|
|
121
|
+
"aria-hidden": true
|
|
122
|
+
}), labels.map((label, index) => {
|
|
123
|
+
const date = dates?.[index];
|
|
124
|
+
const isToday = !!date && isSameDay(date, today);
|
|
125
|
+
return /* @__PURE__ */ React.createElement("div", {
|
|
126
|
+
key: index,
|
|
127
|
+
className: mx("flex flex-col items-center p-2 text-sm font-thin", isToday && "text-accent-text")
|
|
128
|
+
}, /* @__PURE__ */ React.createElement("span", null, label), date && /* @__PURE__ */ React.createElement("span", {
|
|
129
|
+
className: "text-lg font-normal tabular-nums"
|
|
130
|
+
}, date.getDate()));
|
|
131
|
+
}));
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// src/components/Calendar/Week.tsx
|
|
135
|
+
var CALENDAR_WEEK_NAME = "CalendarWeek";
|
|
136
|
+
var HOUR_HEIGHT = 48;
|
|
137
|
+
var GUTTER_WIDTH = 56;
|
|
138
|
+
var RESIZE_HANDLE = 6;
|
|
139
|
+
var MIN_DURATION = SNAP_MINUTES;
|
|
140
|
+
var INITIAL_HOUR = 8;
|
|
141
|
+
var CalendarWeek = composable(({ classNames, date, events = [], onEventCreate, onEventUpdate, ...props }, forwardedRef) => {
|
|
142
|
+
const { weekStartsOn, event: scrollEvent, setIndex } = useCalendarContext(CALENDAR_WEEK_NAME);
|
|
143
|
+
const today = useMemo2(() => /* @__PURE__ */ new Date(), []);
|
|
144
|
+
const [viewDate, setViewDate] = useState(() => date ?? today);
|
|
145
|
+
useEffect(() => {
|
|
146
|
+
if (date) {
|
|
147
|
+
setViewDate(date);
|
|
148
|
+
}
|
|
149
|
+
}, [
|
|
150
|
+
date
|
|
151
|
+
]);
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
return scrollEvent.on(({ date: date2 }) => setViewDate(date2));
|
|
154
|
+
}, [
|
|
155
|
+
scrollEvent
|
|
156
|
+
]);
|
|
157
|
+
const weekDays = useMemo2(() => {
|
|
158
|
+
const weekStart = startOfWeek2(viewDate, {
|
|
159
|
+
weekStartsOn
|
|
160
|
+
});
|
|
161
|
+
return Array.from({
|
|
162
|
+
length: 7
|
|
163
|
+
}, (_, index) => startOfDay2(addDays2(weekStart, index)));
|
|
164
|
+
}, [
|
|
165
|
+
viewDate,
|
|
166
|
+
weekStartsOn
|
|
167
|
+
]);
|
|
168
|
+
useEffect(() => {
|
|
169
|
+
setIndex(getRowIndex(gridEpoch, weekDays[0], weekStartsOn));
|
|
170
|
+
}, [
|
|
171
|
+
weekDays,
|
|
172
|
+
weekStartsOn,
|
|
173
|
+
setIndex
|
|
174
|
+
]);
|
|
175
|
+
const eventsByDay = useMemo2(() => {
|
|
176
|
+
const byDay = weekDays.map(() => []);
|
|
177
|
+
for (const event of events) {
|
|
178
|
+
const dayIndex = weekDays.findIndex((day) => isSameDay(day, event.start));
|
|
179
|
+
if (dayIndex >= 0) {
|
|
180
|
+
byDay[dayIndex].push(event);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return byDay;
|
|
184
|
+
}, [
|
|
185
|
+
events,
|
|
186
|
+
weekDays
|
|
187
|
+
]);
|
|
188
|
+
const scrollRef = useRef(null);
|
|
189
|
+
useLayoutEffect(() => {
|
|
190
|
+
scrollRef.current?.scrollTo({
|
|
191
|
+
top: minutesToY(INITIAL_HOUR * 60, HOUR_HEIGHT)
|
|
192
|
+
});
|
|
193
|
+
}, []);
|
|
194
|
+
const gestureRef = useRef(void 0);
|
|
195
|
+
const columnsRef = useRef([]);
|
|
196
|
+
const [draft, setDraft] = useState(void 0);
|
|
197
|
+
const pointerMinutes = useCallback((clientY) => {
|
|
198
|
+
const rect = columnsRef.current.find(Boolean)?.getBoundingClientRect();
|
|
199
|
+
if (!rect) {
|
|
200
|
+
return 0;
|
|
201
|
+
}
|
|
202
|
+
return Math.max(0, Math.min(MINUTES_PER_DAY, yToMinutes(clientY - rect.top, HOUR_HEIGHT)));
|
|
203
|
+
}, []);
|
|
204
|
+
const dayFromX = useCallback((clientX) => {
|
|
205
|
+
const index = columnsRef.current.findIndex((node) => {
|
|
206
|
+
const rect = node?.getBoundingClientRect();
|
|
207
|
+
return rect && clientX >= rect.left && clientX < rect.right;
|
|
208
|
+
});
|
|
209
|
+
return index >= 0 ? weekDays[index] : void 0;
|
|
210
|
+
}, [
|
|
211
|
+
weekDays
|
|
212
|
+
]);
|
|
213
|
+
const applyGesture = useCallback((clientX, clientY) => {
|
|
214
|
+
const gesture = gestureRef.current;
|
|
215
|
+
if (!gesture) {
|
|
216
|
+
return void 0;
|
|
217
|
+
}
|
|
218
|
+
const { kind, day, eventId, anchorMinutes, grabOffset, durationMinutes } = gesture;
|
|
219
|
+
const raw = pointerMinutes(clientY);
|
|
220
|
+
switch (kind) {
|
|
221
|
+
case "create": {
|
|
222
|
+
const focus = snapMinutes(raw);
|
|
223
|
+
const from = Math.min(anchorMinutes, focus);
|
|
224
|
+
const to = Math.max(anchorMinutes, focus);
|
|
225
|
+
const end = Math.max(to, from + MIN_DURATION);
|
|
226
|
+
return {
|
|
227
|
+
eventId,
|
|
228
|
+
start: setMinutesOfDay(day, from),
|
|
229
|
+
end: setMinutesOfDay(day, end)
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
case "move": {
|
|
233
|
+
const targetDay = dayFromX(clientX) ?? day;
|
|
234
|
+
let start2 = snapMinutes(raw - grabOffset);
|
|
235
|
+
start2 = Math.max(0, Math.min(MINUTES_PER_DAY - durationMinutes, start2));
|
|
236
|
+
return {
|
|
237
|
+
eventId,
|
|
238
|
+
start: setMinutesOfDay(targetDay, start2),
|
|
239
|
+
end: setMinutesOfDay(targetDay, start2 + durationMinutes)
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
case "resize-start": {
|
|
243
|
+
const start2 = Math.min(snapMinutes(raw), anchorMinutes - MIN_DURATION);
|
|
244
|
+
return {
|
|
245
|
+
eventId,
|
|
246
|
+
start: setMinutesOfDay(day, start2),
|
|
247
|
+
end: setMinutesOfDay(day, anchorMinutes)
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
case "resize-end": {
|
|
251
|
+
const end = Math.max(snapMinutes(raw), anchorMinutes + MIN_DURATION);
|
|
252
|
+
return {
|
|
253
|
+
eventId,
|
|
254
|
+
start: setMinutesOfDay(day, anchorMinutes),
|
|
255
|
+
end: setMinutesOfDay(day, end)
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}, [
|
|
260
|
+
dayFromX,
|
|
261
|
+
pointerMinutes
|
|
262
|
+
]);
|
|
263
|
+
const callbacksRef = useRef({
|
|
264
|
+
onEventCreate,
|
|
265
|
+
onEventUpdate
|
|
266
|
+
});
|
|
267
|
+
callbacksRef.current = {
|
|
268
|
+
onEventCreate,
|
|
269
|
+
onEventUpdate
|
|
270
|
+
};
|
|
271
|
+
const detachRef = useRef(() => {
|
|
272
|
+
});
|
|
273
|
+
const beginGesture = useCallback((gesture, ev) => {
|
|
274
|
+
ev.preventDefault();
|
|
275
|
+
ev.stopPropagation();
|
|
276
|
+
gestureRef.current = gesture;
|
|
277
|
+
setDraft(applyGesture(ev.clientX, ev.clientY));
|
|
278
|
+
const handleMove = (moveEv) => {
|
|
279
|
+
if (gestureRef.current) {
|
|
280
|
+
setDraft(applyGesture(moveEv.clientX, moveEv.clientY));
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
const handleUp = (upEv) => {
|
|
284
|
+
const active = gestureRef.current;
|
|
285
|
+
const result = applyGesture(upEv.clientX, upEv.clientY);
|
|
286
|
+
detachRef.current();
|
|
287
|
+
gestureRef.current = void 0;
|
|
288
|
+
setDraft(void 0);
|
|
289
|
+
if (active && result) {
|
|
290
|
+
if (active.kind === "create") {
|
|
291
|
+
callbacksRef.current.onEventCreate?.({
|
|
292
|
+
start: result.start,
|
|
293
|
+
end: result.end
|
|
294
|
+
});
|
|
295
|
+
} else if (result.eventId) {
|
|
296
|
+
callbacksRef.current.onEventUpdate?.({
|
|
297
|
+
id: result.eventId,
|
|
298
|
+
start: result.start,
|
|
299
|
+
end: result.end
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
detachRef.current = () => {
|
|
305
|
+
window.removeEventListener("pointermove", handleMove);
|
|
306
|
+
window.removeEventListener("pointerup", handleUp);
|
|
307
|
+
window.removeEventListener("pointercancel", handleUp);
|
|
308
|
+
};
|
|
309
|
+
window.addEventListener("pointermove", handleMove);
|
|
310
|
+
window.addEventListener("pointerup", handleUp);
|
|
311
|
+
window.addEventListener("pointercancel", handleUp);
|
|
312
|
+
}, [
|
|
313
|
+
applyGesture
|
|
314
|
+
]);
|
|
315
|
+
useEffect(() => () => detachRef.current(), []);
|
|
316
|
+
const handleColumnPointerDown = useCallback((day, event) => {
|
|
317
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
318
|
+
const anchor = snapMinutes(yToMinutes(event.clientY - rect.top, HOUR_HEIGHT));
|
|
319
|
+
beginGesture({
|
|
320
|
+
kind: "create",
|
|
321
|
+
day,
|
|
322
|
+
anchorMinutes: anchor,
|
|
323
|
+
grabOffset: 0,
|
|
324
|
+
durationMinutes: 0
|
|
325
|
+
}, event);
|
|
326
|
+
}, [
|
|
327
|
+
beginGesture
|
|
328
|
+
]);
|
|
329
|
+
const renderEvent = useCallback((event, day, start2, end, columnIndex, columnCount) => /* @__PURE__ */ React2.createElement(EventBlock, {
|
|
330
|
+
key: event.id,
|
|
331
|
+
event,
|
|
332
|
+
start: start2,
|
|
333
|
+
end,
|
|
334
|
+
columnIndex,
|
|
335
|
+
columnCount,
|
|
336
|
+
onMoveStart: (ev) => beginGesture({
|
|
337
|
+
kind: "move",
|
|
338
|
+
day,
|
|
339
|
+
eventId: event.id,
|
|
340
|
+
anchorMinutes: 0,
|
|
341
|
+
grabOffset: pointerMinutes(ev.clientY) - minutesOfDay(event.start),
|
|
342
|
+
durationMinutes: minutesOfDay(event.end) - minutesOfDay(event.start)
|
|
343
|
+
}, ev),
|
|
344
|
+
onResizeStart: (ev) => beginGesture({
|
|
345
|
+
kind: "resize-start",
|
|
346
|
+
day,
|
|
347
|
+
eventId: event.id,
|
|
348
|
+
anchorMinutes: minutesOfDay(event.end),
|
|
349
|
+
grabOffset: 0,
|
|
350
|
+
durationMinutes: 0
|
|
351
|
+
}, ev),
|
|
352
|
+
onResizeEnd: (ev) => beginGesture({
|
|
353
|
+
kind: "resize-end",
|
|
354
|
+
day,
|
|
355
|
+
eventId: event.id,
|
|
356
|
+
anchorMinutes: minutesOfDay(event.start),
|
|
357
|
+
grabOffset: 0,
|
|
358
|
+
durationMinutes: 0
|
|
359
|
+
}, ev)
|
|
360
|
+
}), [
|
|
361
|
+
beginGesture,
|
|
362
|
+
pointerMinutes
|
|
363
|
+
]);
|
|
364
|
+
const draggedEvent = draft?.eventId ? events.find((event) => event.id === draft.eventId) : void 0;
|
|
365
|
+
return /* @__PURE__ */ React2.createElement("div", {
|
|
366
|
+
...composableProps(props, {
|
|
367
|
+
classNames: [
|
|
368
|
+
"flex flex-col h-full w-full overflow-hidden outline-hidden",
|
|
369
|
+
classNames
|
|
370
|
+
]
|
|
371
|
+
}),
|
|
372
|
+
ref: forwardedRef
|
|
373
|
+
}, /* @__PURE__ */ React2.createElement(Weekdays, {
|
|
374
|
+
weekStartsOn,
|
|
375
|
+
gutter: GUTTER_WIDTH,
|
|
376
|
+
dates: weekDays
|
|
377
|
+
}), /* @__PURE__ */ React2.createElement("div", {
|
|
378
|
+
ref: scrollRef,
|
|
379
|
+
className: "flex-1 overflow-y-auto _scrollbar-thin"
|
|
380
|
+
}, /* @__PURE__ */ React2.createElement("div", {
|
|
381
|
+
className: "grid relative",
|
|
382
|
+
style: {
|
|
383
|
+
height: minutesToY(MINUTES_PER_DAY, HOUR_HEIGHT),
|
|
384
|
+
gridTemplateColumns: `${GUTTER_WIDTH}px repeat(7, 1fr)`
|
|
385
|
+
}
|
|
386
|
+
}, /* @__PURE__ */ React2.createElement("div", {
|
|
387
|
+
className: "relative"
|
|
388
|
+
}, Array.from({
|
|
389
|
+
length: 24
|
|
390
|
+
}, (_, hour) => /* @__PURE__ */ React2.createElement("div", {
|
|
391
|
+
key: hour,
|
|
392
|
+
className: "absolute right-1 -translate-y-1/2 text-xs text-description tabular-nums",
|
|
393
|
+
style: {
|
|
394
|
+
top: minutesToY(hour * 60, HOUR_HEIGHT)
|
|
395
|
+
}
|
|
396
|
+
}, hour === 0 ? "" : `${hour.toString().padStart(2, "0")}:00`))), weekDays.map((day, dayIndex) => {
|
|
397
|
+
const dayEvents = eventsByDay[dayIndex];
|
|
398
|
+
const layout = layoutDayEvents(dayEvents);
|
|
399
|
+
const isToday = isSameDay(day, today);
|
|
400
|
+
const draftHere = draft && isSameDay(day, draft.start) ? draft : void 0;
|
|
401
|
+
return /* @__PURE__ */ React2.createElement("div", {
|
|
402
|
+
key: day.toISOString(),
|
|
403
|
+
ref: (node) => {
|
|
404
|
+
columnsRef.current[dayIndex] = node;
|
|
405
|
+
},
|
|
406
|
+
"data-date": day.toISOString(),
|
|
407
|
+
className: mx2("relative border-l border-separator cursor-cell select-none", dayIndex === 6 && "border-r", isToday && "bg-primary-500/5"),
|
|
408
|
+
onPointerDown: (ev) => handleColumnPointerDown(day, ev)
|
|
409
|
+
}, Array.from({
|
|
410
|
+
length: 24
|
|
411
|
+
}, (_, hour) => /* @__PURE__ */ React2.createElement("div", {
|
|
412
|
+
key: hour,
|
|
413
|
+
className: "absolute inset-x-0 border-t border-separator/60",
|
|
414
|
+
style: {
|
|
415
|
+
top: minutesToY(hour * 60, HOUR_HEIGHT)
|
|
416
|
+
}
|
|
417
|
+
})), dayEvents.map((event, index) => {
|
|
418
|
+
const slot = layout.get(index) ?? {
|
|
419
|
+
columnIndex: 0,
|
|
420
|
+
columnCount: 1
|
|
421
|
+
};
|
|
422
|
+
const editing = draft && draft.eventId === event.id ? draft : void 0;
|
|
423
|
+
if (editing && !isSameDay(editing.start, day)) {
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
const start2 = editing ? editing.start : event.start;
|
|
427
|
+
const end = editing ? editing.end : event.end;
|
|
428
|
+
return renderEvent(event, day, start2, end, slot.columnIndex, slot.columnCount);
|
|
429
|
+
}), draftHere && draggedEvent && !dayEvents.some((event) => event.id === draggedEvent.id) && renderEvent(draggedEvent, day, draftHere.start, draftHere.end, 0, 1), draftHere && !draftHere.eventId && /* @__PURE__ */ React2.createElement(PendingBlock, {
|
|
430
|
+
start: draftHere.start,
|
|
431
|
+
end: draftHere.end
|
|
432
|
+
}));
|
|
433
|
+
}))));
|
|
434
|
+
});
|
|
435
|
+
CalendarWeek.displayName = CALENDAR_WEEK_NAME;
|
|
436
|
+
var formatTime = (date) => {
|
|
437
|
+
const minutes = minutesOfDay(date);
|
|
438
|
+
const hour = Math.floor(minutes / 60);
|
|
439
|
+
const minute = Math.round(minutes % 60);
|
|
440
|
+
return `${hour.toString().padStart(2, "0")}:${minute.toString().padStart(2, "0")}`;
|
|
441
|
+
};
|
|
442
|
+
var EventBlock = ({ event, start: start2, end, columnIndex, columnCount, onMoveStart, onResizeStart, onResizeEnd }) => {
|
|
443
|
+
const top = minutesToY(minutesOfDay(start2), HOUR_HEIGHT);
|
|
444
|
+
const height = Math.max(minutesToY(minutesOfDay(end) - minutesOfDay(start2), HOUR_HEIGHT), RESIZE_HANDLE * 2);
|
|
445
|
+
const widthPct = 100 / columnCount;
|
|
446
|
+
return /* @__PURE__ */ React2.createElement("div", {
|
|
447
|
+
className: "absolute rounded-sm bg-primary-500/80 text-inverse-fg overflow-hidden cursor-move shadow-sm",
|
|
448
|
+
style: {
|
|
449
|
+
top,
|
|
450
|
+
height,
|
|
451
|
+
left: `calc(${columnIndex * widthPct}% + 1px)`,
|
|
452
|
+
width: `calc(${widthPct}% - 2px)`
|
|
453
|
+
},
|
|
454
|
+
onPointerDown: onMoveStart
|
|
455
|
+
}, /* @__PURE__ */ React2.createElement("div", {
|
|
456
|
+
className: "absolute inset-x-0 top-0 cursor-ns-resize",
|
|
457
|
+
style: {
|
|
458
|
+
height: RESIZE_HANDLE
|
|
459
|
+
},
|
|
460
|
+
onPointerDown: onResizeStart
|
|
461
|
+
}), /* @__PURE__ */ React2.createElement("div", {
|
|
462
|
+
className: "px-1 py-0.5 text-xs leading-tight"
|
|
463
|
+
}, /* @__PURE__ */ React2.createElement("div", {
|
|
464
|
+
className: "font-medium truncate"
|
|
465
|
+
}, event.title ?? "(untitled)")), /* @__PURE__ */ React2.createElement("div", {
|
|
466
|
+
className: "absolute inset-x-0 bottom-0 cursor-ns-resize",
|
|
467
|
+
style: {
|
|
468
|
+
height: RESIZE_HANDLE
|
|
469
|
+
},
|
|
470
|
+
onPointerDown: onResizeEnd
|
|
471
|
+
}));
|
|
472
|
+
};
|
|
473
|
+
var PendingBlock = ({ start: start2, end }) => {
|
|
474
|
+
const top = minutesToY(minutesOfDay(start2), HOUR_HEIGHT);
|
|
475
|
+
const height = minutesToY(minutesOfDay(end) - minutesOfDay(start2), HOUR_HEIGHT);
|
|
476
|
+
return /* @__PURE__ */ React2.createElement("div", {
|
|
477
|
+
className: "absolute inset-x-0 rounded bg-primary-500/40 border border-primary-500 pointer-events-none",
|
|
478
|
+
style: {
|
|
479
|
+
top,
|
|
480
|
+
height
|
|
481
|
+
}
|
|
482
|
+
}, /* @__PURE__ */ React2.createElement("div", {
|
|
483
|
+
className: "px-1 py-0.5 text-xs tabular-nums text-inverse-fg"
|
|
484
|
+
}, formatTime(start2), "\u2013", formatTime(end)));
|
|
485
|
+
};
|
|
34
486
|
|
|
35
487
|
// src/components/Calendar/Calendar.tsx
|
|
36
488
|
var maxRows = 50 * 100;
|
|
37
|
-
var start =
|
|
38
|
-
var size =
|
|
489
|
+
var start = gridEpoch;
|
|
490
|
+
var size = 40;
|
|
39
491
|
var defaultWidth = 7 * size;
|
|
40
|
-
var
|
|
492
|
+
var EDGE_SCROLL_ZONE = 32;
|
|
493
|
+
var EDGE_SCROLL_MAX_SPEED = 12;
|
|
494
|
+
var DATE_CLASS_NAMES = {
|
|
495
|
+
current: "ring-2 ring-primary-500",
|
|
496
|
+
today: "border-2 border-amber-500 bg-amber-500/50 text-inverse-fg",
|
|
497
|
+
busy: "border border-green-700",
|
|
498
|
+
starred: "border-2 border-dashed border-amber-500"
|
|
499
|
+
};
|
|
500
|
+
var makeRange = (a, b) => {
|
|
501
|
+
const dayA = startOfDay3(a);
|
|
502
|
+
const dayB = startOfDay3(b);
|
|
503
|
+
return dayA <= dayB ? {
|
|
504
|
+
from: dayA,
|
|
505
|
+
to: dayB
|
|
506
|
+
} : {
|
|
507
|
+
from: dayB,
|
|
508
|
+
to: dayA
|
|
509
|
+
};
|
|
510
|
+
};
|
|
511
|
+
var isInRange = (date, range) => {
|
|
512
|
+
if (!range) {
|
|
513
|
+
return false;
|
|
514
|
+
}
|
|
515
|
+
const day = startOfDay3(date).getTime();
|
|
516
|
+
return day >= range.from.getTime() && day <= range.to.getTime();
|
|
517
|
+
};
|
|
518
|
+
var cellDate = (el) => {
|
|
519
|
+
let current = el;
|
|
520
|
+
while (current && current !== document.body) {
|
|
521
|
+
const iso = current.getAttribute?.("data-date");
|
|
522
|
+
if (iso) {
|
|
523
|
+
return new Date(iso);
|
|
524
|
+
}
|
|
525
|
+
current = current.parentElement;
|
|
526
|
+
}
|
|
527
|
+
return void 0;
|
|
528
|
+
};
|
|
41
529
|
var CalendarRoot = /* @__PURE__ */ forwardRef(({ children, weekStartsOn = 1 }, forwardedRef) => {
|
|
42
|
-
const event =
|
|
43
|
-
const [selected, setSelected] =
|
|
44
|
-
const [index, setIndex] =
|
|
530
|
+
const event = useMemo3(() => new Event(), []);
|
|
531
|
+
const [selected, setSelected] = useState2();
|
|
532
|
+
const [index, setIndex] = useState2();
|
|
533
|
+
const [range, setRange] = useState2();
|
|
534
|
+
const [pendingRange, setPendingRange] = useState2();
|
|
45
535
|
useImperativeHandle(forwardedRef, () => ({
|
|
46
536
|
scrollTo: (date) => {
|
|
47
537
|
event.emit({
|
|
48
538
|
type: "scroll",
|
|
49
539
|
date
|
|
50
540
|
});
|
|
541
|
+
},
|
|
542
|
+
select: (date) => {
|
|
543
|
+
event.emit({
|
|
544
|
+
type: "select",
|
|
545
|
+
date
|
|
546
|
+
});
|
|
51
547
|
}
|
|
52
548
|
}), [
|
|
53
549
|
event
|
|
54
550
|
]);
|
|
55
|
-
return /* @__PURE__ */
|
|
551
|
+
return /* @__PURE__ */ React3.createElement(CalendarContextProvider, {
|
|
56
552
|
weekStartsOn,
|
|
57
553
|
event,
|
|
58
554
|
index,
|
|
59
555
|
setIndex,
|
|
60
556
|
selected,
|
|
61
|
-
setSelected
|
|
557
|
+
setSelected,
|
|
558
|
+
range,
|
|
559
|
+
setRange,
|
|
560
|
+
pendingRange,
|
|
561
|
+
setPendingRange
|
|
62
562
|
}, children);
|
|
63
563
|
});
|
|
64
564
|
var CALENDAR_TOOLBAR_NAME = "CalendarHeader";
|
|
65
|
-
var CalendarToolbar =
|
|
565
|
+
var CalendarToolbar = composable2(({ classNames, ...props }, forwardedRef) => {
|
|
66
566
|
const { t } = useTranslation(translationKey);
|
|
67
567
|
const { weekStartsOn, event, index, selected } = useCalendarContext(CALENDAR_TOOLBAR_NAME);
|
|
68
|
-
const top =
|
|
568
|
+
const top = useMemo3(() => getDate(start, index ?? 0, 6, weekStartsOn), [
|
|
69
569
|
index,
|
|
70
570
|
weekStartsOn
|
|
71
571
|
]);
|
|
72
|
-
const today =
|
|
73
|
-
const handleToday =
|
|
572
|
+
const today = useMemo3(() => /* @__PURE__ */ new Date(), []);
|
|
573
|
+
const handleToday = useCallback2(() => {
|
|
74
574
|
event.emit({
|
|
75
575
|
type: "scroll",
|
|
76
576
|
date: today
|
|
@@ -80,100 +580,358 @@ var CalendarToolbar = composable(({ classNames, ...props }, forwardedRef) => {
|
|
|
80
580
|
start,
|
|
81
581
|
today
|
|
82
582
|
]);
|
|
83
|
-
return /* @__PURE__ */
|
|
84
|
-
...
|
|
583
|
+
return /* @__PURE__ */ React3.createElement("div", {
|
|
584
|
+
...composableProps2(props, {
|
|
85
585
|
role: "none",
|
|
86
586
|
classNames: [
|
|
87
587
|
"shrink-0 grid! grid-cols-3 items-center bg-toolbar-surface",
|
|
88
588
|
classNames
|
|
89
589
|
]
|
|
90
590
|
}),
|
|
91
|
-
ref: forwardedRef
|
|
92
|
-
|
|
93
|
-
width: defaultWidth
|
|
94
|
-
}
|
|
95
|
-
}, /* @__PURE__ */ React.createElement("div", {
|
|
591
|
+
ref: forwardedRef
|
|
592
|
+
}, /* @__PURE__ */ React3.createElement("div", {
|
|
96
593
|
className: "flex justify-start"
|
|
97
|
-
}, /* @__PURE__ */
|
|
594
|
+
}, /* @__PURE__ */ React3.createElement(IconButton, {
|
|
98
595
|
variant: "ghost",
|
|
99
596
|
icon: "ph--calendar--regular",
|
|
100
597
|
iconOnly: true,
|
|
101
598
|
classNames: "aspect-square",
|
|
102
599
|
label: t("today.button"),
|
|
103
600
|
onClick: handleToday
|
|
104
|
-
})), /* @__PURE__ */
|
|
601
|
+
})), /* @__PURE__ */ React3.createElement("div", {
|
|
105
602
|
className: "flex justify-center p-2 text-description"
|
|
106
|
-
},
|
|
603
|
+
}, format2(selected ?? top, "MMMM")), /* @__PURE__ */ React3.createElement("div", {
|
|
107
604
|
className: "flex justify-end p-2 text-description"
|
|
108
605
|
}, (selected ?? top).getFullYear()));
|
|
109
606
|
});
|
|
110
607
|
CalendarToolbar.displayName = CALENDAR_TOOLBAR_NAME;
|
|
111
608
|
var CALENDAR_GRID_NAME = "CalendarGrid";
|
|
112
|
-
var CalendarGrid =
|
|
113
|
-
const { weekStartsOn, event, setIndex, selected, setSelected } = useCalendarContext(CALENDAR_GRID_NAME);
|
|
609
|
+
var CalendarGrid = composable2(({ classNames, rows, dates = [], initialDate, scrollMargin = 2, onSelect, onSelectRange, ...props }, forwardedRef) => {
|
|
610
|
+
const { weekStartsOn, event, setIndex, selected, setSelected, range, setRange, pendingRange, setPendingRange } = useCalendarContext(CALENDAR_GRID_NAME);
|
|
114
611
|
const { ref: containerRef, width = 0, height = 0 } = useResizeDetector();
|
|
115
612
|
const maxHeight = rows ? rows * size : void 0;
|
|
116
|
-
const listRef =
|
|
117
|
-
const
|
|
118
|
-
const
|
|
613
|
+
const listRef = useRef2(null);
|
|
614
|
+
const gridRef = useRef2(null);
|
|
615
|
+
const today = useMemo3(() => /* @__PURE__ */ new Date(), []);
|
|
616
|
+
const dateMarkers = useMemo3(() => {
|
|
617
|
+
const markers = /* @__PURE__ */ new Map();
|
|
618
|
+
for (const { startDate, endDate, tag = "busy" } of dates) {
|
|
619
|
+
const end = endDate ? startOfDay3(endDate) : startOfDay3(startDate);
|
|
620
|
+
for (let date = startOfDay3(startDate); date <= end; date = addDays3(date, 1)) {
|
|
621
|
+
const iso = date.toISOString();
|
|
622
|
+
if (markers.get(iso) !== "star") {
|
|
623
|
+
markers.set(iso, tag);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
return markers;
|
|
628
|
+
}, [
|
|
119
629
|
dates
|
|
120
630
|
]);
|
|
121
|
-
const
|
|
122
|
-
|
|
631
|
+
const getMarker = useCallback2((date) => {
|
|
632
|
+
const iso = startOfDay3(date).toISOString();
|
|
633
|
+
const tag = dateMarkers.get(iso);
|
|
634
|
+
return tag ? {
|
|
635
|
+
tag
|
|
636
|
+
} : void 0;
|
|
637
|
+
}, [
|
|
638
|
+
dateMarkers
|
|
123
639
|
]);
|
|
124
|
-
const [initialized, setInitialized] =
|
|
125
|
-
|
|
126
|
-
const index =
|
|
127
|
-
listRef.current?.scrollToRow(index);
|
|
640
|
+
const [initialized, setInitialized] = useState2(false);
|
|
641
|
+
useEffect2(() => {
|
|
642
|
+
const index = getRowIndex(start, initialDate ?? today, weekStartsOn);
|
|
643
|
+
listRef.current?.scrollToRow(Math.max(0, index - scrollMargin));
|
|
128
644
|
}, [
|
|
129
645
|
initialized,
|
|
130
646
|
start,
|
|
131
|
-
today
|
|
647
|
+
today,
|
|
648
|
+
initialDate,
|
|
649
|
+
weekStartsOn,
|
|
650
|
+
scrollMargin
|
|
132
651
|
]);
|
|
133
|
-
|
|
652
|
+
useEffect2(() => {
|
|
134
653
|
return event.on((event2) => {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const index = differenceInWeeks(event2.date, start);
|
|
138
|
-
listRef.current?.scrollToRow(index);
|
|
139
|
-
break;
|
|
140
|
-
}
|
|
654
|
+
if (event2.type === "select") {
|
|
655
|
+
setSelected(event2.date);
|
|
141
656
|
}
|
|
657
|
+
const index = getRowIndex(start, event2.date, weekStartsOn);
|
|
658
|
+
listRef.current?.scrollToRow(Math.max(0, index - scrollMargin));
|
|
142
659
|
});
|
|
143
660
|
}, [
|
|
144
|
-
event
|
|
661
|
+
event,
|
|
662
|
+
start,
|
|
663
|
+
weekStartsOn,
|
|
664
|
+
scrollMargin,
|
|
665
|
+
setSelected
|
|
145
666
|
]);
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
667
|
+
const anchorRef = useRef2(void 0);
|
|
668
|
+
const focusRef = useRef2(void 0);
|
|
669
|
+
const draggingRef = useRef2(false);
|
|
670
|
+
const pointerXRef = useRef2(0);
|
|
671
|
+
const pointerYRef = useRef2(0);
|
|
672
|
+
const scrollTopRef = useRef2(0);
|
|
673
|
+
const scrollRafRef = useRef2(void 0);
|
|
674
|
+
const scrollIntoView = useCallback2((date) => {
|
|
675
|
+
const targetRow = getRowIndex(start, date, weekStartsOn);
|
|
676
|
+
const visibleHeight = maxHeight ?? height;
|
|
677
|
+
if (!visibleHeight) {
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
const firstFullyVisibleRow = Math.ceil(scrollTopRef.current / size);
|
|
681
|
+
const lastFullyVisibleRow = Math.floor((scrollTopRef.current + visibleHeight) / size) - 1;
|
|
682
|
+
if (targetRow < firstFullyVisibleRow) {
|
|
683
|
+
listRef.current?.scrollToPosition(targetRow * size);
|
|
684
|
+
} else if (targetRow > lastFullyVisibleRow) {
|
|
685
|
+
listRef.current?.scrollToPosition(Math.max(0, (targetRow + 1) * size - visibleHeight));
|
|
686
|
+
}
|
|
687
|
+
}, [
|
|
688
|
+
height,
|
|
689
|
+
maxHeight,
|
|
690
|
+
weekStartsOn
|
|
691
|
+
]);
|
|
692
|
+
const updateRangeFromAnchor = useCallback2((focus, fireRange = false) => {
|
|
693
|
+
const anchor = anchorRef.current;
|
|
694
|
+
if (!anchor) {
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
focusRef.current = focus;
|
|
698
|
+
if (isSameDay(anchor, focus)) {
|
|
699
|
+
setRange(void 0);
|
|
700
|
+
setSelected(anchor);
|
|
701
|
+
} else {
|
|
702
|
+
setSelected(void 0);
|
|
703
|
+
const committed = makeRange(anchor, focus);
|
|
704
|
+
setRange(committed);
|
|
705
|
+
if (fireRange) {
|
|
706
|
+
onSelectRange?.({
|
|
707
|
+
range: committed
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
}, [
|
|
712
|
+
onSelectRange,
|
|
713
|
+
setRange,
|
|
714
|
+
setSelected
|
|
715
|
+
]);
|
|
716
|
+
const prevSelectedRef = useRef2(void 0);
|
|
717
|
+
const handleDayPointerDown = useCallback2((date, ev) => {
|
|
718
|
+
ev.preventDefault();
|
|
719
|
+
prevSelectedRef.current = selected;
|
|
720
|
+
anchorRef.current = date;
|
|
721
|
+
focusRef.current = date;
|
|
722
|
+
draggingRef.current = true;
|
|
723
|
+
setRange(void 0);
|
|
724
|
+
setPendingRange(void 0);
|
|
725
|
+
setSelected(date);
|
|
726
|
+
gridRef.current?.focus({
|
|
727
|
+
preventScroll: true
|
|
155
728
|
});
|
|
156
|
-
}, [
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
729
|
+
}, [
|
|
730
|
+
selected,
|
|
731
|
+
setPendingRange,
|
|
732
|
+
setRange,
|
|
733
|
+
setSelected
|
|
734
|
+
]);
|
|
735
|
+
const handleDayPointerEnter = useCallback2((date) => {
|
|
736
|
+
if (!draggingRef.current) {
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
const anchor = anchorRef.current;
|
|
740
|
+
if (!anchor) {
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
focusRef.current = date;
|
|
744
|
+
setSelected(void 0);
|
|
745
|
+
setPendingRange(makeRange(anchor, date));
|
|
746
|
+
}, [
|
|
747
|
+
setPendingRange,
|
|
748
|
+
setSelected
|
|
749
|
+
]);
|
|
750
|
+
const handleDayPointerUp = useCallback2((date) => {
|
|
751
|
+
const anchor = anchorRef.current;
|
|
752
|
+
const wasDragging = draggingRef.current;
|
|
753
|
+
draggingRef.current = false;
|
|
754
|
+
setPendingRange(void 0);
|
|
755
|
+
if (!wasDragging || !anchor) {
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
focusRef.current = date;
|
|
759
|
+
if (isSameDay(anchor, date)) {
|
|
760
|
+
if (prevSelectedRef.current && isSameDay(prevSelectedRef.current, date)) {
|
|
761
|
+
setSelected(void 0);
|
|
762
|
+
anchorRef.current = void 0;
|
|
763
|
+
focusRef.current = void 0;
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
setSelected(anchor);
|
|
767
|
+
onSelect?.({
|
|
768
|
+
date
|
|
769
|
+
});
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
const committed = makeRange(anchor, date);
|
|
773
|
+
setRange(committed);
|
|
774
|
+
onSelectRange?.({
|
|
775
|
+
range: committed
|
|
161
776
|
});
|
|
162
777
|
}, [
|
|
163
|
-
onSelect
|
|
778
|
+
onSelect,
|
|
779
|
+
onSelectRange,
|
|
780
|
+
setPendingRange,
|
|
781
|
+
setRange,
|
|
782
|
+
setSelected
|
|
783
|
+
]);
|
|
784
|
+
useEffect2(() => {
|
|
785
|
+
const cancel = () => {
|
|
786
|
+
if (draggingRef.current) {
|
|
787
|
+
draggingRef.current = false;
|
|
788
|
+
setPendingRange(void 0);
|
|
789
|
+
}
|
|
790
|
+
};
|
|
791
|
+
window.addEventListener("pointerup", cancel);
|
|
792
|
+
window.addEventListener("pointercancel", cancel);
|
|
793
|
+
return () => {
|
|
794
|
+
window.removeEventListener("pointerup", cancel);
|
|
795
|
+
window.removeEventListener("pointercancel", cancel);
|
|
796
|
+
};
|
|
797
|
+
}, [
|
|
798
|
+
setPendingRange
|
|
799
|
+
]);
|
|
800
|
+
const tickEdgeScroll = useCallback2(() => {
|
|
801
|
+
scrollRafRef.current = void 0;
|
|
802
|
+
if (!draggingRef.current) {
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
const rect = containerRef.current?.getBoundingClientRect();
|
|
806
|
+
if (!rect) {
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
const y = pointerYRef.current;
|
|
810
|
+
let delta = 0;
|
|
811
|
+
if (y < rect.top + EDGE_SCROLL_ZONE) {
|
|
812
|
+
delta = -EDGE_SCROLL_MAX_SPEED * Math.min(1, Math.max(0, (rect.top + EDGE_SCROLL_ZONE - y) / EDGE_SCROLL_ZONE));
|
|
813
|
+
} else if (y > rect.bottom - EDGE_SCROLL_ZONE) {
|
|
814
|
+
delta = EDGE_SCROLL_MAX_SPEED * Math.min(1, Math.max(0, (y - (rect.bottom - EDGE_SCROLL_ZONE)) / EDGE_SCROLL_ZONE));
|
|
815
|
+
}
|
|
816
|
+
if (delta !== 0) {
|
|
817
|
+
const newScroll = Math.max(0, scrollTopRef.current + delta);
|
|
818
|
+
listRef.current?.scrollToPosition(newScroll);
|
|
819
|
+
const date = cellDate(document.elementFromPoint(pointerXRef.current, y));
|
|
820
|
+
const anchor = anchorRef.current;
|
|
821
|
+
if (date && anchor) {
|
|
822
|
+
focusRef.current = date;
|
|
823
|
+
if (isSameDay(anchor, date)) {
|
|
824
|
+
setPendingRange(void 0);
|
|
825
|
+
setSelected(anchor);
|
|
826
|
+
} else {
|
|
827
|
+
setSelected(void 0);
|
|
828
|
+
setPendingRange(makeRange(anchor, date));
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
scrollRafRef.current = requestAnimationFrame(tickEdgeScroll);
|
|
832
|
+
}
|
|
833
|
+
}, [
|
|
834
|
+
containerRef,
|
|
835
|
+
setPendingRange,
|
|
836
|
+
setSelected
|
|
837
|
+
]);
|
|
838
|
+
useEffect2(() => {
|
|
839
|
+
const handleMove = (ev) => {
|
|
840
|
+
if (!draggingRef.current) {
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
pointerXRef.current = ev.clientX;
|
|
844
|
+
pointerYRef.current = ev.clientY;
|
|
845
|
+
if (scrollRafRef.current === void 0) {
|
|
846
|
+
scrollRafRef.current = requestAnimationFrame(tickEdgeScroll);
|
|
847
|
+
}
|
|
848
|
+
};
|
|
849
|
+
window.addEventListener("pointermove", handleMove);
|
|
850
|
+
return () => {
|
|
851
|
+
window.removeEventListener("pointermove", handleMove);
|
|
852
|
+
if (scrollRafRef.current !== void 0) {
|
|
853
|
+
cancelAnimationFrame(scrollRafRef.current);
|
|
854
|
+
scrollRafRef.current = void 0;
|
|
855
|
+
}
|
|
856
|
+
};
|
|
857
|
+
}, [
|
|
858
|
+
tickEdgeScroll
|
|
859
|
+
]);
|
|
860
|
+
const handleKeyDown = useCallback2((ev) => {
|
|
861
|
+
let dx = 0;
|
|
862
|
+
switch (ev.key) {
|
|
863
|
+
case "ArrowLeft":
|
|
864
|
+
dx = -1;
|
|
865
|
+
break;
|
|
866
|
+
case "ArrowRight":
|
|
867
|
+
dx = 1;
|
|
868
|
+
break;
|
|
869
|
+
case "ArrowUp":
|
|
870
|
+
dx = -7;
|
|
871
|
+
break;
|
|
872
|
+
case "ArrowDown":
|
|
873
|
+
dx = 7;
|
|
874
|
+
break;
|
|
875
|
+
default:
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
ev.preventDefault();
|
|
879
|
+
if (ev.shiftKey) {
|
|
880
|
+
let anchor = anchorRef.current;
|
|
881
|
+
let focus = focusRef.current;
|
|
882
|
+
if (!anchor) {
|
|
883
|
+
if (selected) {
|
|
884
|
+
anchor = startOfDay3(selected);
|
|
885
|
+
focus = anchor;
|
|
886
|
+
} else if (range) {
|
|
887
|
+
anchor = range.from;
|
|
888
|
+
focus = range.to;
|
|
889
|
+
} else {
|
|
890
|
+
anchor = startOfDay3(today);
|
|
891
|
+
focus = anchor;
|
|
892
|
+
}
|
|
893
|
+
anchorRef.current = anchor;
|
|
894
|
+
focusRef.current = focus;
|
|
895
|
+
}
|
|
896
|
+
const newFocus = addDays3(focus ?? anchor, dx);
|
|
897
|
+
updateRangeFromAnchor(newFocus, true);
|
|
898
|
+
scrollIntoView(newFocus);
|
|
899
|
+
} else {
|
|
900
|
+
const current = selected ?? focusRef.current ?? anchorRef.current ?? today;
|
|
901
|
+
const next = addDays3(startOfDay3(current), dx);
|
|
902
|
+
anchorRef.current = next;
|
|
903
|
+
focusRef.current = next;
|
|
904
|
+
setRange(void 0);
|
|
905
|
+
setPendingRange(void 0);
|
|
906
|
+
setSelected(next);
|
|
907
|
+
onSelect?.({
|
|
908
|
+
date: next
|
|
909
|
+
});
|
|
910
|
+
scrollIntoView(next);
|
|
911
|
+
}
|
|
912
|
+
}, [
|
|
913
|
+
onSelect,
|
|
914
|
+
range,
|
|
915
|
+
scrollIntoView,
|
|
916
|
+
selected,
|
|
917
|
+
setPendingRange,
|
|
918
|
+
setRange,
|
|
919
|
+
setSelected,
|
|
920
|
+
today,
|
|
921
|
+
updateRangeFromAnchor
|
|
164
922
|
]);
|
|
165
|
-
const
|
|
923
|
+
const activeRange = pendingRange ?? range;
|
|
924
|
+
const handleScroll = useCallback2((info) => {
|
|
925
|
+
scrollTopRef.current = info.scrollTop;
|
|
166
926
|
setIndex(Math.round(info.scrollTop / size));
|
|
167
927
|
}, []);
|
|
168
|
-
const rowRenderer =
|
|
169
|
-
const getBgColor = (date) => date.getMonth() % 2 === 0
|
|
170
|
-
return /* @__PURE__ */
|
|
928
|
+
const rowRenderer = useCallback2(({ key, index, style }) => {
|
|
929
|
+
const getBgColor = (date) => date.getMonth() % 2 === 0 ? "bg-group-surface" : "bg-group-alt-surface";
|
|
930
|
+
return /* @__PURE__ */ React3.createElement("div", {
|
|
171
931
|
key,
|
|
172
|
-
role: "none",
|
|
173
932
|
style,
|
|
174
933
|
className: "grid"
|
|
175
|
-
}, /* @__PURE__ */
|
|
176
|
-
role: "none",
|
|
934
|
+
}, /* @__PURE__ */ React3.createElement("div", {
|
|
177
935
|
className: "grid grid-cols-7 bg-input-surface",
|
|
178
936
|
style: {
|
|
179
937
|
gridTemplateColumns: `repeat(7, ${size}px)`
|
|
@@ -182,51 +940,66 @@ var CalendarGrid = composable(({ classNames, rows, dates = [], onSelect, ...prop
|
|
|
182
940
|
length: 7
|
|
183
941
|
}).map((_, i) => {
|
|
184
942
|
const date = getDate(start, index, i, weekStartsOn);
|
|
185
|
-
const
|
|
186
|
-
|
|
943
|
+
const marker = getMarker(date);
|
|
944
|
+
const isToday = isSameDay(date, today);
|
|
945
|
+
const isCurrent = isSameDay(date, selected);
|
|
946
|
+
const dateClassNames = isToday ? DATE_CLASS_NAMES.today : marker?.tag === "star" ? DATE_CLASS_NAMES.starred : marker ? DATE_CLASS_NAMES.busy : void 0;
|
|
947
|
+
const inRange = isInRange(date, activeRange);
|
|
948
|
+
return /* @__PURE__ */ React3.createElement("div", {
|
|
187
949
|
key: i,
|
|
188
|
-
|
|
189
|
-
className:
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
},
|
|
950
|
+
"data-date": startOfDay3(date).toISOString(),
|
|
951
|
+
className: mx3("relative flex justify-center cursor-pointer select-none", getBgColor(date)),
|
|
952
|
+
onPointerDown: (ev) => handleDayPointerDown(date, ev),
|
|
953
|
+
onPointerEnter: () => handleDayPointerEnter(date),
|
|
954
|
+
onPointerUp: () => handleDayPointerUp(date)
|
|
955
|
+
}, inRange && /* @__PURE__ */ React3.createElement("div", {
|
|
956
|
+
className: "absolute inset-0 bg-primary-500/20"
|
|
957
|
+
}), !dateClassNames && date.getDate() === 1 && /* @__PURE__ */ React3.createElement("span", {
|
|
194
958
|
className: "absolute top-0 text-xs text-description"
|
|
195
|
-
},
|
|
196
|
-
|
|
197
|
-
|
|
959
|
+
}, format2(date, "MMM")), /* @__PURE__ */ React3.createElement("div", {
|
|
960
|
+
className: mx3("absolute inset-1 rounded-full flex justify-center items-center text-sm text-description", dateClassNames)
|
|
961
|
+
}, date.getDate()), isCurrent && /* @__PURE__ */ React3.createElement("div", {
|
|
962
|
+
className: mx3("absolute inset-0.5 rounded-full", DATE_CLASS_NAMES.current)
|
|
198
963
|
}));
|
|
199
964
|
})));
|
|
200
965
|
}, [
|
|
201
|
-
|
|
202
|
-
|
|
966
|
+
activeRange,
|
|
967
|
+
handleDayPointerDown,
|
|
968
|
+
handleDayPointerEnter,
|
|
969
|
+
handleDayPointerUp,
|
|
970
|
+
getMarker,
|
|
203
971
|
selected,
|
|
204
972
|
weekStartsOn
|
|
205
973
|
]);
|
|
206
|
-
return /* @__PURE__ */
|
|
207
|
-
...
|
|
974
|
+
return /* @__PURE__ */ React3.createElement("div", {
|
|
975
|
+
...composableProps2(props, {
|
|
208
976
|
role: "none",
|
|
209
977
|
classNames: [
|
|
210
|
-
"flex flex-col h-full w-full justify-center overflow-hidden",
|
|
978
|
+
"flex flex-col h-full w-full justify-center overflow-hidden outline-hidden",
|
|
211
979
|
classNames
|
|
212
980
|
]
|
|
213
981
|
}),
|
|
214
|
-
ref:
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
982
|
+
ref: (node) => {
|
|
983
|
+
gridRef.current = node;
|
|
984
|
+
if (typeof forwardedRef === "function") {
|
|
985
|
+
forwardedRef(node);
|
|
986
|
+
} else if (forwardedRef) {
|
|
987
|
+
forwardedRef.current = node;
|
|
988
|
+
}
|
|
989
|
+
},
|
|
990
|
+
tabIndex: 0,
|
|
991
|
+
onKeyDown: handleKeyDown
|
|
992
|
+
}, /* @__PURE__ */ React3.createElement("div", {
|
|
218
993
|
style: {
|
|
219
994
|
width: defaultWidth
|
|
220
995
|
}
|
|
221
|
-
},
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
}, date))), /* @__PURE__ */ React.createElement("div", {
|
|
226
|
-
role: "none",
|
|
996
|
+
}, /* @__PURE__ */ React3.createElement(Weekdays, {
|
|
997
|
+
weekStartsOn,
|
|
998
|
+
columnWidth: size
|
|
999
|
+
})), /* @__PURE__ */ React3.createElement("div", {
|
|
227
1000
|
className: "flex flex-col h-full w-full justify-center overflow-hidden",
|
|
228
1001
|
ref: containerRef
|
|
229
|
-
}, /* @__PURE__ */
|
|
1002
|
+
}, /* @__PURE__ */ React3.createElement(List, {
|
|
230
1003
|
ref: listRef,
|
|
231
1004
|
role: "none",
|
|
232
1005
|
className: "scrollbar-none outline-hidden",
|
|
@@ -244,7 +1017,8 @@ CalendarGrid.displayName = CALENDAR_GRID_NAME;
|
|
|
244
1017
|
var Calendar = {
|
|
245
1018
|
Root: CalendarRoot,
|
|
246
1019
|
Toolbar: CalendarToolbar,
|
|
247
|
-
Grid: CalendarGrid
|
|
1020
|
+
Grid: CalendarGrid,
|
|
1021
|
+
Week: CalendarWeek
|
|
248
1022
|
};
|
|
249
1023
|
export {
|
|
250
1024
|
Calendar
|