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