@dxos/react-ui-calendar 0.8.4-main.fffef41 → 0.8.4-staging.60fe92afc8

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
  };
@@ -36,240 +33,515 @@ var isSameDay = (date1, date2) => {
36
33
  // src/components/Calendar/Calendar.tsx
37
34
  var maxRows = 50 * 100;
38
35
  var start = /* @__PURE__ */ new Date("1970-01-01");
39
- var size = 48;
36
+ var size = 40;
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 = [], initialDate, 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, initialDate ?? today, weekStartsOn);
163
+ listRef.current?.scrollToRow(index);
164
+ }, [
165
+ initialized,
166
+ start,
167
+ today,
168
+ initialDate,
169
+ weekStartsOn
170
+ ]);
171
+ useEffect(() => {
172
+ return event.on((event2) => {
173
+ switch (event2.type) {
174
+ case "scroll": {
175
+ const index = getRowIndex(start, event2.date, weekStartsOn);
176
+ listRef.current?.scrollToRow(index);
177
+ break;
153
178
  }
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);
179
+ }
180
+ });
181
+ }, [
182
+ event
183
+ ]);
184
+ const days = useMemo(() => {
185
+ const weekStart = startOfWeek(/* @__PURE__ */ new Date(), {
186
+ weekStartsOn
187
+ });
188
+ return Array.from({
189
+ length: 7
190
+ }, (_, i) => {
191
+ const day = addDays(weekStart, i);
192
+ return format(day, "EEE");
193
+ });
194
+ }, []);
195
+ const anchorRef = useRef(void 0);
196
+ const focusRef = useRef(void 0);
197
+ const draggingRef = useRef(false);
198
+ const pointerXRef = useRef(0);
199
+ const pointerYRef = useRef(0);
200
+ const scrollTopRef = useRef(0);
201
+ const scrollRafRef = useRef(void 0);
202
+ const scrollIntoView = useCallback((date) => {
203
+ const targetRow = getRowIndex(start, date, weekStartsOn);
204
+ const visibleHeight = maxHeight ?? height;
205
+ if (!visibleHeight) {
206
+ return;
207
+ }
208
+ const firstFullyVisibleRow = Math.ceil(scrollTopRef.current / size);
209
+ const lastFullyVisibleRow = Math.floor((scrollTopRef.current + visibleHeight) / size) - 1;
210
+ if (targetRow < firstFullyVisibleRow) {
211
+ listRef.current?.scrollToPosition(targetRow * size);
212
+ } else if (targetRow > lastFullyVisibleRow) {
213
+ listRef.current?.scrollToPosition(Math.max(0, (targetRow + 1) * size - visibleHeight));
214
+ }
215
+ }, [
216
+ height,
217
+ maxHeight,
218
+ weekStartsOn
219
+ ]);
220
+ const updateRangeFromAnchor = useCallback((focus, fireRange = false) => {
221
+ const anchor = anchorRef.current;
222
+ if (!anchor) {
223
+ return;
224
+ }
225
+ focusRef.current = focus;
226
+ if (isSameDay(anchor, focus)) {
227
+ setRange(void 0);
228
+ setSelected(anchor);
229
+ } else {
230
+ setSelected(void 0);
231
+ const committed = makeRange(anchor, focus);
232
+ setRange(committed);
233
+ if (fireRange) {
234
+ onSelectRange?.({
235
+ range: committed
236
+ });
237
+ }
238
+ }
239
+ }, [
240
+ onSelectRange,
241
+ setRange,
242
+ setSelected
243
+ ]);
244
+ const prevSelectedRef = useRef(void 0);
245
+ const handleDayPointerDown = useCallback((date, ev) => {
246
+ ev.preventDefault();
247
+ prevSelectedRef.current = selected;
248
+ anchorRef.current = date;
249
+ focusRef.current = date;
250
+ draggingRef.current = true;
251
+ setRange(void 0);
252
+ setPendingRange(void 0);
253
+ setSelected(date);
254
+ gridRef.current?.focus({
255
+ preventScroll: true
256
+ });
257
+ }, [
258
+ selected,
259
+ setPendingRange,
260
+ setRange,
261
+ setSelected
262
+ ]);
263
+ const handleDayPointerEnter = useCallback((date) => {
264
+ if (!draggingRef.current) {
265
+ return;
266
+ }
267
+ const anchor = anchorRef.current;
268
+ if (!anchor) {
269
+ return;
270
+ }
271
+ focusRef.current = date;
272
+ setSelected(void 0);
273
+ setPendingRange(makeRange(anchor, date));
274
+ }, [
275
+ setPendingRange,
276
+ setSelected
277
+ ]);
278
+ const handleDayPointerUp = useCallback((date) => {
279
+ const anchor = anchorRef.current;
280
+ const wasDragging = draggingRef.current;
281
+ draggingRef.current = false;
282
+ setPendingRange(void 0);
283
+ if (!wasDragging || !anchor) {
284
+ return;
285
+ }
286
+ focusRef.current = date;
287
+ if (isSameDay(anchor, date)) {
288
+ if (prevSelectedRef.current && isSameDay(prevSelectedRef.current, date)) {
289
+ setSelected(void 0);
290
+ anchorRef.current = void 0;
291
+ focusRef.current = void 0;
292
+ return;
293
+ }
294
+ setSelected(anchor);
174
295
  onSelect?.({
175
296
  date
176
297
  });
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)`
298
+ return;
299
+ }
300
+ const committed = makeRange(anchor, date);
301
+ setRange(committed);
302
+ onSelectRange?.({
303
+ range: committed
304
+ });
305
+ }, [
306
+ onSelect,
307
+ onSelectRange,
308
+ setPendingRange,
309
+ setRange,
310
+ setSelected
311
+ ]);
312
+ useEffect(() => {
313
+ const cancel = () => {
314
+ if (draggingRef.current) {
315
+ draggingRef.current = false;
316
+ setPendingRange(void 0);
317
+ }
318
+ };
319
+ window.addEventListener("pointerup", cancel);
320
+ window.addEventListener("pointercancel", cancel);
321
+ return () => {
322
+ window.removeEventListener("pointerup", cancel);
323
+ window.removeEventListener("pointercancel", cancel);
324
+ };
325
+ }, [
326
+ setPendingRange
327
+ ]);
328
+ const tickEdgeScroll = useCallback(() => {
329
+ scrollRafRef.current = void 0;
330
+ if (!draggingRef.current) {
331
+ return;
332
+ }
333
+ const rect = containerRef.current?.getBoundingClientRect();
334
+ if (!rect) {
335
+ return;
336
+ }
337
+ const y = pointerYRef.current;
338
+ let delta = 0;
339
+ if (y < rect.top + EDGE_SCROLL_ZONE) {
340
+ delta = -EDGE_SCROLL_MAX_SPEED * Math.min(1, Math.max(0, (rect.top + EDGE_SCROLL_ZONE - y) / EDGE_SCROLL_ZONE));
341
+ } else if (y > rect.bottom - EDGE_SCROLL_ZONE) {
342
+ delta = EDGE_SCROLL_MAX_SPEED * Math.min(1, Math.max(0, (y - (rect.bottom - EDGE_SCROLL_ZONE)) / EDGE_SCROLL_ZONE));
343
+ }
344
+ if (delta !== 0) {
345
+ const newScroll = Math.max(0, scrollTopRef.current + delta);
346
+ listRef.current?.scrollToPosition(newScroll);
347
+ const date = cellDate(document.elementFromPoint(pointerXRef.current, y));
348
+ const anchor = anchorRef.current;
349
+ if (date && anchor) {
350
+ focusRef.current = date;
351
+ if (isSameDay(anchor, date)) {
352
+ setPendingRange(void 0);
353
+ setSelected(anchor);
354
+ } else {
355
+ setSelected(void 0);
356
+ setPendingRange(makeRange(anchor, date));
198
357
  }
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
- ]);
358
+ }
359
+ scrollRafRef.current = requestAnimationFrame(tickEdgeScroll);
360
+ }
361
+ }, [
362
+ containerRef,
363
+ setPendingRange,
364
+ setSelected
365
+ ]);
366
+ useEffect(() => {
367
+ const handleMove = (ev) => {
368
+ if (!draggingRef.current) {
369
+ return;
370
+ }
371
+ pointerXRef.current = ev.clientX;
372
+ pointerYRef.current = ev.clientY;
373
+ if (scrollRafRef.current === void 0) {
374
+ scrollRafRef.current = requestAnimationFrame(tickEdgeScroll);
375
+ }
376
+ };
377
+ window.addEventListener("pointermove", handleMove);
378
+ return () => {
379
+ window.removeEventListener("pointermove", handleMove);
380
+ if (scrollRafRef.current !== void 0) {
381
+ cancelAnimationFrame(scrollRafRef.current);
382
+ scrollRafRef.current = void 0;
383
+ }
384
+ };
385
+ }, [
386
+ tickEdgeScroll
387
+ ]);
388
+ const handleKeyDown = useCallback((ev) => {
389
+ let dx = 0;
390
+ switch (ev.key) {
391
+ case "ArrowLeft":
392
+ dx = -1;
393
+ break;
394
+ case "ArrowRight":
395
+ dx = 1;
396
+ break;
397
+ case "ArrowUp":
398
+ dx = -7;
399
+ break;
400
+ case "ArrowDown":
401
+ dx = 7;
402
+ break;
403
+ default:
404
+ return;
405
+ }
406
+ ev.preventDefault();
407
+ if (ev.shiftKey) {
408
+ let anchor = anchorRef.current;
409
+ let focus = focusRef.current;
410
+ if (!anchor) {
411
+ if (selected) {
412
+ anchor = startOfDay(selected);
413
+ focus = anchor;
414
+ } else if (range) {
415
+ anchor = range.from;
416
+ focus = range.to;
417
+ } else {
418
+ anchor = startOfDay(today);
419
+ focus = anchor;
420
+ }
421
+ anchorRef.current = anchor;
422
+ focusRef.current = focus;
423
+ }
424
+ const newFocus = addDays(focus ?? anchor, dx);
425
+ updateRangeFromAnchor(newFocus, true);
426
+ scrollIntoView(newFocus);
427
+ } else {
428
+ const current = selected ?? focusRef.current ?? anchorRef.current ?? today;
429
+ const next = addDays(startOfDay(current), dx);
430
+ anchorRef.current = next;
431
+ focusRef.current = next;
432
+ setRange(void 0);
433
+ setPendingRange(void 0);
434
+ setSelected(next);
435
+ onSelect?.({
436
+ date: next
437
+ });
438
+ scrollIntoView(next);
439
+ }
440
+ }, [
441
+ onSelect,
442
+ range,
443
+ scrollIntoView,
444
+ selected,
445
+ setPendingRange,
446
+ setRange,
447
+ setSelected,
448
+ today,
449
+ updateRangeFromAnchor
450
+ ]);
451
+ const activeRange = pendingRange ?? range;
452
+ const handleScroll = useCallback((info) => {
453
+ scrollTopRef.current = info.scrollTop;
454
+ setIndex(Math.round(info.scrollTop / size));
455
+ }, []);
456
+ const rowRenderer = useCallback(({ key, index, style }) => {
457
+ const getBgColor = (date) => date.getMonth() % 2 === 0 ? "bg-group-surface" : "bg-group-alt-surface";
231
458
  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"
459
+ key,
460
+ style,
461
+ className: "grid"
237
462
  }, /* @__PURE__ */ React.createElement("div", {
238
- role: "none",
239
- className: "flex is-full grid grid-cols-7",
463
+ className: "grid grid-cols-7 bg-input-surface",
240
464
  style: {
241
- width: defaultWidth
465
+ gridTemplateColumns: `repeat(7, ${size}px)`
242
466
  }
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)
467
+ }, Array.from({
468
+ length: 7
469
+ }).map((_, i) => {
470
+ const date = getDate(start, index, i, weekStartsOn);
471
+ const inRange = isInRange(date, activeRange);
472
+ const border = isSameDay(date, selected) ? "border-primary-500" : isSameDay(date, today) ? "border-amber-500" : hasDate(date) ? "border-neutral-700 border-dashed" : void 0;
473
+ return /* @__PURE__ */ React.createElement("div", {
474
+ key: i,
475
+ "data-date": startOfDay(date).toISOString(),
476
+ className: mx("relative flex justify-center items-center cursor-pointer select-none", getBgColor(date)),
477
+ onPointerDown: (ev) => handleDayPointerDown(date, ev),
478
+ onPointerEnter: () => handleDayPointerEnter(date),
479
+ onPointerUp: () => handleDayPointerUp(date)
480
+ }, inRange && /* @__PURE__ */ React.createElement("div", {
481
+ className: "absolute inset-0 bg-primary-500/20"
482
+ }), /* @__PURE__ */ React.createElement("span", {
483
+ className: "relative text-description text-sm"
484
+ }, date.getDate()), !border && date.getDate() === 1 && /* @__PURE__ */ React.createElement("span", {
485
+ className: "absolute top-0 text-xs text-description"
486
+ }, format(date, "MMM")), border && /* @__PURE__ */ React.createElement("div", {
487
+ className: mx("absolute inset-1 border-2 rounded-full", border)
488
+ }));
264
489
  })));
265
- } finally {
266
- _effect.f();
267
- }
268
- };
269
- CalendarGrid.displayName = "CalendarGrid";
490
+ }, [
491
+ activeRange,
492
+ handleDayPointerDown,
493
+ handleDayPointerEnter,
494
+ handleDayPointerUp,
495
+ hasDate,
496
+ selected,
497
+ weekStartsOn
498
+ ]);
499
+ return /* @__PURE__ */ React.createElement("div", {
500
+ ...composableProps(props, {
501
+ role: "none",
502
+ classNames: [
503
+ "flex flex-col h-full w-full justify-center overflow-hidden outline-hidden",
504
+ classNames
505
+ ]
506
+ }),
507
+ ref: (node) => {
508
+ gridRef.current = node;
509
+ if (typeof forwardedRef === "function") {
510
+ forwardedRef(node);
511
+ } else if (forwardedRef) {
512
+ forwardedRef.current = node;
513
+ }
514
+ },
515
+ tabIndex: 0,
516
+ onKeyDown: handleKeyDown
517
+ }, /* @__PURE__ */ React.createElement("div", {
518
+ className: "grid w-full grid-cols-7",
519
+ style: {
520
+ width: defaultWidth
521
+ }
522
+ }, days.map((date, i) => /* @__PURE__ */ React.createElement("div", {
523
+ key: i,
524
+ className: "flex justify-center p-2 text-sm font-thin"
525
+ }, date))), /* @__PURE__ */ React.createElement("div", {
526
+ className: "flex flex-col h-full w-full justify-center overflow-hidden",
527
+ ref: containerRef
528
+ }, /* @__PURE__ */ React.createElement(List, {
529
+ ref: listRef,
530
+ role: "none",
531
+ className: "scrollbar-none outline-hidden",
532
+ width,
533
+ height: maxHeight ?? height,
534
+ rowCount: maxRows,
535
+ rowHeight: size,
536
+ rowRenderer,
537
+ scrollToAlignment: "start",
538
+ onScroll: handleScroll,
539
+ onRowsRendered: () => setInitialized(true)
540
+ })));
541
+ });
542
+ CalendarGrid.displayName = CALENDAR_GRID_NAME;
270
543
  var Calendar = {
271
544
  Root: CalendarRoot,
272
- Viewport: CalendarViewport,
273
545
  Toolbar: CalendarToolbar,
274
546
  Grid: CalendarGrid
275
547
  };