@calchemy/date-react 0.1.0 → 0.1.1

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/dist/index.cjs ADDED
@@ -0,0 +1,1719 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ Calchemy: () => Calchemy,
24
+ useCalchemy: () => useCalchemy,
25
+ useCalchemyCalendar: () => useCalchemyCalendar,
26
+ useCalchemyContext: () => useCalchemyContext
27
+ });
28
+ module.exports = __toCommonJS(index_exports);
29
+
30
+ // src/components/calendar/context.ts
31
+ var import_react = require("react");
32
+ var CalchemyContext = (0, import_react.createContext)(null);
33
+ var CalendarContext = (0, import_react.createContext)(null);
34
+ var CalendarPeriodContext = (0, import_react.createContext)(null);
35
+ var CalendarScrollContext = (0, import_react.createContext)(null);
36
+ function useCalchemyContext() {
37
+ const state = (0, import_react.useContext)(CalchemyContext);
38
+ if (!state) {
39
+ throw new Error("Calchemy components must be rendered inside Calchemy.Root.");
40
+ }
41
+ return state;
42
+ }
43
+ function useCalchemyCalendar() {
44
+ const state = (0, import_react.useContext)(CalendarContext);
45
+ if (!state) {
46
+ throw new Error("Calchemy calendar components must be rendered inside Calchemy.Calendar.");
47
+ }
48
+ return state;
49
+ }
50
+ function useCalendarPeriod() {
51
+ return (0, import_react.useContext)(CalendarPeriodContext);
52
+ }
53
+
54
+ // src/hooks/useCalchemy.ts
55
+ var import_react3 = require("react");
56
+ var import_date_core = require("@calchemy/date-core");
57
+
58
+ // src/inline-completion.ts
59
+ function composeInlineCompletion(inputValue, completion) {
60
+ return `${inputValue}${completion.suffix}`;
61
+ }
62
+ function formatInlineCompletionDescription(inputValue, completion) {
63
+ return `Suggestion: ${composeInlineCompletion(inputValue, completion)}. Press Tab to accept.`;
64
+ }
65
+
66
+ // src/hooks/useDebouncedValue.ts
67
+ var import_react2 = require("react");
68
+ var PARSE_QUERY_DEBOUNCE_MS = 150;
69
+ function useDebouncedValue(value, delayMs = PARSE_QUERY_DEBOUNCE_MS) {
70
+ const [debouncedValue, setDebouncedValue] = (0, import_react2.useState)(value);
71
+ (0, import_react2.useEffect)(() => {
72
+ const timeoutId = setTimeout(() => {
73
+ setDebouncedValue(value);
74
+ }, delayMs);
75
+ return () => {
76
+ clearTimeout(timeoutId);
77
+ };
78
+ }, [value, delayMs]);
79
+ return debouncedValue;
80
+ }
81
+
82
+ // src/hooks/useCalchemy.ts
83
+ function useCalchemy(options) {
84
+ const [uncontrolledInputValue, setUncontrolledInputValue] = (0, import_react3.useState)(options.defaultInputValue ?? "");
85
+ const [uncontrolledValue, setUncontrolledValue] = (0, import_react3.useState)(options.defaultValue ?? null);
86
+ const [uncontrolledInputMode, setUncontrolledInputMode] = (0, import_react3.useState)(
87
+ options.defaultInputMode ?? "field"
88
+ );
89
+ const inputValue = options.inputValue ?? uncontrolledInputValue;
90
+ const value = options.value ?? uncontrolledValue;
91
+ const inputMode = options.inputMode ?? uncontrolledInputMode;
92
+ const fieldInputActive = inputMode === "field";
93
+ const queryValue = useDebouncedValue(inputValue);
94
+ const queryPending = inputValue !== queryValue;
95
+ const result = (0, import_react3.useMemo)(
96
+ () => options.calchemy.parseDate(queryValue, options.parseContext),
97
+ [queryValue, options.calchemy, options.parseContext]
98
+ );
99
+ const expectedValue = options.expectedValue;
100
+ const expectedOptions = {
101
+ ...options.multipleRangeExpansionLimit === void 0 ? {} : { multipleRangeExpansionLimit: options.multipleRangeExpansionLimit }
102
+ };
103
+ const expectedResult = (0, import_date_core.resolveExpectedDateValue)(result, expectedValue, expectedOptions);
104
+ const valueKindMismatch = expectedResult.status === "invalid" && result.status === "valid";
105
+ const settledInlineCompletion = (0, import_react3.useMemo)(
106
+ () => options.calchemy.getInlineCompletion(queryValue, options.parseContext),
107
+ [queryValue, options.calchemy, options.parseContext]
108
+ );
109
+ const inlineCompletion = queryPending ? null : settledInlineCompletion;
110
+ function updateValue(nextValue, nextResult = result) {
111
+ if (options.value === void 0) {
112
+ setUncontrolledValue(nextValue);
113
+ }
114
+ options.onValueChange?.(nextValue, nextResult);
115
+ }
116
+ (0, import_react3.useEffect)(() => {
117
+ const nextResult = options.calchemy.parseDate(queryValue, options.parseContext);
118
+ const nextExpectedResult = (0, import_date_core.resolveExpectedDateValue)(
119
+ nextResult,
120
+ expectedValue,
121
+ expectedOptions
122
+ );
123
+ if (nextExpectedResult.status === "valid") {
124
+ if (options.value === void 0) {
125
+ setUncontrolledValue(nextExpectedResult.value);
126
+ }
127
+ options.onValueChange?.(nextExpectedResult.value, nextExpectedResult);
128
+ }
129
+ }, [
130
+ queryValue,
131
+ expectedValue,
132
+ expectedOptions.multipleRangeExpansionLimit,
133
+ options.calchemy,
134
+ options.parseContext
135
+ ]);
136
+ function updateInputValue(nextValue) {
137
+ if (options.inputValue === void 0) {
138
+ setUncontrolledInputValue(nextValue);
139
+ }
140
+ options.onInputValueChange?.(nextValue);
141
+ }
142
+ function getActiveInlineCompletion() {
143
+ if (!fieldInputActive || queryPending) {
144
+ return null;
145
+ }
146
+ return settledInlineCompletion;
147
+ }
148
+ function setInputMode(nextMode) {
149
+ if (options.inputMode === void 0) {
150
+ setUncontrolledInputMode(nextMode);
151
+ }
152
+ options.onInputModeChange?.(nextMode);
153
+ }
154
+ function acceptCompletion() {
155
+ const activeCompletion = getActiveInlineCompletion();
156
+ if (!activeCompletion) {
157
+ return;
158
+ }
159
+ updateInputValue(composeInlineCompletion(inputValue, activeCompletion));
160
+ }
161
+ function selectCandidate(candidateId) {
162
+ if (!fieldInputActive || result.status !== "ambiguous") {
163
+ return;
164
+ }
165
+ const candidate = result.candidates.find((item) => item.id === candidateId);
166
+ if (!candidate) {
167
+ return;
168
+ }
169
+ const candidateResult = {
170
+ status: "valid",
171
+ input: inputValue,
172
+ value: candidate.value,
173
+ candidates: [candidate],
174
+ corrections: result.corrections,
175
+ warnings: result.warnings
176
+ };
177
+ const resolvedCandidateResult = (0, import_date_core.resolveExpectedDateValue)(candidateResult, expectedValue, expectedOptions);
178
+ if (resolvedCandidateResult.status !== "valid") {
179
+ return;
180
+ }
181
+ updateValue(resolvedCandidateResult.value, resolvedCandidateResult);
182
+ }
183
+ function selectDate(nextValue) {
184
+ const nextResult = {
185
+ status: "valid",
186
+ input: inputValue,
187
+ value: nextValue,
188
+ candidates: [],
189
+ corrections: [],
190
+ warnings: []
191
+ };
192
+ const resolvedResult = (0, import_date_core.resolveExpectedDateValue)(nextResult, expectedValue, expectedOptions);
193
+ if (resolvedResult.status !== "valid") {
194
+ return;
195
+ }
196
+ updateValue(resolvedResult.value, resolvedResult);
197
+ if (resolvedResult.value.kind === "single") {
198
+ updateInputValue(resolvedResult.value.date.toString());
199
+ }
200
+ }
201
+ return {
202
+ calchemy: options.calchemy,
203
+ parseContext: options.parseContext,
204
+ inputValue,
205
+ value,
206
+ result: expectedResult,
207
+ expectedValue,
208
+ inputMode,
209
+ valueKindMismatch,
210
+ inlineCompletion,
211
+ setInputValue: updateInputValue,
212
+ setInputMode,
213
+ acceptCompletion,
214
+ selectCandidate,
215
+ selectDate,
216
+ getInputProps() {
217
+ const activeCompletion = getActiveInlineCompletion();
218
+ return {
219
+ value: inputValue,
220
+ readOnly: !fieldInputActive,
221
+ onChange(event) {
222
+ if (!fieldInputActive) {
223
+ return;
224
+ }
225
+ updateInputValue(event.currentTarget.value);
226
+ },
227
+ onKeyDown(event) {
228
+ if (!fieldInputActive) {
229
+ return;
230
+ }
231
+ if (event.key === "Tab" && activeCompletion) {
232
+ event.preventDefault();
233
+ acceptCompletion();
234
+ }
235
+ },
236
+ "aria-invalid": expectedResult.status === "invalid",
237
+ ...fieldInputActive && inlineCompletion ? {
238
+ "aria-description": formatInlineCompletionDescription(
239
+ inputValue,
240
+ inlineCompletion
241
+ )
242
+ } : {},
243
+ "calchemy-status": valueKindMismatch ? "kind-mismatch" : expectedResult.status,
244
+ "calchemy-expected-value": expectedValue,
245
+ "calchemy-value-kind": expectedResult.status === "valid" ? expectedResult.value.kind : void 0
246
+ };
247
+ }
248
+ };
249
+ }
250
+
251
+ // src/components/calendar/Calendar.tsx
252
+ var import_react6 = require("react");
253
+
254
+ // src/components/calendar/CalendarGrid.tsx
255
+ var import_react5 = require("react");
256
+
257
+ // src/components/calendar/calendar-period-drag.tsx
258
+ var import_react4 = require("react");
259
+ var import_jsx_runtime = require("react/jsx-runtime");
260
+ var CalendarPeriodDragContext = (0, import_react4.createContext)(null);
261
+ var multipleDragSurfaceStyle = {
262
+ position: "relative",
263
+ userSelect: "none",
264
+ WebkitUserSelect: "none",
265
+ touchAction: "none"
266
+ };
267
+ var multiplePeriodListDragSurfaceStyle = {
268
+ ...multipleDragSurfaceStyle,
269
+ position: "relative",
270
+ background: "transparent"
271
+ };
272
+ function useOptionalCalendarPeriodDrag() {
273
+ return (0, import_react4.useContext)(CalendarPeriodDragContext);
274
+ }
275
+ function useCalendarPeriodDragSurface(dragSelection = true, surfaceElementRef) {
276
+ const calendar = useCalchemyCalendar();
277
+ const multipleSelection = dragSelection && calendar.calchemy.expectedValue === "multiple";
278
+ const surfaceRef = (0, import_react4.useRef)(null);
279
+ const dayCells = (0, import_react4.useRef)(/* @__PURE__ */ new Map());
280
+ const suppressClickRef = (0, import_react4.useRef)(false);
281
+ const dragStateRef = (0, import_react4.useRef)(null);
282
+ const dragPreviewFrameRef = (0, import_react4.useRef)(null);
283
+ const dragGestureCleanupRef = (0, import_react4.useRef)(null);
284
+ const [dragState, setDragState] = (0, import_react4.useState)(null);
285
+ const [dragRectangle, setDragRectangle] = (0, import_react4.useState)(null);
286
+ const previewSelectedKeys = (0, import_react4.useMemo)(
287
+ () => new Set(dragState?.previewKeys ?? []),
288
+ [dragState]
289
+ );
290
+ const acquireDragGestureLock = (0, import_react4.useCallback)(() => {
291
+ if (dragGestureCleanupRef.current || typeof document === "undefined") {
292
+ return;
293
+ }
294
+ const preventGestureDefault = (event) => {
295
+ event.preventDefault();
296
+ };
297
+ const clearDocumentSelection = () => {
298
+ document.getSelection()?.removeAllRanges();
299
+ };
300
+ const previousBodyUserSelect = document.body.style.userSelect;
301
+ const previousDocumentUserSelect = document.documentElement.style.userSelect;
302
+ document.body.style.userSelect = "none";
303
+ document.documentElement.style.userSelect = "none";
304
+ document.addEventListener("pointermove", preventGestureDefault, { capture: true, passive: false });
305
+ document.addEventListener("touchmove", preventGestureDefault, { capture: true, passive: false });
306
+ document.addEventListener("wheel", preventGestureDefault, { capture: true, passive: false });
307
+ document.addEventListener("selectstart", preventGestureDefault, { capture: true });
308
+ document.addEventListener("dragstart", preventGestureDefault, { capture: true });
309
+ clearDocumentSelection();
310
+ dragGestureCleanupRef.current = () => {
311
+ document.removeEventListener("pointermove", preventGestureDefault, { capture: true });
312
+ document.removeEventListener("touchmove", preventGestureDefault, { capture: true });
313
+ document.removeEventListener("wheel", preventGestureDefault, { capture: true });
314
+ document.removeEventListener("selectstart", preventGestureDefault, { capture: true });
315
+ document.removeEventListener("dragstart", preventGestureDefault, { capture: true });
316
+ document.body.style.userSelect = previousBodyUserSelect;
317
+ document.documentElement.style.userSelect = previousDocumentUserSelect;
318
+ clearDocumentSelection();
319
+ dragGestureCleanupRef.current = null;
320
+ };
321
+ }, []);
322
+ const releaseDragGestureLock = (0, import_react4.useCallback)(() => {
323
+ dragGestureCleanupRef.current?.();
324
+ if (dragPreviewFrameRef.current !== null) {
325
+ cancelAnimationFrame(dragPreviewFrameRef.current);
326
+ dragPreviewFrameRef.current = null;
327
+ }
328
+ }, []);
329
+ const scheduleDragPreviewUpdate = (0, import_react4.useCallback)((nextDragState) => {
330
+ dragStateRef.current = nextDragState;
331
+ if (dragPreviewFrameRef.current !== null) {
332
+ return;
333
+ }
334
+ dragPreviewFrameRef.current = requestAnimationFrame(() => {
335
+ setDragState(dragStateRef.current);
336
+ dragPreviewFrameRef.current = null;
337
+ });
338
+ }, []);
339
+ const registerDay = (0, import_react4.useCallback)((element, date, disabled) => {
340
+ const key = date.toString();
341
+ if (element) {
342
+ dayCells.current.set(key, { date, disabled, element });
343
+ return;
344
+ }
345
+ dayCells.current.delete(key);
346
+ }, []);
347
+ const getDayCellFromTarget = (0, import_react4.useCallback)((target) => {
348
+ if (!(target instanceof Element)) {
349
+ return null;
350
+ }
351
+ const button = target.closest("[calchemy-date]");
352
+ if (!(button instanceof HTMLButtonElement)) {
353
+ return null;
354
+ }
355
+ for (const cell of dayCells.current.values()) {
356
+ if (cell.element === button) {
357
+ return cell;
358
+ }
359
+ }
360
+ return null;
361
+ }, []);
362
+ const commitMultipleSelection = (0, import_react4.useCallback)(
363
+ (keys, fallbackDates = []) => {
364
+ const dates = resolveDateKeys(keys, dayCells.current, fallbackDates);
365
+ calendar.selectValue({
366
+ kind: "multiple",
367
+ dates
368
+ });
369
+ },
370
+ [calendar]
371
+ );
372
+ const endDragGesture = (0, import_react4.useCallback)(
373
+ (pointerId) => {
374
+ if (pointerId !== void 0) {
375
+ surfaceRef.current?.releasePointerCapture?.(pointerId);
376
+ }
377
+ releaseDragGestureLock();
378
+ dragStateRef.current = null;
379
+ setDragState(null);
380
+ setDragRectangle(null);
381
+ },
382
+ [releaseDragGestureLock]
383
+ );
384
+ const handlePointerMove = (0, import_react4.useCallback)(
385
+ (event) => {
386
+ const activeDrag = dragStateRef.current;
387
+ if (!multipleSelection || !activeDrag || event.pointerId !== activeDrag.pointerId) {
388
+ return;
389
+ }
390
+ event.preventDefault();
391
+ const current = getPointerPoint(event);
392
+ setDragRectangle({ start: activeDrag.start, current });
393
+ const dragRect = getDragRect(activeDrag.start, current);
394
+ const clipRect = getDragClipRect(surfaceRef.current);
395
+ const gestureKeys = getIntersectingDateKeys(
396
+ dayCells.current,
397
+ dragRect,
398
+ activeDrag.cellBounds,
399
+ clipRect
400
+ );
401
+ const baseKeys = activeDrag.baseDates.map((date) => date.toString());
402
+ const previewKeys = toggleDateKeys(baseKeys, gestureKeys);
403
+ const hasMoved = activeDrag.hasMoved || hasPointerMoved(activeDrag.start, current);
404
+ scheduleDragPreviewUpdate({
405
+ ...activeDrag,
406
+ current,
407
+ previewKeys,
408
+ hasMoved
409
+ });
410
+ },
411
+ [multipleSelection, scheduleDragPreviewUpdate]
412
+ );
413
+ const handlePointerDownCapture = (0, import_react4.useCallback)(
414
+ (event) => {
415
+ if (!multipleSelection || event.button !== 0 || !event.isPrimary) {
416
+ return;
417
+ }
418
+ const baseDates = getMultipleDates(calendar.selected);
419
+ event.preventDefault();
420
+ if (typeof document !== "undefined") {
421
+ document.getSelection()?.removeAllRanges();
422
+ }
423
+ surfaceRef.current = surfaceElementRef?.current ?? event.currentTarget;
424
+ surfaceRef.current?.setPointerCapture?.(event.pointerId);
425
+ acquireDragGestureLock();
426
+ const nextDragState = {
427
+ pointerId: event.pointerId,
428
+ start: getPointerPoint(event),
429
+ current: getPointerPoint(event),
430
+ baseDates,
431
+ previewKeys: baseDates.map((selectedDate) => selectedDate.toString()),
432
+ hasMoved: false,
433
+ startDayCell: getDayCellFromTarget(event.target),
434
+ cellBounds: snapshotCellBounds(dayCells.current)
435
+ };
436
+ dragStateRef.current = nextDragState;
437
+ setDragState(nextDragState);
438
+ setDragRectangle({
439
+ start: nextDragState.start,
440
+ current: nextDragState.current
441
+ });
442
+ },
443
+ [acquireDragGestureLock, calendar.selected, getDayCellFromTarget, multipleSelection, surfaceElementRef]
444
+ );
445
+ const handlePointerUp = (0, import_react4.useCallback)(
446
+ (event) => {
447
+ const activeDrag = dragStateRef.current;
448
+ if (!multipleSelection || !activeDrag || event.pointerId !== activeDrag.pointerId) {
449
+ return;
450
+ }
451
+ if (dragPreviewFrameRef.current !== null) {
452
+ cancelAnimationFrame(dragPreviewFrameRef.current);
453
+ dragPreviewFrameRef.current = null;
454
+ setDragState(activeDrag);
455
+ }
456
+ event.preventDefault();
457
+ suppressClickRef.current = true;
458
+ setTimeout(() => {
459
+ suppressClickRef.current = false;
460
+ }, 0);
461
+ if (activeDrag.hasMoved) {
462
+ commitMultipleSelection(activeDrag.previewKeys, activeDrag.baseDates);
463
+ } else {
464
+ const dayCell = activeDrag.startDayCell;
465
+ if (dayCell && !dayCell.disabled) {
466
+ const selectedDates = getMultipleDates(calendar.selected);
467
+ const nextKeys = toggleDateKeys(
468
+ selectedDates.map((selectedDate) => selectedDate.toString()),
469
+ [dayCell.date.toString()]
470
+ );
471
+ commitMultipleSelection(nextKeys, selectedDates.concat(dayCell.date));
472
+ }
473
+ }
474
+ endDragGesture(event.pointerId);
475
+ const focused = document.activeElement;
476
+ if (focused instanceof HTMLElement && event.currentTarget.contains(focused)) {
477
+ focused.blur();
478
+ }
479
+ },
480
+ [calendar.selected, commitMultipleSelection, endDragGesture, getDayCellFromTarget, multipleSelection]
481
+ );
482
+ const handlePointerCancel = (0, import_react4.useCallback)(
483
+ (event) => {
484
+ const activeDrag = dragStateRef.current;
485
+ if (!activeDrag || event.pointerId !== activeDrag.pointerId) {
486
+ return;
487
+ }
488
+ endDragGesture(event.pointerId);
489
+ },
490
+ [endDragGesture]
491
+ );
492
+ return (0, import_react4.useMemo)(() => {
493
+ if (!multipleSelection) {
494
+ return null;
495
+ }
496
+ return {
497
+ multipleSelection,
498
+ dragState,
499
+ dragRectangle,
500
+ previewSelectedKeys,
501
+ suppressClickRef,
502
+ surfaceRef,
503
+ registerDay,
504
+ handlePointerDownCapture,
505
+ handlePointerMove,
506
+ handlePointerUp,
507
+ handlePointerCancel
508
+ };
509
+ }, [
510
+ dragRectangle,
511
+ dragState,
512
+ handlePointerCancel,
513
+ handlePointerDownCapture,
514
+ handlePointerMove,
515
+ handlePointerUp,
516
+ multipleSelection,
517
+ previewSelectedKeys,
518
+ registerDay
519
+ ]);
520
+ }
521
+ function CalendarDragRectangleOverlay({
522
+ dragRectangle: dragRectangleProp,
523
+ surfaceRef: surfaceRefProp
524
+ } = {}) {
525
+ const drag = useOptionalCalendarPeriodDrag();
526
+ const dragRectangle = dragRectangleProp ?? drag?.dragRectangle ?? null;
527
+ const surfaceRef = surfaceRefProp ?? drag?.surfaceRef;
528
+ if (!dragRectangle) {
529
+ return null;
530
+ }
531
+ const surface = surfaceRef?.current;
532
+ if (!surface) {
533
+ return null;
534
+ }
535
+ const surfaceRect = surface.getBoundingClientRect();
536
+ const left = Math.min(dragRectangle.start.x, dragRectangle.current.x) - surfaceRect.left;
537
+ const top = Math.min(dragRectangle.start.y, dragRectangle.current.y) - surfaceRect.top;
538
+ const width = Math.abs(dragRectangle.current.x - dragRectangle.start.x);
539
+ const height = Math.abs(dragRectangle.current.y - dragRectangle.start.y);
540
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
541
+ "div",
542
+ {
543
+ "aria-hidden": "true",
544
+ "calchemy-drag-rect": "",
545
+ style: {
546
+ position: "absolute",
547
+ left,
548
+ top,
549
+ width,
550
+ height,
551
+ pointerEvents: "none",
552
+ boxSizing: "border-box"
553
+ }
554
+ }
555
+ );
556
+ }
557
+ function CalendarPeriodDragProvider({ value, children }) {
558
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CalendarPeriodDragContext.Provider, { value, children });
559
+ }
560
+ function getMultipleDates(value) {
561
+ return value?.kind === "multiple" ? value.dates : [];
562
+ }
563
+ function getSelectedDateKeys(value) {
564
+ return getMultipleDates(value).map((date) => date.toString());
565
+ }
566
+ function toggleDateKeys(baseKeys, toggledKeys) {
567
+ const next = new Set(baseKeys);
568
+ for (const key of toggledKeys) {
569
+ if (next.has(key)) {
570
+ next.delete(key);
571
+ } else {
572
+ next.add(key);
573
+ }
574
+ }
575
+ return Array.from(next).sort();
576
+ }
577
+ function getPointerPoint(event) {
578
+ return { x: event.clientX, y: event.clientY };
579
+ }
580
+ function hasPointerMoved(start, current) {
581
+ return Math.abs(start.x - current.x) > 2 || Math.abs(start.y - current.y) > 2;
582
+ }
583
+ function getDragRect(start, current) {
584
+ const left = Math.min(start.x, current.x);
585
+ const right = Math.max(start.x, current.x);
586
+ const top = Math.min(start.y, current.y);
587
+ const bottom = Math.max(start.y, current.y);
588
+ return {
589
+ left,
590
+ right,
591
+ top,
592
+ bottom,
593
+ x: left,
594
+ y: top,
595
+ width: right - left,
596
+ height: bottom - top,
597
+ toJSON: () => ({})
598
+ };
599
+ }
600
+ function snapshotCellBounds(cells) {
601
+ const bounds = /* @__PURE__ */ new Map();
602
+ for (const [key, cell] of cells) {
603
+ const rect = cell.element.getBoundingClientRect();
604
+ bounds.set(key, {
605
+ left: rect.left,
606
+ right: rect.right,
607
+ top: rect.top,
608
+ bottom: rect.bottom
609
+ });
610
+ }
611
+ return bounds;
612
+ }
613
+ function getDragClipRect(surface) {
614
+ if (!surface) {
615
+ return null;
616
+ }
617
+ const scrollContainer = surface.closest("[calchemy-scroll]");
618
+ if (!(scrollContainer instanceof HTMLElement)) {
619
+ return null;
620
+ }
621
+ const rect = scrollContainer.getBoundingClientRect();
622
+ return {
623
+ left: rect.left,
624
+ right: rect.right,
625
+ top: rect.top,
626
+ bottom: rect.bottom
627
+ };
628
+ }
629
+ function clipBoundsToRect(bounds, clip) {
630
+ const left = Math.max(bounds.left, clip.left);
631
+ const right = Math.min(bounds.right, clip.right);
632
+ const top = Math.max(bounds.top, clip.top);
633
+ const bottom = Math.min(bounds.bottom, clip.bottom);
634
+ if (left > right || top > bottom) {
635
+ return null;
636
+ }
637
+ return { left, right, top, bottom };
638
+ }
639
+ function getIntersectingDateKeys(cells, dragRect, cellBounds, clipRect = null) {
640
+ const clippedDragRect = clipRect ? clipBoundsToRect(dragRect, clipRect) : dragRect;
641
+ if (!clippedDragRect) {
642
+ return [];
643
+ }
644
+ return Array.from(cells.entries()).filter(([key, cell]) => {
645
+ const bounds = cellBounds.get(key);
646
+ if (!bounds || cell.disabled) {
647
+ return false;
648
+ }
649
+ const visibleBounds = clipRect ? clipBoundsToRect(bounds, clipRect) : bounds;
650
+ return visibleBounds && rectsIntersect(clippedDragRect, visibleBounds);
651
+ }).map(([key]) => key);
652
+ }
653
+ function rectsIntersect(left, right) {
654
+ return left.left <= right.right && left.right >= right.left && left.top <= right.bottom && left.bottom >= right.top;
655
+ }
656
+ function resolveDateKeys(keys, cells, fallbackDates) {
657
+ const fallbackByKey = new Map(fallbackDates.map((date) => [date.toString(), date]));
658
+ return Array.from(keys).sort().flatMap((key) => {
659
+ const date = cells.get(key)?.date ?? fallbackByKey.get(key);
660
+ return date ? [date] : [];
661
+ });
662
+ }
663
+
664
+ // src/components/calendar/date-model.ts
665
+ var defaultDateOrderPreference = ["DMY", "MDY", "YMD"];
666
+ function getFirstVisibleCalendarPeriod(calendar) {
667
+ const period = calendar.visiblePeriods[0];
668
+ if (!period) {
669
+ throw new Error("Calchemy.Calendar requires at least one visible generated period.");
670
+ }
671
+ return period;
672
+ }
673
+ function getCalendarDayState(calendar, period, date) {
674
+ const bounded = isDateWithinBounds(date, calendar.bounds);
675
+ const namedDates = getNamedDatesForDate(calendar, date);
676
+ const disabled = !bounded || (calendar.isDateDisabled?.(date, calendar) ?? false);
677
+ return {
678
+ outside: isBefore(date, period.start) || isAfter(date, period.end),
679
+ selected: isSelectedDate(calendar.selected, date),
680
+ today: calendar.today.equals(date),
681
+ weekend: isWeekend(date),
682
+ firstOfPeriod: date.equals(period.start),
683
+ lastOfPeriod: date.equals(period.end),
684
+ bounded,
685
+ disabled,
686
+ namedDates
687
+ };
688
+ }
689
+ function parseCalendarDuration(value, propName) {
690
+ const hasMonths = "months" in value;
691
+ const hasWeeks = "weeks" in value;
692
+ if (hasMonths === hasWeeks) {
693
+ throw new Error(`Calchemy.Calendar ${propName} must include exactly one of months or weeks.`);
694
+ }
695
+ const count = hasMonths ? value.months : value.weeks;
696
+ if (!Number.isInteger(count) || count < 1) {
697
+ throw new Error(`Calchemy.Calendar ${propName} must be a positive integer duration.`);
698
+ }
699
+ return {
700
+ unit: hasMonths ? "month" : "week",
701
+ count
702
+ };
703
+ }
704
+ function validateCalendarBounds(bounds) {
705
+ if (bounds?.start && bounds.end && isAfter(bounds.start, bounds.end)) {
706
+ throw new Error("Calchemy.Calendar bounds.start must be on or before bounds.end.");
707
+ }
708
+ }
709
+ function clampDateToBounds(date, bounds) {
710
+ if (bounds?.start && isBefore(date, bounds.start)) {
711
+ return bounds.start;
712
+ }
713
+ if (bounds?.end && isAfter(date, bounds.end)) {
714
+ return bounds.end;
715
+ }
716
+ return date;
717
+ }
718
+ function isDateWithinBounds(date, bounds) {
719
+ return (!bounds?.start || !isBefore(date, bounds.start)) && (!bounds?.end || !isAfter(date, bounds.end));
720
+ }
721
+ function periodIntersectsBounds(period, bounds) {
722
+ return (!bounds?.start || !isBefore(period.end, bounds.start)) && (!bounds?.end || !isAfter(period.start, bounds.end));
723
+ }
724
+ function getCalendarPeriodAtOffset(anchor, period, weekStartsOn, locale, offset) {
725
+ const firstStart = period.unit === "month" ? startOfMonth(anchor) : startOfWeek(anchor, weekStartsOn);
726
+ const start = addCalendarPeriod(firstStart, period.unit, offset);
727
+ const end = period.unit === "month" ? endOfMonth(start) : start.add({ days: 6 });
728
+ return {
729
+ id: `${period.unit}-${start.toString()}`,
730
+ unit: period.unit,
731
+ index: offset,
732
+ start,
733
+ end,
734
+ label: formatPeriodLabel(start, end, period.unit, locale)
735
+ };
736
+ }
737
+ function getInitialPeriodExtensions(period) {
738
+ return {
739
+ before: period.count * 6,
740
+ after: period.count * 3
741
+ };
742
+ }
743
+ function getSelectedValue(state) {
744
+ const resultValue = getResultValue(state);
745
+ return state.value ?? resultValue;
746
+ }
747
+ function getDateValueAnchor(value) {
748
+ if (!value) {
749
+ return null;
750
+ }
751
+ switch (value.kind) {
752
+ case "single":
753
+ return value.date;
754
+ case "range":
755
+ return value.start;
756
+ case "multiple":
757
+ return value.dates[0] ?? null;
758
+ }
759
+ }
760
+ function getDateValueKey(value) {
761
+ if (!value) {
762
+ return "";
763
+ }
764
+ switch (value.kind) {
765
+ case "single":
766
+ return `single:${value.date.toString()}`;
767
+ case "range":
768
+ return `range:${value.start.toString()}:${value.end.toString()}`;
769
+ case "multiple":
770
+ return `multiple:${value.dates.map((date) => date.toString()).join(",")}`;
771
+ }
772
+ }
773
+ function isDateInCalendarViewport(date, periods, visiblePeriodIndex, windowCount) {
774
+ return periods.filter(
775
+ (period) => period.index >= visiblePeriodIndex && period.index < visiblePeriodIndex + windowCount
776
+ ).some((period) => !isBefore(date, period.start) && !isAfter(date, period.end));
777
+ }
778
+ function getResultValue(state) {
779
+ return state.result.status === "valid" ? state.result.value : null;
780
+ }
781
+ function getToday(state) {
782
+ if (state.parseContext?.referenceDate) {
783
+ return state.parseContext.referenceDate;
784
+ }
785
+ const todayResult = state.calchemy.parseDate("today", state.parseContext);
786
+ if (todayResult.status === "valid" && todayResult.value.kind === "single") {
787
+ return todayResult.value.date;
788
+ }
789
+ return state.calchemy.Temporal.Now.plainDateISO(state.parseContext?.timeZone);
790
+ }
791
+ function buildCalendarPeriods(anchor, period, weekStartsOn, locale, extensions, bounds) {
792
+ const total = extensions.before + period.count + extensions.after;
793
+ return Array.from({ length: total }, (_, index) => {
794
+ const offset = index - extensions.before;
795
+ return getCalendarPeriodAtOffset(anchor, period, weekStartsOn, locale, offset);
796
+ }).filter((item) => periodIntersectsBounds(item, bounds));
797
+ }
798
+ function buildCalendarWeeks(period, weekStartsOn) {
799
+ const start = startOfWeek(period.start, weekStartsOn);
800
+ const end = endOfWeek(period.end, weekStartsOn);
801
+ const weeks = [];
802
+ let cursor = start;
803
+ while (!isAfter(cursor, end)) {
804
+ const week = Array.from({ length: 7 }, (_, index) => cursor.add({ days: index }));
805
+ weeks.push(week);
806
+ cursor = cursor.add({ days: 7 });
807
+ }
808
+ return weeks;
809
+ }
810
+ function buildWeekdays(calendar, weekdayFormat = "short") {
811
+ const sunday = startOfWeek(calendar.today, 0);
812
+ const first = startOfWeek(calendar.today, calendar.weekStartsOn);
813
+ return Array.from({ length: 7 }, (_, index) => {
814
+ const date = first.add({ days: index });
815
+ const weekdayIndex = sunday.until(date).days % 7;
816
+ return {
817
+ index: weekdayIndex,
818
+ label: date.toLocaleString(calendar.locale, { weekday: weekdayFormat }),
819
+ weekend: isWeekend(date)
820
+ };
821
+ });
822
+ }
823
+ function addCalendarPeriod(date, unit, count) {
824
+ return unit === "month" ? date.add({ months: count }) : date.add({ weeks: count });
825
+ }
826
+ function formatCalendarWindowLabel(periods, locale) {
827
+ const first = periods[0];
828
+ const last = periods.at(-1);
829
+ if (!first || !last) {
830
+ return "";
831
+ }
832
+ if (first.start.equals(last.start) && first.end.equals(last.end)) {
833
+ return first.label;
834
+ }
835
+ return `${formatDateLabel(first.start, locale)} - ${formatDateLabel(last.end, locale)}`;
836
+ }
837
+ function formatMonthLabel(date, locale) {
838
+ return date.toLocaleString(locale, { month: "long" });
839
+ }
840
+ function startOfMonth(date) {
841
+ return date.with({ day: 1 });
842
+ }
843
+ function endOfMonth(date) {
844
+ return startOfMonth(date).add({ months: 1 }).subtract({ days: 1 });
845
+ }
846
+ function startOfWeek(date, weekStartsOn) {
847
+ const temporalWeekday = weekStartsOn === 0 ? 7 : weekStartsOn;
848
+ let cursor = date;
849
+ while (cursor.dayOfWeek !== temporalWeekday) {
850
+ cursor = cursor.subtract({ days: 1 });
851
+ }
852
+ return cursor;
853
+ }
854
+ function endOfWeek(date, weekStartsOn) {
855
+ return startOfWeek(date, weekStartsOn).add({ days: 6 });
856
+ }
857
+ function isBefore(left, right) {
858
+ return left.toString() < right.toString();
859
+ }
860
+ function isAfter(left, right) {
861
+ return left.toString() > right.toString();
862
+ }
863
+ function isSelectedDate(value, date) {
864
+ if (!value) {
865
+ return false;
866
+ }
867
+ switch (value.kind) {
868
+ case "single":
869
+ return value.date.equals(date);
870
+ case "range":
871
+ return !isBefore(date, value.start) && !isAfter(date, value.end);
872
+ case "multiple":
873
+ return value.dates.some((selectedDate) => selectedDate.equals(date));
874
+ }
875
+ }
876
+ function isWeekend(date) {
877
+ return date.dayOfWeek === 6 || date.dayOfWeek === 7;
878
+ }
879
+ function getNamedDatesForDate(calendar, date) {
880
+ if (!calendar.namedDates) {
881
+ return [];
882
+ }
883
+ const context = getResolvedNamedDateContext(calendar);
884
+ return calendar.calchemy.calchemy.namedDatesVocabulary.filter((entry) => {
885
+ if (calendar.namedDates === "holidays" && !entry.isHoliday) {
886
+ return false;
887
+ }
888
+ return entry.resolveDate({ year: date.year, context })?.equals(date) ?? false;
889
+ });
890
+ }
891
+ function getResolvedNamedDateContext(calendar) {
892
+ const context = calendar.calchemy.parseContext;
893
+ return {
894
+ referenceDate: context?.referenceDate ?? calendar.calchemy.calchemy.Temporal.Now.plainDateISO(context?.timeZone),
895
+ locale: context?.locale ?? "en-US",
896
+ weekStartsOn: context?.weekStartsOn ?? 0,
897
+ dateOrderPreference: normalizeDateOrderPreference(context?.dateOrderPreference),
898
+ lastNDaysIncludesToday: context?.lastNDaysIncludesToday ?? true
899
+ };
900
+ }
901
+ function normalizeDateOrderPreference(value) {
902
+ if (!value || value.length === 0) {
903
+ return defaultDateOrderPreference;
904
+ }
905
+ return Array.from(new Set(value));
906
+ }
907
+ function formatPeriodLabel(start, end, unit, locale) {
908
+ if (unit === "month") {
909
+ return formatDateLabel(start, locale);
910
+ }
911
+ return `${start.toLocaleString(locale, { month: "short", day: "numeric" })} - ${end.toLocaleString(locale, {
912
+ month: "short",
913
+ day: "numeric",
914
+ year: "numeric"
915
+ })}`;
916
+ }
917
+ function formatDateLabel(date, locale) {
918
+ return date.toLocaleString(locale, { month: "long", year: "numeric" });
919
+ }
920
+
921
+ // src/components/calendar/CalendarGrid.tsx
922
+ var import_jsx_runtime2 = require("react/jsx-runtime");
923
+ var defaultCalendarWeekdayFormat = "short";
924
+ function CalendarWeekdays({
925
+ weekdayFormat = defaultCalendarWeekdayFormat,
926
+ ...props
927
+ }) {
928
+ const calendar = useCalchemyCalendar();
929
+ const weekdays = buildWeekdays(calendar, weekdayFormat);
930
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ...props, "calchemy-days": "", children: weekdays.map((weekday) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
931
+ "div",
932
+ {
933
+ "calchemy-weekday": "",
934
+ "calchemy-weekend": weekday.weekend ? "" : void 0,
935
+ children: weekday.label
936
+ },
937
+ weekday.index
938
+ )) });
939
+ }
940
+ function CalendarGrid({
941
+ showBookends = false,
942
+ dragSelection = true,
943
+ style,
944
+ onPointerDownCapture,
945
+ onPointerMove,
946
+ onPointerUp,
947
+ onPointerCancel,
948
+ onDragStart,
949
+ ...props
950
+ }) {
951
+ const calendar = useCalchemyCalendar();
952
+ const period = useCalendarPeriod() ?? getFirstVisibleCalendarPeriod(calendar);
953
+ const weeks = buildCalendarWeeks(period, calendar.weekStartsOn);
954
+ const parentDrag = useOptionalCalendarPeriodDrag();
955
+ const localDrag = useCalendarPeriodDragSurface(
956
+ calendar.editable && dragSelection && parentDrag === null
957
+ );
958
+ const drag = parentDrag ?? localDrag;
959
+ const multipleSelection = Boolean(drag?.multipleSelection);
960
+ const useLocalDragHandlers = Boolean(drag && parentDrag === null);
961
+ const committedSelectedKeys = (0, import_react5.useMemo)(() => new Set(getSelectedDateKeys(calendar.selected)), [calendar.selected]);
962
+ const previewSelectedKeys = drag?.previewSelectedKeys ?? /* @__PURE__ */ new Set();
963
+ const dragState = drag?.dragState ?? null;
964
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
965
+ "div",
966
+ {
967
+ ...props,
968
+ "calchemy-grid": "",
969
+ "calchemy-multiple-drag": useLocalDragHandlers ? "" : void 0,
970
+ "calchemy-dragging": useLocalDragHandlers && dragState ? "" : void 0,
971
+ style: useLocalDragHandlers ? { ...multipleDragSurfaceStyle, ...style } : style,
972
+ onPointerDownCapture: useLocalDragHandlers ? (event) => {
973
+ drag?.handlePointerDownCapture(event);
974
+ onPointerDownCapture?.(event);
975
+ } : onPointerDownCapture,
976
+ onPointerMove: useLocalDragHandlers ? (event) => {
977
+ drag?.handlePointerMove(event);
978
+ onPointerMove?.(event);
979
+ } : onPointerMove,
980
+ onPointerUp: useLocalDragHandlers ? (event) => {
981
+ drag?.handlePointerUp(event);
982
+ onPointerUp?.(event);
983
+ } : onPointerUp,
984
+ onPointerCancel: useLocalDragHandlers ? (event) => {
985
+ drag?.handlePointerCancel(event);
986
+ onPointerCancel?.(event);
987
+ } : onPointerCancel,
988
+ onDragStart: useLocalDragHandlers ? (event) => {
989
+ event.preventDefault();
990
+ onDragStart?.(event);
991
+ } : onDragStart,
992
+ children: [
993
+ useLocalDragHandlers && drag?.dragRectangle ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CalendarDragRectangleOverlay, { dragRectangle: drag.dragRectangle, surfaceRef: drag.surfaceRef }) : null,
994
+ weeks.map((week) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { "calchemy-week": "", children: week.map((date) => {
995
+ const dayState = getCalendarDayState(calendar, period, date);
996
+ const namedDateLabels = dayState.namedDates.map((item) => item.value).join(", ");
997
+ const dateKey = date.toString();
998
+ const selected = multipleSelection ? dragState ? previewSelectedKeys.has(dateKey) : committedSelectedKeys.has(dateKey) : dayState.selected;
999
+ const dragPreview = Boolean(dragState && selected !== dayState.selected);
1000
+ if (dayState.outside && !showBookends) {
1001
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1002
+ "div",
1003
+ {
1004
+ "aria-hidden": "true",
1005
+ "calchemy-cell": "",
1006
+ "calchemy-blank": ""
1007
+ },
1008
+ date.toString()
1009
+ );
1010
+ }
1011
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1012
+ "button",
1013
+ {
1014
+ type: "button",
1015
+ ref: (element) => drag?.registerDay(element, date, dayState.disabled || !calendar.editable),
1016
+ "calchemy-date": "",
1017
+ "calchemy-selected": selected ? "" : void 0,
1018
+ "calchemy-drag-preview": dragPreview ? "" : void 0,
1019
+ "calchemy-drag-preview-selected": dragPreview && selected ? "" : void 0,
1020
+ "calchemy-drag-preview-deselected": dragPreview && !selected ? "" : void 0,
1021
+ "calchemy-today": dayState.today ? "" : void 0,
1022
+ "calchemy-weekend": dayState.weekend ? "" : void 0,
1023
+ "calchemy-outside": dayState.outside ? "" : void 0,
1024
+ "calchemy-first-of-period": dayState.firstOfPeriod ? "" : void 0,
1025
+ "calchemy-last-of-period": dayState.lastOfPeriod ? "" : void 0,
1026
+ "calchemy-disabled": dayState.disabled ? "" : void 0,
1027
+ "calchemy-out-of-bounds": !dayState.bounded ? "" : void 0,
1028
+ "calchemy-named-date": dayState.namedDates.length > 0 ? "" : void 0,
1029
+ "calchemy-holiday": dayState.namedDates.some((item) => item.isHoliday) ? "" : void 0,
1030
+ "calchemy-named-date-labels": namedDateLabels || void 0,
1031
+ "aria-disabled": !calendar.editable && !dayState.disabled ? true : void 0,
1032
+ disabled: dayState.disabled,
1033
+ onClick: () => {
1034
+ if (!calendar.editable) {
1035
+ return;
1036
+ }
1037
+ if (drag?.suppressClickRef.current) {
1038
+ drag.suppressClickRef.current = false;
1039
+ return;
1040
+ }
1041
+ if (!dayState.disabled) {
1042
+ if (multipleSelection) {
1043
+ const selectedDates = getMultipleDates(calendar.selected);
1044
+ const nextKeys = toggleDateKeys(
1045
+ selectedDates.map((selectedDate) => selectedDate.toString()),
1046
+ [dateKey]
1047
+ );
1048
+ const nextDates = nextKeys.flatMap((key) => {
1049
+ if (key === dateKey) {
1050
+ return [date];
1051
+ }
1052
+ const existing = selectedDates.find((selectedDate) => selectedDate.toString() === key);
1053
+ return existing ? [existing] : [];
1054
+ });
1055
+ calendar.selectValue({
1056
+ kind: "multiple",
1057
+ dates: nextDates
1058
+ });
1059
+ } else {
1060
+ calendar.selectDate(date);
1061
+ }
1062
+ }
1063
+ },
1064
+ children: date.day
1065
+ },
1066
+ date.toString()
1067
+ );
1068
+ }) }, week[0]?.toString()))
1069
+ ]
1070
+ }
1071
+ );
1072
+ }
1073
+
1074
+ // src/components/calendar/Calendar.tsx
1075
+ var import_jsx_runtime3 = require("react/jsx-runtime");
1076
+ var defaultCalendarPeriod = { months: 1 };
1077
+ function Calendar({
1078
+ period = defaultCalendarPeriod,
1079
+ bounds,
1080
+ isDateDisabled,
1081
+ namedDates,
1082
+ children,
1083
+ ...divProps
1084
+ }) {
1085
+ const state = useCalchemyContext();
1086
+ const editable = state.inputMode === "calendar";
1087
+ validateCalendarBounds(bounds);
1088
+ const parsedPeriod = (0, import_react6.useMemo)(() => parseCalendarDuration(period, "period"), [period]);
1089
+ const selected = getSelectedValue(state);
1090
+ const today = getToday(state);
1091
+ const [periodAnchor, setPeriodAnchor] = (0, import_react6.useState)(
1092
+ () => clampDateToBounds(getDateValueAnchor(selected) ?? today, bounds)
1093
+ );
1094
+ const [navigationAnchor, setNavigationAnchor] = (0, import_react6.useState)(null);
1095
+ const [periodExtensions, setPeriodExtensions] = (0, import_react6.useState)(() => getInitialPeriodExtensions(parsedPeriod));
1096
+ const [visiblePeriodIndex, setScrolledVisiblePeriodIndex] = (0, import_react6.useState)(null);
1097
+ const prevInputValueRef = (0, import_react6.useRef)(state.inputValue);
1098
+ const prevSelectedKeyRef = (0, import_react6.useRef)(getDateValueKey(selected));
1099
+ const prevExpectedValueRef = (0, import_react6.useRef)(state.expectedValue);
1100
+ (0, import_react6.useEffect)(() => {
1101
+ if (prevExpectedValueRef.current === state.expectedValue) {
1102
+ return;
1103
+ }
1104
+ prevExpectedValueRef.current = state.expectedValue;
1105
+ setScrolledVisiblePeriodIndex(null);
1106
+ setNavigationAnchor(null);
1107
+ setPeriodAnchor(clampDateToBounds(getDateValueAnchor(selected) ?? today, bounds));
1108
+ prevInputValueRef.current = state.inputValue;
1109
+ prevSelectedKeyRef.current = getDateValueKey(selected);
1110
+ }, [bounds, selected, state.expectedValue, state.inputValue, today]);
1111
+ const periodAnchorKey = periodAnchor.toString();
1112
+ const activeVisiblePeriodIndex = visiblePeriodIndex?.anchor === periodAnchorKey && visiblePeriodIndex.inputValue === state.inputValue ? visiblePeriodIndex.index : 0;
1113
+ const weekStartsOn = state.parseContext?.weekStartsOn ?? 0;
1114
+ const locale = state.parseContext?.locale ?? "en-US";
1115
+ const periods = (0, import_react6.useMemo)(
1116
+ () => buildCalendarPeriods(periodAnchor, parsedPeriod, weekStartsOn, locale, periodExtensions, bounds),
1117
+ [periodAnchor, parsedPeriod.count, parsedPeriod.unit, weekStartsOn, locale, periodExtensions, bounds]
1118
+ );
1119
+ const visiblePeriods = (0, import_react6.useMemo)(
1120
+ () => {
1121
+ const visible = periods.filter(
1122
+ (item) => item.index >= activeVisiblePeriodIndex && item.index < activeVisiblePeriodIndex + parsedPeriod.count
1123
+ );
1124
+ return visible.length > 0 ? visible : periods.slice(0, parsedPeriod.count);
1125
+ },
1126
+ [periods, activeVisiblePeriodIndex, parsedPeriod.count]
1127
+ );
1128
+ const visiblePeriodAnchor = visiblePeriods[0]?.start ?? periodAnchor;
1129
+ (0, import_react6.useLayoutEffect)(() => {
1130
+ const inputChanged = prevInputValueRef.current !== state.inputValue;
1131
+ prevInputValueRef.current = state.inputValue;
1132
+ const selectedKey = getDateValueKey(selected);
1133
+ const selectionChanged = prevSelectedKeyRef.current !== selectedKey;
1134
+ prevSelectedKeyRef.current = selectedKey;
1135
+ if (!inputChanged && !selectionChanged) {
1136
+ return;
1137
+ }
1138
+ if (selectionChanged && editable) {
1139
+ return;
1140
+ }
1141
+ const selectionAnchor = clampDateToBounds(getDateValueAnchor(selected) ?? today, bounds);
1142
+ const inputReflectsCalendarSelection = state.inputValue === selectionAnchor.toString();
1143
+ const shouldRevealSelection = inputChanged && !inputReflectsCalendarSelection;
1144
+ if (!shouldRevealSelection && isDateInCalendarViewport(
1145
+ selectionAnchor,
1146
+ periods,
1147
+ activeVisiblePeriodIndex,
1148
+ parsedPeriod.count
1149
+ )) {
1150
+ return;
1151
+ }
1152
+ setPeriodAnchor((current) => current.equals(selectionAnchor) ? current : selectionAnchor);
1153
+ setScrolledVisiblePeriodIndex(null);
1154
+ }, [
1155
+ activeVisiblePeriodIndex,
1156
+ bounds,
1157
+ editable,
1158
+ parsedPeriod.count,
1159
+ periods,
1160
+ selected,
1161
+ state.inputValue,
1162
+ today
1163
+ ]);
1164
+ function setCalendarPeriodAnchor(date) {
1165
+ const clamped = clampDateToBounds(date, bounds);
1166
+ setPeriodAnchor(clamped);
1167
+ setNavigationAnchor({ date: clamped, inputValue: state.inputValue });
1168
+ setPeriodExtensions(getInitialPeriodExtensions(parsedPeriod));
1169
+ setScrolledVisiblePeriodIndex(null);
1170
+ }
1171
+ function canMoveCalendar(unit, count) {
1172
+ const target = addCalendarPeriod(visiblePeriodAnchor, unit, count);
1173
+ return target.equals(clampDateToBounds(target, bounds));
1174
+ }
1175
+ function canExtendCalendarPeriods(direction, windows = 1) {
1176
+ if (!bounds) {
1177
+ return true;
1178
+ }
1179
+ const extendCount = parsedPeriod.count * windows;
1180
+ const firstIndex = periods[0]?.index ?? 0;
1181
+ const lastIndex = periods.at(-1)?.index ?? parsedPeriod.count - 1;
1182
+ const targetIndex = direction === "before" ? firstIndex - extendCount : lastIndex + 1;
1183
+ const targetPeriod = getCalendarPeriodAtOffset(periodAnchor, parsedPeriod, weekStartsOn, locale, targetIndex);
1184
+ return periodIntersectsBounds(targetPeriod, bounds);
1185
+ }
1186
+ const calendarState = (0, import_react6.useMemo)(
1187
+ () => ({
1188
+ calchemy: state,
1189
+ period: parsedPeriod,
1190
+ periodAnchor,
1191
+ visiblePeriodAnchor,
1192
+ today,
1193
+ selected,
1194
+ periods,
1195
+ visiblePeriods,
1196
+ weekStartsOn,
1197
+ locale,
1198
+ bounds,
1199
+ namedDates,
1200
+ editable,
1201
+ isDateDisabled,
1202
+ setPeriodAnchor: setCalendarPeriodAnchor,
1203
+ setVisiblePeriodIndex(index) {
1204
+ setScrolledVisiblePeriodIndex((current) => {
1205
+ if (current?.anchor === periodAnchorKey && current.inputValue === state.inputValue && current.index === index) {
1206
+ return current;
1207
+ }
1208
+ return { anchor: periodAnchorKey, inputValue: state.inputValue, index };
1209
+ });
1210
+ },
1211
+ canMove: canMoveCalendar,
1212
+ move(unit, count) {
1213
+ if (!canMoveCalendar(unit, count)) {
1214
+ return;
1215
+ }
1216
+ const clamped = clampDateToBounds(addCalendarPeriod(visiblePeriodAnchor, unit, count), bounds);
1217
+ setPeriodAnchor(clamped);
1218
+ setNavigationAnchor({
1219
+ date: clamped,
1220
+ inputValue: state.inputValue
1221
+ });
1222
+ setPeriodExtensions(getInitialPeriodExtensions(parsedPeriod));
1223
+ setScrolledVisiblePeriodIndex(null);
1224
+ },
1225
+ canExtendPeriods: canExtendCalendarPeriods,
1226
+ extendPeriods(direction, windows = 1) {
1227
+ if (!canExtendCalendarPeriods(direction, windows)) {
1228
+ return;
1229
+ }
1230
+ setPeriodExtensions((current) => ({
1231
+ ...current,
1232
+ [direction]: current[direction] + parsedPeriod.count * windows
1233
+ }));
1234
+ },
1235
+ selectDate(date) {
1236
+ if (!editable) {
1237
+ return;
1238
+ }
1239
+ if (state.expectedValue === "range") {
1240
+ const current = selected;
1241
+ if (current?.kind === "range" && current.start.equals(current.end)) {
1242
+ const start = isBefore(current.start, date) ? current.start : date;
1243
+ const end = isBefore(current.start, date) ? date : current.start;
1244
+ state.selectDate({ kind: "range", start, end });
1245
+ return;
1246
+ }
1247
+ state.selectDate({ kind: "range", start: date, end: date });
1248
+ return;
1249
+ }
1250
+ state.selectDate({ kind: "single", date });
1251
+ },
1252
+ selectValue(value) {
1253
+ if (!editable) {
1254
+ return;
1255
+ }
1256
+ state.selectDate(value);
1257
+ }
1258
+ }),
1259
+ [
1260
+ state,
1261
+ parsedPeriod,
1262
+ periodAnchor,
1263
+ periodAnchorKey,
1264
+ visiblePeriodAnchor,
1265
+ today,
1266
+ selected,
1267
+ periods,
1268
+ visiblePeriods,
1269
+ weekStartsOn,
1270
+ locale,
1271
+ bounds,
1272
+ namedDates,
1273
+ editable,
1274
+ isDateDisabled
1275
+ ]
1276
+ );
1277
+ const content = children ?? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1278
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(CalendarHeader, { children: [
1279
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CalendarPrevious, {}),
1280
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CalendarHeading, {}),
1281
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CalendarNext, {})
1282
+ ] }),
1283
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CalendarWeekdays, {}),
1284
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CalendarGrid, {})
1285
+ ] });
1286
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CalendarContext.Provider, { value: calendarState, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1287
+ "div",
1288
+ {
1289
+ ...divProps,
1290
+ "calchemy-calendar": "",
1291
+ "calchemy-editable": editable ? "" : void 0,
1292
+ style: {
1293
+ ...divProps.style,
1294
+ "--calchemy-calendar-period-count": parsedPeriod.count
1295
+ },
1296
+ children: content
1297
+ }
1298
+ ) });
1299
+ }
1300
+ function CalendarHeader(props) {
1301
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { ...props, "calchemy-header": "" });
1302
+ }
1303
+ function CalendarHeading(props) {
1304
+ const calendar = useCalchemyCalendar();
1305
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { ...props, "calchemy-heading": "", children: props.children ?? formatCalendarWindowLabel(calendar.visiblePeriods, calendar.locale) });
1306
+ }
1307
+ function CalendarPrevious({
1308
+ onClick,
1309
+ children,
1310
+ ...props
1311
+ }) {
1312
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1313
+ CalendarNavigationButton,
1314
+ {
1315
+ ...props,
1316
+ direction: -1,
1317
+ "calchemy-previous": "",
1318
+ onClick,
1319
+ children: children ?? "Previous"
1320
+ }
1321
+ );
1322
+ }
1323
+ function CalendarNext({
1324
+ onClick,
1325
+ children,
1326
+ ...props
1327
+ }) {
1328
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1329
+ CalendarNavigationButton,
1330
+ {
1331
+ ...props,
1332
+ direction: 1,
1333
+ "calchemy-next": "",
1334
+ onClick,
1335
+ children: children ?? "Next"
1336
+ }
1337
+ );
1338
+ }
1339
+ function CalendarNavigationButton({
1340
+ direction,
1341
+ onClick,
1342
+ type = "button",
1343
+ ...props
1344
+ }) {
1345
+ const calendar = useCalchemyCalendar();
1346
+ const disabled = props.disabled ?? !calendar.canMove(calendar.period.unit, calendar.period.count * direction);
1347
+ function handleClick(event) {
1348
+ onClick?.(event);
1349
+ if (event.defaultPrevented || disabled) {
1350
+ return;
1351
+ }
1352
+ calendar.move(calendar.period.unit, calendar.period.count * direction);
1353
+ }
1354
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { ...props, type, disabled, onClick: handleClick });
1355
+ }
1356
+
1357
+ // src/components/calendar/CalendarPeriodHeading.tsx
1358
+ var import_jsx_runtime4 = require("react/jsx-runtime");
1359
+ function CalendarPeriodHeading(props) {
1360
+ const calendar = useCalchemyCalendar();
1361
+ const period = useCalendarPeriod() ?? getFirstVisibleCalendarPeriod(calendar);
1362
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { ...props, "calchemy-period-heading": "", children: props.children ?? period.label });
1363
+ }
1364
+
1365
+ // src/components/calendar/CalendarPeriod.tsx
1366
+ var import_react7 = require("react");
1367
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1368
+ function CalendarPeriod({
1369
+ dragSelection = true,
1370
+ style,
1371
+ children,
1372
+ onPointerDownCapture,
1373
+ onPointerMove,
1374
+ onPointerUp,
1375
+ onPointerCancel,
1376
+ onDragStart,
1377
+ ...props
1378
+ }) {
1379
+ const period = (0, import_react7.useContext)(CalendarPeriodContext);
1380
+ const calendar = useCalchemyCalendar();
1381
+ const parentDrag = useOptionalCalendarPeriodDrag();
1382
+ const drag = useCalendarPeriodDragSurface(
1383
+ calendar.editable && dragSelection && parentDrag === null
1384
+ );
1385
+ const content = drag ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(CalendarPeriodDragProvider, { value: drag, children: [
1386
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CalendarDragRectangleOverlay, {}),
1387
+ children
1388
+ ] }) : children;
1389
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1390
+ "section",
1391
+ {
1392
+ ...props,
1393
+ "calchemy-period": "",
1394
+ "calchemy-period-id": period?.id,
1395
+ "calchemy-period-index": period?.index,
1396
+ "calchemy-multiple-drag": drag ? "" : void 0,
1397
+ "calchemy-dragging": drag?.dragState ? "" : void 0,
1398
+ style: drag ? { ...multipleDragSurfaceStyle, ...style } : parentDrag ? { touchAction: "none", ...style } : style,
1399
+ onPointerDownCapture: drag ? (event) => {
1400
+ drag.handlePointerDownCapture(event);
1401
+ onPointerDownCapture?.(event);
1402
+ } : onPointerDownCapture,
1403
+ onPointerMove: drag ? (event) => {
1404
+ drag.handlePointerMove(event);
1405
+ onPointerMove?.(event);
1406
+ } : onPointerMove,
1407
+ onPointerUp: drag ? (event) => {
1408
+ drag.handlePointerUp(event);
1409
+ onPointerUp?.(event);
1410
+ } : onPointerUp,
1411
+ onPointerCancel: drag ? (event) => {
1412
+ drag.handlePointerCancel(event);
1413
+ onPointerCancel?.(event);
1414
+ } : onPointerCancel,
1415
+ onDragStart: drag ? (event) => {
1416
+ event.preventDefault();
1417
+ onDragStart?.(event);
1418
+ } : onDragStart,
1419
+ children: content
1420
+ }
1421
+ );
1422
+ }
1423
+
1424
+ // src/components/calendar/CalendarPeriodList.tsx
1425
+ var import_react8 = require("react");
1426
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1427
+ function CalendarPeriodList({
1428
+ children,
1429
+ dragSelection = true,
1430
+ style,
1431
+ onPointerDownCapture,
1432
+ onPointerMove,
1433
+ onPointerUp,
1434
+ onPointerCancel,
1435
+ onDragStart,
1436
+ ...props
1437
+ }) {
1438
+ const calendar = useCalchemyCalendar();
1439
+ const scrollContext = (0, import_react8.useContext)(CalendarScrollContext);
1440
+ const parentDrag = useOptionalCalendarPeriodDrag();
1441
+ const surfaceRef = (0, import_react8.useRef)(null);
1442
+ const hitCaptureRef = (0, import_react8.useRef)(null);
1443
+ const drag = useCalendarPeriodDragSurface(
1444
+ calendar.editable && dragSelection && parentDrag === null,
1445
+ surfaceRef
1446
+ );
1447
+ const periods = scrollContext ? calendar.periods : calendar.visiblePeriods;
1448
+ (0, import_react8.useLayoutEffect)(() => {
1449
+ if (!drag) {
1450
+ return;
1451
+ }
1452
+ const surface = surfaceRef.current;
1453
+ const hitCapture = hitCaptureRef.current;
1454
+ if (!surface || !hitCapture) {
1455
+ return;
1456
+ }
1457
+ const updateDragHitBounds = () => {
1458
+ const periodElements = surface.querySelectorAll("[calchemy-period]");
1459
+ if (periodElements.length === 0) {
1460
+ hitCapture.style.removeProperty("inline-size");
1461
+ hitCapture.style.removeProperty("block-size");
1462
+ return;
1463
+ }
1464
+ let maxInlineEnd = 0;
1465
+ let maxBlockEnd = 0;
1466
+ for (const periodElement of periodElements) {
1467
+ maxInlineEnd = Math.max(maxInlineEnd, periodElement.offsetLeft + periodElement.offsetWidth);
1468
+ maxBlockEnd = Math.max(maxBlockEnd, periodElement.offsetTop + periodElement.offsetHeight);
1469
+ }
1470
+ hitCapture.style.inlineSize = `${maxInlineEnd}px`;
1471
+ hitCapture.style.blockSize = `${maxBlockEnd}px`;
1472
+ };
1473
+ updateDragHitBounds();
1474
+ if (typeof ResizeObserver === "undefined") {
1475
+ return;
1476
+ }
1477
+ const resizeObserver = new ResizeObserver(updateDragHitBounds);
1478
+ resizeObserver.observe(surface);
1479
+ for (const periodElement of surface.querySelectorAll("[calchemy-period]")) {
1480
+ resizeObserver.observe(periodElement);
1481
+ }
1482
+ return () => {
1483
+ resizeObserver.disconnect();
1484
+ };
1485
+ }, [drag, periods]);
1486
+ const spacerStyle = scrollContext?.direction === "horizontal" ? { gridColumn: `span ${scrollContext.leadingSpacerPeriodCount}` } : { blockSize: `${scrollContext?.leadingSpacerPixelSize ?? 0}px` };
1487
+ const periodContent = /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1488
+ scrollContext && scrollContext.leadingSpacerPeriodCount > 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1489
+ "div",
1490
+ {
1491
+ "aria-hidden": "true",
1492
+ "calchemy-scroll-spacer": "",
1493
+ style: spacerStyle
1494
+ }
1495
+ ) : null,
1496
+ periods.map((period) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CalendarPeriodContext.Provider, { value: period, children: children ?? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(CalendarPeriod, { children: [
1497
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CalendarPeriodHeading, {}),
1498
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CalendarWeekdays, {}),
1499
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CalendarGrid, {})
1500
+ ] }) }, period.id))
1501
+ ] });
1502
+ const content = drag ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(CalendarPeriodDragProvider, { value: drag, children: [
1503
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1504
+ "div",
1505
+ {
1506
+ ref: hitCaptureRef,
1507
+ "aria-hidden": "true",
1508
+ style: {
1509
+ position: "absolute",
1510
+ top: 0,
1511
+ left: 0,
1512
+ zIndex: 0,
1513
+ pointerEvents: "auto",
1514
+ touchAction: "none"
1515
+ }
1516
+ }
1517
+ ),
1518
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CalendarDragRectangleOverlay, {}),
1519
+ periodContent
1520
+ ] }) : periodContent;
1521
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1522
+ "div",
1523
+ {
1524
+ ...props,
1525
+ ref: surfaceRef,
1526
+ "calchemy-period-list": "",
1527
+ "calchemy-multiple-drag": drag ? "" : void 0,
1528
+ "calchemy-dragging": drag?.dragState ? "" : void 0,
1529
+ style: drag ? { ...multiplePeriodListDragSurfaceStyle, ...style } : style,
1530
+ onPointerDownCapture: drag ? (event) => {
1531
+ drag.handlePointerDownCapture(event);
1532
+ onPointerDownCapture?.(event);
1533
+ } : onPointerDownCapture,
1534
+ onPointerMove: drag ? (event) => {
1535
+ drag.handlePointerMove(event);
1536
+ onPointerMove?.(event);
1537
+ } : onPointerMove,
1538
+ onPointerUp: drag ? (event) => {
1539
+ drag.handlePointerUp(event);
1540
+ onPointerUp?.(event);
1541
+ } : onPointerUp,
1542
+ onPointerCancel: drag ? (event) => {
1543
+ drag.handlePointerCancel(event);
1544
+ onPointerCancel?.(event);
1545
+ } : onPointerCancel,
1546
+ onDragStart: drag ? (event) => {
1547
+ event.preventDefault();
1548
+ onDragStart?.(event);
1549
+ } : onDragStart,
1550
+ children: content
1551
+ }
1552
+ );
1553
+ }
1554
+
1555
+ // src/components/calendar/CalendarSelects.tsx
1556
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1557
+ function CalendarMonthSelect({ onChange, ...props }) {
1558
+ const calendar = useCalchemyCalendar();
1559
+ const months = Array.from({ length: 12 }, (_, index) => index + 1).filter(
1560
+ (month) => isMonthWithinBounds(calendar.visiblePeriodAnchor.with({ month, day: 1 }), calendar)
1561
+ );
1562
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1563
+ "select",
1564
+ {
1565
+ ...props,
1566
+ "calchemy-month-select": "",
1567
+ value: String(calendar.visiblePeriodAnchor.month),
1568
+ onChange: (event) => handleCalendarSelectChange(
1569
+ event,
1570
+ onChange,
1571
+ (value) => calendar.setPeriodAnchor(calendar.visiblePeriodAnchor.with({ month: value, day: 1 }))
1572
+ ),
1573
+ children: months.map((month) => {
1574
+ const date = calendar.visiblePeriodAnchor.with({ month, day: 1 });
1575
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: String(month), children: formatMonthLabel(date, calendar.locale) }, month);
1576
+ })
1577
+ }
1578
+ );
1579
+ }
1580
+ function CalendarYearSelect({ startYear, endYear, onChange, ...props }) {
1581
+ const calendar = useCalchemyCalendar();
1582
+ const visibleYear = calendar.visiblePeriodAnchor.year;
1583
+ const firstYear = Math.min(calendar.bounds?.start?.year ?? startYear ?? visibleYear - 100, visibleYear);
1584
+ const lastYear = Math.max(calendar.bounds?.end?.year ?? endYear ?? visibleYear + 100, visibleYear);
1585
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1586
+ "select",
1587
+ {
1588
+ ...props,
1589
+ "calchemy-year-select": "",
1590
+ value: String(visibleYear),
1591
+ onChange: (event) => handleCalendarSelectChange(
1592
+ event,
1593
+ onChange,
1594
+ (value) => calendar.setPeriodAnchor(calendar.visiblePeriodAnchor.with({ year: value, day: 1 }))
1595
+ ),
1596
+ children: Array.from({ length: lastYear - firstYear + 1 }, (_, index) => firstYear + index).map((year) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: String(year), children: year }, year))
1597
+ }
1598
+ );
1599
+ }
1600
+ function handleCalendarSelectChange(event, onChange, updatePeriodAnchor) {
1601
+ onChange?.(event);
1602
+ if (event.defaultPrevented) {
1603
+ return;
1604
+ }
1605
+ updatePeriodAnchor(Number(event.currentTarget.value));
1606
+ }
1607
+ function isMonthWithinBounds(monthStart, calendar) {
1608
+ const monthEnd = monthStart.add({ months: 1 }).subtract({ days: 1 });
1609
+ return (!calendar.bounds?.start || !isBefore(monthEnd, calendar.bounds.start)) && (!calendar.bounds?.end || !isAfter(monthStart, calendar.bounds.end));
1610
+ }
1611
+
1612
+ // src/components/Calchemy.tsx
1613
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1614
+ function Root(props) {
1615
+ const { children, ...options } = props;
1616
+ const state = useCalchemy(options);
1617
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CalchemyContext.Provider, { value: state, children });
1618
+ }
1619
+ function Field({
1620
+ renderInlineCompletion = true,
1621
+ readOnly,
1622
+ ...props
1623
+ }) {
1624
+ const state = useCalchemyContext();
1625
+ const inputProps = state.getInputProps();
1626
+ const completion = state.inputMode === "field" ? state.inlineCompletion : null;
1627
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1628
+ "span",
1629
+ {
1630
+ "calchemy-field": "",
1631
+ "calchemy-input-mode": state.inputMode,
1632
+ "calchemy-has-completion": completion ? "" : void 0,
1633
+ children: [
1634
+ renderInlineCompletion && completion ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { "calchemy-field-backdrop": "", "aria-hidden": "true", children: [
1635
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { "calchemy-field-typed": "", children: state.inputValue }),
1636
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { "calchemy-completions": "", children: completion.suffix })
1637
+ ] }) : null,
1638
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("input", { ...props, ...inputProps, readOnly: readOnly ?? inputProps.readOnly })
1639
+ ]
1640
+ }
1641
+ );
1642
+ }
1643
+ function InputMode({
1644
+ fieldLabel = "Type",
1645
+ calendarLabel = "Pick",
1646
+ ...props
1647
+ }) {
1648
+ const state = useCalchemyContext();
1649
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { ...props, "calchemy-mode": "", "calchemy-active": state.inputMode, role: "group", children: [
1650
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1651
+ "button",
1652
+ {
1653
+ type: "button",
1654
+ "calchemy-value": "field",
1655
+ "aria-pressed": state.inputMode === "field",
1656
+ onClick: () => state.setInputMode("field"),
1657
+ children: fieldLabel
1658
+ }
1659
+ ),
1660
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1661
+ "button",
1662
+ {
1663
+ type: "button",
1664
+ "calchemy-value": "calendar",
1665
+ "aria-pressed": state.inputMode === "calendar",
1666
+ onClick: () => state.setInputMode("calendar"),
1667
+ children: calendarLabel
1668
+ }
1669
+ )
1670
+ ] });
1671
+ }
1672
+ function Candidates(props) {
1673
+ const state = useCalchemyContext();
1674
+ if (state.inputMode !== "field" || state.result.status !== "ambiguous") {
1675
+ return null;
1676
+ }
1677
+ const candidates = state.expectedValue && state.expectedValue !== "multiple" ? state.result.candidates.filter(
1678
+ (candidate) => candidate.value.kind === state.expectedValue
1679
+ ) : state.result.candidates;
1680
+ if (candidates.length === 0) {
1681
+ return null;
1682
+ }
1683
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { ...props, "calchemy-candidates": "", children: candidates.map((candidate) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1684
+ "button",
1685
+ {
1686
+ type: "button",
1687
+ "calchemy-candidate": "",
1688
+ onClick: () => state.selectCandidate(candidate.id),
1689
+ children: candidate.label
1690
+ },
1691
+ candidate.id
1692
+ )) });
1693
+ }
1694
+ var Calchemy = {
1695
+ Root,
1696
+ Field,
1697
+ InputMode,
1698
+ Candidates,
1699
+ Calendar,
1700
+ CalendarHeader,
1701
+ CalendarHeading,
1702
+ CalendarPrevious,
1703
+ CalendarNext,
1704
+ CalendarPeriodList,
1705
+ CalendarPeriod,
1706
+ CalendarPeriodHeading,
1707
+ CalendarWeekdays,
1708
+ CalendarGrid,
1709
+ CalendarMonthSelect,
1710
+ CalendarYearSelect
1711
+ };
1712
+ // Annotate the CommonJS export names for ESM import in node:
1713
+ 0 && (module.exports = {
1714
+ Calchemy,
1715
+ useCalchemy,
1716
+ useCalchemyCalendar,
1717
+ useCalchemyContext
1718
+ });
1719
+ //# sourceMappingURL=index.cjs.map