@dxos/react-ui-calendar 0.8.4-main.d05673bc65 → 0.8.4-main.d9fc60f731

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 CHANGED
@@ -1,8 +1,105 @@
1
- MIT License
2
- Copyright (c) 2022 DXOS
1
+ # Functional Source License, Version 1.1, ALv2 Future License
3
2
 
4
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
3
+ ## Abbreviation
5
4
 
6
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
5
+ FSL-1.1-Apache-2.0
7
6
 
8
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
7
+ ## Notice
8
+
9
+ Copyright 2026 DXOS
10
+
11
+ ## Terms and Conditions
12
+
13
+ ### Licensor ("We")
14
+
15
+ The party offering the Software under these Terms and Conditions.
16
+
17
+ ### The Software
18
+
19
+ The "Software" is each version of the software that we make available under
20
+ these Terms and Conditions, as indicated by our inclusion of these Terms and
21
+ Conditions with the Software.
22
+
23
+ ### License Grant
24
+
25
+ Subject to your compliance with this License Grant and the Patents,
26
+ Redistribution and Trademark clauses below, we hereby grant you the right to
27
+ use, copy, modify, create derivative works, publicly perform, publicly display
28
+ and redistribute the Software for any Permitted Purpose identified below.
29
+
30
+ ### Permitted Purpose
31
+
32
+ A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
33
+ means making the Software available to others in a commercial product or
34
+ service that:
35
+
36
+ 1. substitutes for the Software;
37
+
38
+ 2. substitutes for any other product or service we offer using the Software
39
+ that exists as of the date we make the Software available; or
40
+
41
+ 3. offers the same or substantially similar functionality as the Software.
42
+
43
+ Permitted Purposes specifically include using the Software:
44
+
45
+ 1. for your internal use and access;
46
+
47
+ 2. for non-commercial education;
48
+
49
+ 3. for non-commercial research; and
50
+
51
+ 4. in connection with professional services that you provide to a licensee
52
+ using the Software in accordance with these Terms and Conditions.
53
+
54
+ ### Patents
55
+
56
+ To the extent your use for a Permitted Purpose would necessarily infringe our
57
+ patents, the license grant above includes a license under our patents. If you
58
+ make a claim against any party that the Software infringes or contributes to
59
+ the infringement of any patent, then your patent license to the Software ends
60
+ immediately.
61
+
62
+ ### Redistribution
63
+
64
+ The Terms and Conditions apply to all copies, modifications and derivatives of
65
+ the Software.
66
+
67
+ If you redistribute any copies, modifications or derivatives of the Software,
68
+ you must include a copy of or a link to these Terms and Conditions and not
69
+ remove any copyright notices provided in or with the Software.
70
+
71
+ ### Disclaimer
72
+
73
+ THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
74
+ IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
75
+ PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
76
+
77
+ IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
78
+ SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
79
+ EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
80
+
81
+ ### Trademarks
82
+
83
+ Except for displaying the License Details and identifying us as the origin of
84
+ the Software, you have no right under these Terms and Conditions to use our
85
+ trademarks, trade names, service marks or product names.
86
+
87
+ ## Grant of Future License
88
+
89
+ We hereby irrevocably grant you an additional license to use the Software under
90
+ the Apache License, Version 2.0 that is effective on the second anniversary of
91
+ the date we make the Software available. On or after that date, you may use the
92
+ Software under the Apache License, Version 2.0, in which case the following
93
+ will apply:
94
+
95
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not use
96
+ this file except in compliance with the License.
97
+
98
+ You may obtain a copy of the License at
99
+
100
+ http://www.apache.org/licenses/LICENSE-2.0
101
+
102
+ Unless required by applicable law or agreed to in writing, software distributed
103
+ under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
104
+ CONDITIONS OF ANY KIND, either express or implied. See the License for the
105
+ specific language governing permissions and limitations under the License.
@@ -1,26 +1,17 @@
1
1
  // src/components/Calendar/Calendar.tsx
2
2
  import { createContext } from "@radix-ui/react-context";
3
- import { addDays, differenceInWeeks, format, startOfWeek } from "date-fns";
3
+ import { addDays, format, startOfDay, startOfWeek } from "date-fns";
4
4
  import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
5
5
  import { useResizeDetector } from "react-resize-detector";
6
6
  import { List } from "react-virtualized";
7
7
  import { Event } from "@dxos/async";
8
- import { Icon, IconButton, useTranslation } from "@dxos/react-ui";
8
+ import { IconButton, useTranslation } from "@dxos/react-ui";
9
+ import { composable, composableProps } from "@dxos/react-ui";
9
10
  import { mx } from "@dxos/ui-theme";
10
-
11
- // src/translations.ts
12
- var translationKey = "@dxos/react-ui-calendar";
13
- var translations = [
14
- {
15
- "en-US": {
16
- [translationKey]: {
17
- "today button": "Today"
18
- }
19
- }
20
- }
21
- ];
11
+ import { translationKey } from "#translations";
22
12
 
23
13
  // src/components/Calendar/util.ts
14
+ import { differenceInCalendarDays } from "date-fns";
24
15
  var getDate = (start2, weekNumber, dayOfWeek, weekStartsOn) => {
25
16
  const result = new Date(start2);
26
17
  const startDayOfWeek = start2.getDay();
@@ -28,6 +19,13 @@ var getDate = (start2, weekNumber, dayOfWeek, weekStartsOn) => {
28
19
  result.setDate(start2.getDate() - adjustedStartDay + weekNumber * 7 + dayOfWeek);
29
20
  return result;
30
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
+ };
31
29
  var isSameDay = (date1, date2) => {
32
30
  return !!date2 && date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth() && date1.getDate() === date2.getDate();
33
31
  };
@@ -35,13 +33,46 @@ var isSameDay = (date1, date2) => {
35
33
  // src/components/Calendar/Calendar.tsx
36
34
  var maxRows = 50 * 100;
37
35
  var start = /* @__PURE__ */ new Date("1970-01-01");
38
- var size = 48;
36
+ var size = 40;
39
37
  var defaultWidth = 7 * size;
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;
54
+ }
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;
66
+ }
67
+ return void 0;
68
+ };
40
69
  var [CalendarContextProvider, useCalendarContext] = createContext("Calendar");
41
70
  var CalendarRoot = /* @__PURE__ */ forwardRef(({ children, weekStartsOn = 1 }, forwardedRef) => {
42
71
  const event = useMemo(() => new Event(), []);
43
72
  const [selected, setSelected] = useState();
44
73
  const [index, setIndex] = useState();
74
+ const [range, setRange] = useState();
75
+ const [pendingRange, setPendingRange] = useState();
45
76
  useImperativeHandle(forwardedRef, () => ({
46
77
  scrollTo: (date) => {
47
78
  event.emit({
@@ -58,11 +89,15 @@ var CalendarRoot = /* @__PURE__ */ forwardRef(({ children, weekStartsOn = 1 }, f
58
89
  index,
59
90
  setIndex,
60
91
  selected,
61
- setSelected
92
+ setSelected,
93
+ range,
94
+ setRange,
95
+ pendingRange,
96
+ setPendingRange
62
97
  }, children);
63
98
  });
64
99
  var CALENDAR_TOOLBAR_NAME = "CalendarHeader";
65
- var CalendarToolbar = ({ classNames, ...props }) => {
100
+ var CalendarToolbar = composable(({ classNames, ...props }, forwardedRef) => {
66
101
  const { t } = useTranslation(translationKey);
67
102
  const { weekStartsOn, event, index, selected } = useCalendarContext(CALENDAR_TOOLBAR_NAME);
68
103
  const top = useMemo(() => getDate(start, index ?? 0, 6, weekStartsOn), [
@@ -81,9 +116,14 @@ var CalendarToolbar = ({ classNames, ...props }) => {
81
116
  today
82
117
  ]);
83
118
  return /* @__PURE__ */ React.createElement("div", {
84
- ...props,
85
- role: "none",
86
- className: mx("shrink-0 w-full m-auto grid grid-cols-3 items-center bg-toolbar-surface", classNames),
119
+ ...composableProps(props, {
120
+ role: "none",
121
+ classNames: [
122
+ "shrink-0 grid! grid-cols-3 items-center bg-toolbar-surface",
123
+ classNames
124
+ ]
125
+ }),
126
+ ref: forwardedRef,
87
127
  style: {
88
128
  width: defaultWidth
89
129
  }
@@ -91,40 +131,48 @@ var CalendarToolbar = ({ classNames, ...props }) => {
91
131
  className: "flex justify-start"
92
132
  }, /* @__PURE__ */ React.createElement(IconButton, {
93
133
  variant: "ghost",
94
- size: 5,
95
134
  icon: "ph--calendar--regular",
96
135
  iconOnly: true,
97
136
  classNames: "aspect-square",
98
- label: t("today button"),
137
+ label: t("today.button"),
99
138
  onClick: handleToday
100
139
  })), /* @__PURE__ */ React.createElement("div", {
101
140
  className: "flex justify-center p-2 text-description"
102
141
  }, format(selected ?? top, "MMMM")), /* @__PURE__ */ React.createElement("div", {
103
142
  className: "flex justify-end p-2 text-description"
104
143
  }, (selected ?? top).getFullYear()));
105
- };
144
+ });
106
145
  CalendarToolbar.displayName = CALENDAR_TOOLBAR_NAME;
107
146
  var CALENDAR_GRID_NAME = "CalendarGrid";
108
- var CalendarGrid = ({ classNames, rows, onSelect, ...props }) => {
109
- const { weekStartsOn, event, setIndex, selected, setSelected } = useCalendarContext(CALENDAR_GRID_NAME);
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);
110
149
  const { ref: containerRef, width = 0, height = 0 } = useResizeDetector();
111
150
  const maxHeight = rows ? rows * size : void 0;
112
151
  const listRef = useRef(null);
152
+ const gridRef = useRef(null);
113
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
+ ]);
114
160
  const [initialized, setInitialized] = useState(false);
115
161
  useEffect(() => {
116
- const index = differenceInWeeks(today, start);
162
+ const index = getRowIndex(start, initialDate ?? today, weekStartsOn);
117
163
  listRef.current?.scrollToRow(index);
118
164
  }, [
119
165
  initialized,
120
166
  start,
121
- today
167
+ today,
168
+ initialDate,
169
+ weekStartsOn
122
170
  ]);
123
171
  useEffect(() => {
124
172
  return event.on((event2) => {
125
173
  switch (event2.type) {
126
174
  case "scroll": {
127
- const index = differenceInWeeks(event2.date, start);
175
+ const index = getRowIndex(start, event2.date, weekStartsOn);
128
176
  listRef.current?.scrollToRow(index);
129
177
  break;
130
178
  }
@@ -144,33 +192,274 @@ var CalendarGrid = ({ classNames, rows, onSelect, ...props }) => {
144
192
  return format(day, "EEE");
145
193
  });
146
194
  }, []);
147
- const getNumAppointments = useCallback((_date) => {
148
- return 0;
149
- }, []);
150
- const handleDaySelect = useCallback((date) => {
151
- setSelected((current) => isSameDay(date, current) ? void 0 : date);
152
- onSelect?.({
153
- date
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);
295
+ onSelect?.({
296
+ date
297
+ });
298
+ return;
299
+ }
300
+ const committed = makeRange(anchor, date);
301
+ setRange(committed);
302
+ onSelectRange?.({
303
+ range: committed
154
304
  });
155
305
  }, [
156
- onSelect
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
157
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));
357
+ }
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;
158
452
  const handleScroll = useCallback((info) => {
453
+ scrollTopRef.current = info.scrollTop;
159
454
  setIndex(Math.round(info.scrollTop / size));
160
455
  }, []);
161
456
  const rowRenderer = useCallback(({ key, index, style }) => {
162
- const getBgColor = (date) => date.getMonth() % 2 === 0 && "bg-modal-surface";
457
+ const getBgColor = (date) => date.getMonth() % 2 === 0 ? "bg-group-surface" : "bg-group-alt-surface";
163
458
  return /* @__PURE__ */ React.createElement("div", {
164
459
  key,
165
- ...props,
166
- role: "none",
167
460
  style,
168
- className: "w-full grid grid-cols-[1fr_max-content_1fr] snap-center"
461
+ className: "grid"
169
462
  }, /* @__PURE__ */ React.createElement("div", {
170
- role: "none",
171
- className: mx(getBgColor(getDate(start, index, 0, weekStartsOn)))
172
- }), /* @__PURE__ */ React.createElement("div", {
173
- role: "none",
174
463
  className: "grid grid-cols-7 bg-input-surface",
175
464
  style: {
176
465
  gridTemplateColumns: `repeat(7, ${size}px)`
@@ -179,59 +468,67 @@ var CalendarGrid = ({ classNames, rows, onSelect, ...props }) => {
179
468
  length: 7
180
469
  }).map((_, i) => {
181
470
  const date = getDate(start, index, i, weekStartsOn);
182
- const num = getNumAppointments(date);
183
- const border = isSameDay(date, selected) ? "border-primary-500" : isSameDay(date, today) ? "border-amber-500" : void 0;
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;
184
473
  return /* @__PURE__ */ React.createElement("div", {
185
474
  key: i,
186
- role: "none",
187
- className: mx("relative flex justify-center items-center cursor-pointer", getBgColor(date)),
188
- onClick: () => handleDaySelect(date)
189
- }, /* @__PURE__ */ React.createElement("span", {
190
- className: "text-description"
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"
191
484
  }, date.getDate()), !border && date.getDate() === 1 && /* @__PURE__ */ React.createElement("span", {
192
485
  className: "absolute top-0 text-xs text-description"
193
486
  }, format(date, "MMM")), border && /* @__PURE__ */ React.createElement("div", {
194
- role: "none",
195
- className: mx("absolute top-0 left-0 w-full h-full border-2 rounded-full", border)
196
- }), num > 0 && /* @__PURE__ */ React.createElement(Icon, {
197
- classNames: "absolute bottom-0",
198
- icon: num > 3 ? "ph--dots-three--regular" : "ph--dot--regular",
199
- size: 5
487
+ className: mx("absolute inset-1 border-2 rounded-full", border)
200
488
  }));
201
- })), /* @__PURE__ */ React.createElement("div", {
202
- className: mx(getBgColor(getDate(start, index, 6, weekStartsOn)))
203
- }));
489
+ })));
204
490
  }, [
205
- handleDaySelect,
206
- getNumAppointments,
491
+ activeRange,
492
+ handleDayPointerDown,
493
+ handleDayPointerEnter,
494
+ handleDayPointerUp,
495
+ hasDate,
207
496
  selected,
208
497
  weekStartsOn
209
498
  ]);
210
499
  return /* @__PURE__ */ React.createElement("div", {
211
- role: "none",
212
- className: mx("flex flex-col h-full w-full justify-center overflow-hidden", classNames)
213
- }, /* @__PURE__ */ React.createElement("div", {
214
- role: "none",
215
- className: "flex justify-center bg-group-surface"
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
216
517
  }, /* @__PURE__ */ React.createElement("div", {
217
- role: "none",
218
- className: "flex w-full grid grid-cols-7",
518
+ className: "grid w-full grid-cols-7",
219
519
  style: {
220
520
  width: defaultWidth
221
521
  }
222
522
  }, days.map((date, i) => /* @__PURE__ */ React.createElement("div", {
223
523
  key: i,
224
- role: "none",
225
524
  className: "flex justify-center p-2 text-sm font-thin"
226
- }, date)))), /* @__PURE__ */ React.createElement("div", {
227
- role: "none",
525
+ }, date))), /* @__PURE__ */ React.createElement("div", {
228
526
  className: "flex flex-col h-full w-full justify-center overflow-hidden",
229
527
  ref: containerRef
230
528
  }, /* @__PURE__ */ React.createElement(List, {
231
529
  ref: listRef,
232
530
  role: "none",
233
- // TODO(burdon): Snap isn't working.
234
- className: "[&>div]:snap-y scrollbar-none outline-hidden",
531
+ className: "scrollbar-none outline-hidden",
235
532
  width,
236
533
  height: maxHeight ?? height,
237
534
  rowCount: maxRows,
@@ -241,7 +538,7 @@ var CalendarGrid = ({ classNames, rows, onSelect, ...props }) => {
241
538
  onScroll: handleScroll,
242
539
  onRowsRendered: () => setInitialized(true)
243
540
  })));
244
- };
541
+ });
245
542
  CalendarGrid.displayName = CALENDAR_GRID_NAME;
246
543
  var Calendar = {
247
544
  Root: CalendarRoot,