@dxos/react-ui-calendar 0.8.4-main.e8ec1fe → 0.8.4-main.effb148878

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.
@@ -1,27 +1,17 @@
1
1
  // src/components/Calendar/Calendar.tsx
2
- import { useSignals as _useSignals } from "@preact-signals/safe-react/tracking";
3
2
  import { createContext } from "@radix-ui/react-context";
4
- import { addDays, differenceInWeeks, format, startOfWeek } from "date-fns";
3
+ import { addDays, format, startOfDay, startOfWeek } from "date-fns";
5
4
  import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
6
5
  import { useResizeDetector } from "react-resize-detector";
7
6
  import { List } from "react-virtualized";
8
7
  import { Event } from "@dxos/async";
9
- import { Icon, IconButton, useTranslation } from "@dxos/react-ui";
10
- import { mx } from "@dxos/react-ui-theme";
11
-
12
- // src/translations.ts
13
- var translationKey = "react-ui-calendar";
14
- var translations = [
15
- {
16
- "en-US": {
17
- [translationKey]: {
18
- "today button": "Today"
19
- }
20
- }
21
- }
22
- ];
8
+ import { IconButton, useTranslation } from "@dxos/react-ui";
9
+ import { composable, composableProps } from "@dxos/react-ui";
10
+ import { mx } from "@dxos/ui-theme";
11
+ import { translationKey } from "#translations";
23
12
 
24
13
  // src/components/Calendar/util.ts
14
+ import { differenceInCalendarDays } from "date-fns";
25
15
  var getDate = (start2, weekNumber, dayOfWeek, weekStartsOn) => {
26
16
  const result = new Date(start2);
27
17
  const startDayOfWeek = start2.getDay();
@@ -29,6 +19,13 @@ var getDate = (start2, weekNumber, dayOfWeek, weekStartsOn) => {
29
19
  result.setDate(start2.getDate() - adjustedStartDay + weekNumber * 7 + dayOfWeek);
30
20
  return result;
31
21
  };
22
+ var getRowIndex = (start2, date, weekStartsOn) => {
23
+ const startDayOfWeek = start2.getDay();
24
+ const adjustedStartDay = (startDayOfWeek === 0 ? 7 : startDayOfWeek) - weekStartsOn;
25
+ const row0Start = new Date(start2);
26
+ row0Start.setDate(start2.getDate() - adjustedStartDay);
27
+ return Math.floor(differenceInCalendarDays(date, row0Start) / 7);
28
+ };
32
29
  var isSameDay = (date1, date2) => {
33
30
  return !!date2 && date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth() && date1.getDate() === date2.getDate();
34
31
  };
@@ -38,238 +35,512 @@ var maxRows = 50 * 100;
38
35
  var start = /* @__PURE__ */ new Date("1970-01-01");
39
36
  var size = 48;
40
37
  var defaultWidth = 7 * size;
41
- var [CalendarContextProvider, useCalendarContext] = createContext("Calendar");
42
- var CalendarRoot = /* @__PURE__ */ forwardRef(({ children, weekStartsOn = 1 }, forwardedRef) => {
43
- var _effect = _useSignals();
44
- try {
45
- const event = useMemo(() => new Event(), []);
46
- const [selected, setSelected] = useState();
47
- const [index, setIndex] = useState();
48
- useImperativeHandle(forwardedRef, () => ({
49
- scrollTo: (date) => {
50
- event.emit({
51
- type: "scroll",
52
- date
53
- });
54
- }
55
- }), [
56
- event
57
- ]);
58
- return /* @__PURE__ */ React.createElement(CalendarContextProvider, {
59
- weekStartsOn,
60
- event,
61
- index,
62
- setIndex,
63
- selected,
64
- setSelected
65
- }, children);
66
- } finally {
67
- _effect.f();
38
+ var EDGE_SCROLL_ZONE = 32;
39
+ var EDGE_SCROLL_MAX_SPEED = 12;
40
+ var makeRange = (a, b) => {
41
+ const dayA = startOfDay(a);
42
+ const dayB = startOfDay(b);
43
+ return dayA <= dayB ? {
44
+ from: dayA,
45
+ to: dayB
46
+ } : {
47
+ from: dayB,
48
+ to: dayA
49
+ };
50
+ };
51
+ var isInRange = (date, range) => {
52
+ if (!range) {
53
+ return false;
68
54
  }
69
- });
70
- var CalendarViewport = ({ children, classNames }) => {
71
- var _effect = _useSignals();
72
- try {
73
- return /* @__PURE__ */ React.createElement("div", {
74
- role: "none",
75
- className: mx("flex flex-col items-center overflow-hidden bg-inputSurface", classNames)
76
- }, children);
77
- } finally {
78
- _effect.f();
55
+ const day = startOfDay(date).getTime();
56
+ return day >= range.from.getTime() && day <= range.to.getTime();
57
+ };
58
+ var cellDate = (el) => {
59
+ let current = el;
60
+ while (current && current !== document.body) {
61
+ const iso = current.getAttribute?.("data-date");
62
+ if (iso) {
63
+ return new Date(iso);
64
+ }
65
+ current = current.parentElement;
79
66
  }
67
+ return void 0;
80
68
  };
81
- CalendarViewport.displayName = "CalendarContent";
82
- var CalendarToolbar = ({ classNames }) => {
83
- var _effect = _useSignals();
84
- try {
85
- const { t } = useTranslation(translationKey);
86
- const { weekStartsOn, event, index, selected } = useCalendarContext(CalendarToolbar.displayName);
87
- const top = useMemo(() => getDate(start, index ?? 0, 6, weekStartsOn), [
88
- index,
89
- weekStartsOn
90
- ]);
91
- const today = useMemo(() => /* @__PURE__ */ new Date(), []);
92
- const handleToday = useCallback(() => {
69
+ var [CalendarContextProvider, useCalendarContext] = createContext("Calendar");
70
+ var CalendarRoot = /* @__PURE__ */ forwardRef(({ children, weekStartsOn = 1 }, forwardedRef) => {
71
+ const event = useMemo(() => new Event(), []);
72
+ const [selected, setSelected] = useState();
73
+ const [index, setIndex] = useState();
74
+ const [range, setRange] = useState();
75
+ const [pendingRange, setPendingRange] = useState();
76
+ useImperativeHandle(forwardedRef, () => ({
77
+ scrollTo: (date) => {
93
78
  event.emit({
94
79
  type: "scroll",
95
- date: today
80
+ date
96
81
  });
97
- }, [
98
- event,
99
- start,
100
- today
101
- ]);
102
- return /* @__PURE__ */ React.createElement("div", {
82
+ }
83
+ }), [
84
+ event
85
+ ]);
86
+ return /* @__PURE__ */ React.createElement(CalendarContextProvider, {
87
+ weekStartsOn,
88
+ event,
89
+ index,
90
+ setIndex,
91
+ selected,
92
+ setSelected,
93
+ range,
94
+ setRange,
95
+ pendingRange,
96
+ setPendingRange
97
+ }, children);
98
+ });
99
+ var CALENDAR_TOOLBAR_NAME = "CalendarHeader";
100
+ var CalendarToolbar = composable(({ classNames, ...props }, forwardedRef) => {
101
+ const { t } = useTranslation(translationKey);
102
+ const { weekStartsOn, event, index, selected } = useCalendarContext(CALENDAR_TOOLBAR_NAME);
103
+ const top = useMemo(() => getDate(start, index ?? 0, 6, weekStartsOn), [
104
+ index,
105
+ weekStartsOn
106
+ ]);
107
+ const today = useMemo(() => /* @__PURE__ */ new Date(), []);
108
+ const handleToday = useCallback(() => {
109
+ event.emit({
110
+ type: "scroll",
111
+ date: today
112
+ });
113
+ }, [
114
+ event,
115
+ start,
116
+ today
117
+ ]);
118
+ return /* @__PURE__ */ React.createElement("div", {
119
+ ...composableProps(props, {
103
120
  role: "none",
104
- className: mx("shink-0 is-full grid grid-cols-3 items-center bg-barSurface", classNames),
105
- style: {
106
- width: defaultWidth
107
- }
108
- }, /* @__PURE__ */ React.createElement("div", {
109
- className: "flex justify-start"
110
- }, /* @__PURE__ */ React.createElement(IconButton, {
111
- variant: "ghost",
112
- size: 5,
113
- icon: "ph--calendar--regular",
114
- iconOnly: true,
115
- classNames: "aspect-square",
116
- label: t("today button"),
117
- onClick: handleToday
118
- })), /* @__PURE__ */ React.createElement("div", {
119
- className: "flex justify-center p-2 text-description"
120
- }, format(selected ?? top, "MMMM")), /* @__PURE__ */ React.createElement("div", {
121
- className: "flex justify-end p-2 text-description"
122
- }, (selected ?? top).getFullYear()));
123
- } finally {
124
- _effect.f();
125
- }
126
- };
127
- CalendarToolbar.displayName = "CalendarHeader";
128
- var CalendarGrid = ({ classNames, rows, onSelect }) => {
129
- var _effect = _useSignals();
130
- try {
131
- const { weekStartsOn, event, setIndex, selected, setSelected } = useCalendarContext(CalendarGrid.displayName);
132
- const { ref: containerRef, width = 0, height = 0 } = useResizeDetector();
133
- const maxHeight = rows ? rows * size : void 0;
134
- const listRef = useRef(null);
135
- const today = useMemo(() => /* @__PURE__ */ new Date(), []);
136
- const [initialized, setInitialized] = useState(false);
137
- useEffect(() => {
138
- const index = differenceInWeeks(today, start);
139
- listRef.current?.scrollToRow(index);
140
- }, [
141
- initialized,
142
- start,
143
- today
144
- ]);
145
- useEffect(() => {
146
- return event.on((event2) => {
147
- switch (event2.type) {
148
- case "scroll": {
149
- const index = differenceInWeeks(event2.date, start);
150
- listRef.current?.scrollToRow(index);
151
- break;
152
- }
121
+ classNames: [
122
+ "shrink-0 grid! grid-cols-3 items-center bg-toolbar-surface",
123
+ classNames
124
+ ]
125
+ }),
126
+ ref: forwardedRef,
127
+ style: {
128
+ width: defaultWidth
129
+ }
130
+ }, /* @__PURE__ */ React.createElement("div", {
131
+ className: "flex justify-start"
132
+ }, /* @__PURE__ */ React.createElement(IconButton, {
133
+ variant: "ghost",
134
+ icon: "ph--calendar--regular",
135
+ iconOnly: true,
136
+ classNames: "aspect-square",
137
+ label: t("today.button"),
138
+ onClick: handleToday
139
+ })), /* @__PURE__ */ React.createElement("div", {
140
+ className: "flex justify-center p-2 text-description"
141
+ }, format(selected ?? top, "MMMM")), /* @__PURE__ */ React.createElement("div", {
142
+ className: "flex justify-end p-2 text-description"
143
+ }, (selected ?? top).getFullYear()));
144
+ });
145
+ CalendarToolbar.displayName = CALENDAR_TOOLBAR_NAME;
146
+ var CALENDAR_GRID_NAME = "CalendarGrid";
147
+ var CalendarGrid = composable(({ classNames, rows, dates = [], onSelect, onSelectRange, ...props }, forwardedRef) => {
148
+ const { weekStartsOn, event, setIndex, selected, setSelected, range, setRange, pendingRange, setPendingRange } = useCalendarContext(CALENDAR_GRID_NAME);
149
+ const { ref: containerRef, width = 0, height = 0 } = useResizeDetector();
150
+ const maxHeight = rows ? rows * size : void 0;
151
+ const listRef = useRef(null);
152
+ const gridRef = useRef(null);
153
+ const today = useMemo(() => /* @__PURE__ */ new Date(), []);
154
+ const dateSet = useMemo(() => new Set(dates.map((date) => startOfDay(date).toISOString())), [
155
+ dates
156
+ ]);
157
+ const hasDate = useCallback((date) => dateSet.has(startOfDay(date).toISOString()), [
158
+ dateSet
159
+ ]);
160
+ const [initialized, setInitialized] = useState(false);
161
+ useEffect(() => {
162
+ const index = getRowIndex(start, today, weekStartsOn);
163
+ listRef.current?.scrollToRow(index);
164
+ }, [
165
+ initialized,
166
+ start,
167
+ today,
168
+ weekStartsOn
169
+ ]);
170
+ useEffect(() => {
171
+ return event.on((event2) => {
172
+ switch (event2.type) {
173
+ case "scroll": {
174
+ const index = getRowIndex(start, event2.date, weekStartsOn);
175
+ listRef.current?.scrollToRow(index);
176
+ break;
153
177
  }
154
- });
155
- }, [
156
- event
157
- ]);
158
- const days = useMemo(() => {
159
- const weekStart = startOfWeek(/* @__PURE__ */ new Date(), {
160
- weekStartsOn
161
- });
162
- return Array.from({
163
- length: 7
164
- }, (_, i) => {
165
- const day = addDays(weekStart, i);
166
- return format(day, "EEE");
167
- });
168
- }, []);
169
- const getNumAppointments = useCallback((_date) => {
170
- return 0;
171
- }, []);
172
- const handleDaySelect = useCallback((date) => {
173
- setSelected((current) => isSameDay(date, current) ? void 0 : date);
178
+ }
179
+ });
180
+ }, [
181
+ event
182
+ ]);
183
+ const days = useMemo(() => {
184
+ const weekStart = startOfWeek(/* @__PURE__ */ new Date(), {
185
+ weekStartsOn
186
+ });
187
+ return Array.from({
188
+ length: 7
189
+ }, (_, i) => {
190
+ const day = addDays(weekStart, i);
191
+ return format(day, "EEE");
192
+ });
193
+ }, []);
194
+ const anchorRef = useRef(void 0);
195
+ const focusRef = useRef(void 0);
196
+ const draggingRef = useRef(false);
197
+ const pointerXRef = useRef(0);
198
+ const pointerYRef = useRef(0);
199
+ const scrollTopRef = useRef(0);
200
+ const scrollRafRef = useRef(void 0);
201
+ const scrollIntoView = useCallback((date) => {
202
+ const targetRow = getRowIndex(start, date, weekStartsOn);
203
+ const visibleHeight = maxHeight ?? height;
204
+ if (!visibleHeight) {
205
+ return;
206
+ }
207
+ const firstVisibleRow = Math.floor(scrollTopRef.current / size);
208
+ const lastVisibleRow = firstVisibleRow + Math.floor(visibleHeight / size) - 1;
209
+ if (targetRow < firstVisibleRow) {
210
+ listRef.current?.scrollToRow(targetRow);
211
+ } else if (targetRow > lastVisibleRow) {
212
+ listRef.current?.scrollToPosition(Math.max(0, (targetRow + 1) * size - Math.floor(visibleHeight / size) * size));
213
+ }
214
+ }, [
215
+ height,
216
+ maxHeight,
217
+ weekStartsOn
218
+ ]);
219
+ const updateRangeFromAnchor = useCallback((focus, fireRange = false) => {
220
+ const anchor = anchorRef.current;
221
+ if (!anchor) {
222
+ return;
223
+ }
224
+ focusRef.current = focus;
225
+ if (isSameDay(anchor, focus)) {
226
+ setRange(void 0);
227
+ setSelected(anchor);
228
+ } else {
229
+ setSelected(void 0);
230
+ const committed = makeRange(anchor, focus);
231
+ setRange(committed);
232
+ if (fireRange) {
233
+ onSelectRange?.({
234
+ range: committed
235
+ });
236
+ }
237
+ }
238
+ }, [
239
+ onSelectRange,
240
+ setRange,
241
+ setSelected
242
+ ]);
243
+ const prevSelectedRef = useRef(void 0);
244
+ const handleDayPointerDown = useCallback((date, ev) => {
245
+ ev.preventDefault();
246
+ prevSelectedRef.current = selected;
247
+ anchorRef.current = date;
248
+ focusRef.current = date;
249
+ draggingRef.current = true;
250
+ setRange(void 0);
251
+ setPendingRange(void 0);
252
+ setSelected(date);
253
+ gridRef.current?.focus({
254
+ preventScroll: true
255
+ });
256
+ }, [
257
+ selected,
258
+ setPendingRange,
259
+ setRange,
260
+ setSelected
261
+ ]);
262
+ const handleDayPointerEnter = useCallback((date) => {
263
+ if (!draggingRef.current) {
264
+ return;
265
+ }
266
+ const anchor = anchorRef.current;
267
+ if (!anchor) {
268
+ return;
269
+ }
270
+ focusRef.current = date;
271
+ setSelected(void 0);
272
+ setPendingRange(makeRange(anchor, date));
273
+ }, [
274
+ setPendingRange,
275
+ setSelected
276
+ ]);
277
+ const handleDayPointerUp = useCallback((date) => {
278
+ const anchor = anchorRef.current;
279
+ const wasDragging = draggingRef.current;
280
+ draggingRef.current = false;
281
+ setPendingRange(void 0);
282
+ if (!wasDragging || !anchor) {
283
+ return;
284
+ }
285
+ focusRef.current = date;
286
+ if (isSameDay(anchor, date)) {
287
+ if (prevSelectedRef.current && isSameDay(prevSelectedRef.current, date)) {
288
+ setSelected(void 0);
289
+ anchorRef.current = void 0;
290
+ focusRef.current = void 0;
291
+ return;
292
+ }
293
+ setSelected(anchor);
174
294
  onSelect?.({
175
295
  date
176
296
  });
177
- }, [
178
- onSelect
179
- ]);
180
- const handleScroll = useCallback((info) => {
181
- setIndex(Math.round(info.scrollTop / size));
182
- }, []);
183
- const rowRenderer = useCallback(({ key, index, style }) => {
184
- const getBgColor = (date) => date.getMonth() % 2 === 0 && "bg-modalSurface";
185
- return /* @__PURE__ */ React.createElement("div", {
186
- key,
187
- role: "none",
188
- style,
189
- className: "is-full grid grid-cols-[1fr_max-content_1fr] snap-center"
190
- }, /* @__PURE__ */ React.createElement("div", {
191
- role: "none",
192
- className: mx(getBgColor(getDate(start, index, 0, weekStartsOn)))
193
- }), /* @__PURE__ */ React.createElement("div", {
194
- role: "none",
195
- className: "grid grid-cols-7",
196
- style: {
197
- gridTemplateColumns: `repeat(7, ${size}px)`
297
+ return;
298
+ }
299
+ const committed = makeRange(anchor, date);
300
+ setRange(committed);
301
+ onSelectRange?.({
302
+ range: committed
303
+ });
304
+ }, [
305
+ onSelect,
306
+ onSelectRange,
307
+ setPendingRange,
308
+ setRange,
309
+ setSelected
310
+ ]);
311
+ useEffect(() => {
312
+ const cancel = () => {
313
+ if (draggingRef.current) {
314
+ draggingRef.current = false;
315
+ setPendingRange(void 0);
316
+ }
317
+ };
318
+ window.addEventListener("pointerup", cancel);
319
+ window.addEventListener("pointercancel", cancel);
320
+ return () => {
321
+ window.removeEventListener("pointerup", cancel);
322
+ window.removeEventListener("pointercancel", cancel);
323
+ };
324
+ }, [
325
+ setPendingRange
326
+ ]);
327
+ const tickEdgeScroll = useCallback(() => {
328
+ scrollRafRef.current = void 0;
329
+ if (!draggingRef.current) {
330
+ return;
331
+ }
332
+ const rect = containerRef.current?.getBoundingClientRect();
333
+ if (!rect) {
334
+ return;
335
+ }
336
+ const y = pointerYRef.current;
337
+ let delta = 0;
338
+ if (y < rect.top + EDGE_SCROLL_ZONE) {
339
+ delta = -EDGE_SCROLL_MAX_SPEED * Math.min(1, Math.max(0, (rect.top + EDGE_SCROLL_ZONE - y) / EDGE_SCROLL_ZONE));
340
+ } else if (y > rect.bottom - EDGE_SCROLL_ZONE) {
341
+ delta = EDGE_SCROLL_MAX_SPEED * Math.min(1, Math.max(0, (y - (rect.bottom - EDGE_SCROLL_ZONE)) / EDGE_SCROLL_ZONE));
342
+ }
343
+ if (delta !== 0) {
344
+ const newScroll = Math.max(0, scrollTopRef.current + delta);
345
+ listRef.current?.scrollToPosition(newScroll);
346
+ const date = cellDate(document.elementFromPoint(pointerXRef.current, y));
347
+ const anchor = anchorRef.current;
348
+ if (date && anchor) {
349
+ focusRef.current = date;
350
+ if (isSameDay(anchor, date)) {
351
+ setPendingRange(void 0);
352
+ setSelected(anchor);
353
+ } else {
354
+ setSelected(void 0);
355
+ setPendingRange(makeRange(anchor, date));
198
356
  }
199
- }, Array.from({
200
- length: 7
201
- }).map((_, i) => {
202
- const date = getDate(start, index, i, weekStartsOn);
203
- const num = getNumAppointments(date);
204
- const border = isSameDay(date, selected) ? "border-primary-500" : isSameDay(date, today) ? "border-amber-500" : void 0;
205
- return /* @__PURE__ */ React.createElement("div", {
206
- key: i,
207
- role: "none",
208
- className: mx("relative flex justify-center items-center cursor-pointer", getBgColor(date)),
209
- onClick: () => handleDaySelect(date)
210
- }, /* @__PURE__ */ React.createElement("span", {
211
- className: "text-description"
212
- }, date.getDate()), !border && date.getDate() === 1 && /* @__PURE__ */ React.createElement("span", {
213
- className: "absolute top-0 text-xs text-description"
214
- }, format(date, "MMM")), border && /* @__PURE__ */ React.createElement("div", {
215
- role: "none",
216
- className: mx("absolute top-0 left-0 is-full bs-full border-2 rounded-full", border)
217
- }), num > 0 && /* @__PURE__ */ React.createElement(Icon, {
218
- classNames: "absolute bottom-0",
219
- icon: num > 3 ? "ph--dots-three--regular" : "ph--dot--regular",
220
- size: 5
221
- }));
222
- })), /* @__PURE__ */ React.createElement("div", {
223
- className: mx(getBgColor(getDate(start, index, 6, weekStartsOn)))
224
- }));
225
- }, [
226
- handleDaySelect,
227
- getNumAppointments,
228
- selected,
229
- weekStartsOn
230
- ]);
357
+ }
358
+ scrollRafRef.current = requestAnimationFrame(tickEdgeScroll);
359
+ }
360
+ }, [
361
+ containerRef,
362
+ setPendingRange,
363
+ setSelected
364
+ ]);
365
+ useEffect(() => {
366
+ const handleMove = (ev) => {
367
+ if (!draggingRef.current) {
368
+ return;
369
+ }
370
+ pointerXRef.current = ev.clientX;
371
+ pointerYRef.current = ev.clientY;
372
+ if (scrollRafRef.current === void 0) {
373
+ scrollRafRef.current = requestAnimationFrame(tickEdgeScroll);
374
+ }
375
+ };
376
+ window.addEventListener("pointermove", handleMove);
377
+ return () => {
378
+ window.removeEventListener("pointermove", handleMove);
379
+ if (scrollRafRef.current !== void 0) {
380
+ cancelAnimationFrame(scrollRafRef.current);
381
+ scrollRafRef.current = void 0;
382
+ }
383
+ };
384
+ }, [
385
+ tickEdgeScroll
386
+ ]);
387
+ const handleKeyDown = useCallback((ev) => {
388
+ let dx = 0;
389
+ switch (ev.key) {
390
+ case "ArrowLeft":
391
+ dx = -1;
392
+ break;
393
+ case "ArrowRight":
394
+ dx = 1;
395
+ break;
396
+ case "ArrowUp":
397
+ dx = -7;
398
+ break;
399
+ case "ArrowDown":
400
+ dx = 7;
401
+ break;
402
+ default:
403
+ return;
404
+ }
405
+ ev.preventDefault();
406
+ if (ev.shiftKey) {
407
+ let anchor = anchorRef.current;
408
+ let focus = focusRef.current;
409
+ if (!anchor) {
410
+ if (selected) {
411
+ anchor = startOfDay(selected);
412
+ focus = anchor;
413
+ } else if (range) {
414
+ anchor = range.from;
415
+ focus = range.to;
416
+ } else {
417
+ anchor = startOfDay(today);
418
+ focus = anchor;
419
+ }
420
+ anchorRef.current = anchor;
421
+ focusRef.current = focus;
422
+ }
423
+ const newFocus = addDays(focus ?? anchor, dx);
424
+ updateRangeFromAnchor(newFocus, true);
425
+ scrollIntoView(newFocus);
426
+ } else {
427
+ const current = selected ?? focusRef.current ?? anchorRef.current ?? today;
428
+ const next = addDays(startOfDay(current), dx);
429
+ anchorRef.current = next;
430
+ focusRef.current = next;
431
+ setRange(void 0);
432
+ setPendingRange(void 0);
433
+ setSelected(next);
434
+ onSelect?.({
435
+ date: next
436
+ });
437
+ scrollIntoView(next);
438
+ }
439
+ }, [
440
+ onSelect,
441
+ range,
442
+ scrollIntoView,
443
+ selected,
444
+ setPendingRange,
445
+ setRange,
446
+ setSelected,
447
+ today,
448
+ updateRangeFromAnchor
449
+ ]);
450
+ const activeRange = pendingRange ?? range;
451
+ const handleScroll = useCallback((info) => {
452
+ scrollTopRef.current = info.scrollTop;
453
+ setIndex(Math.round(info.scrollTop / size));
454
+ }, []);
455
+ const rowRenderer = useCallback(({ key, index, style }) => {
456
+ const getBgColor = (date) => date.getMonth() % 2 === 0 && "bg-modal-surface";
231
457
  return /* @__PURE__ */ React.createElement("div", {
232
- role: "none",
233
- className: mx("flex flex-col bs-full is-full justify-center overflow-hidden", classNames)
234
- }, /* @__PURE__ */ React.createElement("div", {
235
- role: "none",
236
- className: "flex justify-center bg-groupSurface"
458
+ key,
459
+ style,
460
+ className: "grid"
237
461
  }, /* @__PURE__ */ React.createElement("div", {
238
- role: "none",
239
- className: "flex is-full grid grid-cols-7",
462
+ className: "grid grid-cols-7 bg-input-surface",
240
463
  style: {
241
- width: defaultWidth
464
+ gridTemplateColumns: `repeat(7, ${size}px)`
242
465
  }
243
- }, days.map((date, i) => /* @__PURE__ */ React.createElement("div", {
244
- key: i,
245
- role: "none",
246
- className: "flex justify-center p-2 text-sm font-thin"
247
- }, date)))), /* @__PURE__ */ React.createElement("div", {
248
- role: "none",
249
- className: "flex flex-col bs-full is-full justify-center overflow-hidden",
250
- ref: containerRef
251
- }, /* @__PURE__ */ React.createElement(List, {
252
- ref: listRef,
253
- role: "none",
254
- // TODO(burdon): Snap isn't working.
255
- className: "[&>div]:snap-y scrollbar-none outline-none",
256
- width,
257
- height: maxHeight ?? height,
258
- rowCount: maxRows,
259
- rowHeight: size,
260
- rowRenderer,
261
- scrollToAlignment: "start",
262
- onScroll: handleScroll,
263
- onRowsRendered: () => setInitialized(true)
466
+ }, Array.from({
467
+ length: 7
468
+ }).map((_, i) => {
469
+ const date = getDate(start, index, i, weekStartsOn);
470
+ const inRange = isInRange(date, activeRange);
471
+ const border = isSameDay(date, selected) ? "border-primary-500" : isSameDay(date, today) ? "border-amber-500" : hasDate(date) ? "border-neutral-700 border-dashed" : void 0;
472
+ return /* @__PURE__ */ React.createElement("div", {
473
+ key: i,
474
+ "data-date": startOfDay(date).toISOString(),
475
+ className: mx("relative flex justify-center items-center cursor-pointer select-none", getBgColor(date)),
476
+ onPointerDown: (ev) => handleDayPointerDown(date, ev),
477
+ onPointerEnter: () => handleDayPointerEnter(date),
478
+ onPointerUp: () => handleDayPointerUp(date)
479
+ }, inRange && /* @__PURE__ */ React.createElement("div", {
480
+ className: "absolute inset-0 bg-primary-500/20"
481
+ }), /* @__PURE__ */ React.createElement("span", {
482
+ className: "relative text-description"
483
+ }, date.getDate()), !border && date.getDate() === 1 && /* @__PURE__ */ React.createElement("span", {
484
+ className: "absolute top-0 text-xs text-description"
485
+ }, format(date, "MMM")), border && /* @__PURE__ */ React.createElement("div", {
486
+ className: mx("absolute inset-1 border-2 rounded-full", border)
487
+ }));
264
488
  })));
265
- } finally {
266
- _effect.f();
267
- }
268
- };
269
- CalendarGrid.displayName = "CalendarGrid";
489
+ }, [
490
+ activeRange,
491
+ handleDayPointerDown,
492
+ handleDayPointerEnter,
493
+ handleDayPointerUp,
494
+ hasDate,
495
+ selected,
496
+ weekStartsOn
497
+ ]);
498
+ return /* @__PURE__ */ React.createElement("div", {
499
+ ...composableProps(props, {
500
+ role: "none",
501
+ classNames: [
502
+ "flex flex-col h-full w-full justify-center overflow-hidden outline-hidden",
503
+ classNames
504
+ ]
505
+ }),
506
+ ref: (node) => {
507
+ gridRef.current = node;
508
+ if (typeof forwardedRef === "function") {
509
+ forwardedRef(node);
510
+ } else if (forwardedRef) {
511
+ forwardedRef.current = node;
512
+ }
513
+ },
514
+ tabIndex: 0,
515
+ onKeyDown: handleKeyDown
516
+ }, /* @__PURE__ */ React.createElement("div", {
517
+ className: "grid w-full grid-cols-7",
518
+ style: {
519
+ width: defaultWidth
520
+ }
521
+ }, days.map((date, i) => /* @__PURE__ */ React.createElement("div", {
522
+ key: i,
523
+ className: "flex justify-center p-2 text-sm font-thin"
524
+ }, date))), /* @__PURE__ */ React.createElement("div", {
525
+ className: "flex flex-col h-full w-full justify-center overflow-hidden",
526
+ ref: containerRef
527
+ }, /* @__PURE__ */ React.createElement(List, {
528
+ ref: listRef,
529
+ role: "none",
530
+ className: "scrollbar-none outline-hidden",
531
+ width,
532
+ height: maxHeight ?? height,
533
+ rowCount: maxRows,
534
+ rowHeight: size,
535
+ rowRenderer,
536
+ scrollToAlignment: "start",
537
+ onScroll: handleScroll,
538
+ onRowsRendered: () => setInitialized(true)
539
+ })));
540
+ });
541
+ CalendarGrid.displayName = CALENDAR_GRID_NAME;
270
542
  var Calendar = {
271
543
  Root: CalendarRoot,
272
- Viewport: CalendarViewport,
273
544
  Toolbar: CalendarToolbar,
274
545
  Grid: CalendarGrid
275
546
  };