@gridui/date-picker 0.0.0-stage → 1.0.0

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.js ADDED
@@ -0,0 +1,1004 @@
1
+ // src/shared/date.ts
2
+ var parseDate = (value) => {
3
+ const date = /* @__PURE__ */ new Date(`${value}T00:00:00Z`);
4
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value) || !Number.isFinite(date.getTime()) || date.toISOString().slice(0, 10) !== value) {
5
+ throw new RangeError(`Invalid date-picker date: ${value}`);
6
+ }
7
+ return date;
8
+ };
9
+ var addDays = (value, amount) => {
10
+ const date = parseDate(value);
11
+ date.setUTCDate(date.getUTCDate() + amount);
12
+ return date.toISOString().slice(0, 10);
13
+ };
14
+ var monthStart = (value) => {
15
+ parseDate(value);
16
+ return `${value.slice(0, 7)}-01`;
17
+ };
18
+ var addMonths = (value, amount) => {
19
+ const date = parseDate(value);
20
+ const day = date.getUTCDate();
21
+ date.setUTCDate(1);
22
+ date.setUTCMonth(date.getUTCMonth() + amount);
23
+ const last = new Date(date);
24
+ last.setUTCMonth(last.getUTCMonth() + 1, 0);
25
+ date.setUTCDate(Math.min(day, last.getUTCDate()));
26
+ return date.toISOString().slice(0, 10);
27
+ };
28
+ var formatDate = (value, locale, options) => {
29
+ return new Intl.DateTimeFormat(locale, {
30
+ ...options,
31
+ timeZone: "UTC"
32
+ }).format(parseDate(value));
33
+ };
34
+ var validateConstraints = (constraints) => {
35
+ if (constraints.min) {
36
+ parseDate(constraints.min);
37
+ }
38
+ if (constraints.max) {
39
+ parseDate(constraints.max);
40
+ }
41
+ if (constraints.min && constraints.max && constraints.min > constraints.max) {
42
+ throw new RangeError("Date-picker min must not be after max.");
43
+ }
44
+ };
45
+ var isDisabled = (value, constraints) => {
46
+ return Boolean(constraints.min && value < constraints.min || constraints.max && value > constraints.max || constraints.isDateDisabled?.(value));
47
+ };
48
+ var validateRange = (range) => {
49
+ if (!range) {
50
+ return;
51
+ }
52
+ parseDate(range.start);
53
+ if (range.end) {
54
+ parseDate(range.end);
55
+ if (range.end < range.start) {
56
+ throw new RangeError("Date range end must not be before start.");
57
+ }
58
+ }
59
+ };
60
+ var rangeAllowed = (range, constraints) => {
61
+ if (isDisabled(range.start, constraints) || range.end && isDisabled(range.end, constraints)) {
62
+ return false;
63
+ }
64
+ if (constraints.isDateDisabled && range.end) {
65
+ for (let day = addDays(range.start, 1); day < range.end; day = addDays(day, 1)) {
66
+ if (constraints.isDateDisabled(day)) {
67
+ return false;
68
+ }
69
+ }
70
+ }
71
+ return true;
72
+ };
73
+ var selectRange = (current, day, constraints) => {
74
+ if (!current || current.end) {
75
+ return { start: day };
76
+ }
77
+ const next = day < current.start ? {
78
+ start: day,
79
+ end: current.start
80
+ } : {
81
+ start: current.start,
82
+ end: day
83
+ };
84
+ return rangeAllowed(next, constraints) ? next : { start: day };
85
+ };
86
+ var monthDays = (month, weekStartsOn) => {
87
+ if (!Number.isInteger(weekStartsOn) || weekStartsOn < 0 || weekStartsOn > 6) {
88
+ throw new RangeError("weekStartsOn must be an integer from 0 to 6.");
89
+ }
90
+ const first = monthStart(month);
91
+ const start = addDays(first, -((parseDate(first).getUTCDay() - weekStartsOn + 7) % 7));
92
+ return Array.from({ length: 42 }, (_, index) => {
93
+ return addDays(start, index);
94
+ });
95
+ };
96
+ var keyboardDate = (date, key, weekStartsOn, rtl, shift) => {
97
+ switch (key) {
98
+ case "ArrowLeft":
99
+ return addDays(date, rtl ? 1 : -1);
100
+ case "ArrowRight":
101
+ return addDays(date, rtl ? -1 : 1);
102
+ case "ArrowUp":
103
+ return addDays(date, -7);
104
+ case "ArrowDown":
105
+ return addDays(date, 7);
106
+ case "Home":
107
+ return addDays(date, -((parseDate(date).getUTCDay() - weekStartsOn + 7) % 7));
108
+ case "End":
109
+ return addDays(date, 6 - (parseDate(date).getUTCDay() - weekStartsOn + 7) % 7);
110
+ case "PageUp":
111
+ return addMonths(date, shift ? -12 : -1);
112
+ case "PageDown":
113
+ return addMonths(date, shift ? 12 : 1);
114
+ default:
115
+ return void 0;
116
+ }
117
+ };
118
+
119
+ // src/date-picker-panel/month.tsx
120
+ import { jsx, jsxs } from "react/jsx-runtime";
121
+ var Month = (props) => {
122
+ return /* @__PURE__ */ jsxs(
123
+ "table",
124
+ {
125
+ role: "grid",
126
+ className: "gridui-date-month",
127
+ "aria-label": props.label,
128
+ "aria-multiselectable": props.rangeMode || void 0,
129
+ children: [
130
+ props.showCaption && /* @__PURE__ */ jsx(
131
+ "caption",
132
+ {
133
+ className: "type-ui",
134
+ children: props.label
135
+ }
136
+ ),
137
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: props.weekdays.map((day, index) => {
138
+ return /* @__PURE__ */ jsx(
139
+ "th",
140
+ {
141
+ scope: "col",
142
+ className: "type-caption text-muted",
143
+ children: day
144
+ },
145
+ index
146
+ );
147
+ }) }) }),
148
+ /* @__PURE__ */ jsx("tbody", { children: Array.from({ length: 6 }, (_, row) => {
149
+ return /* @__PURE__ */ jsx(
150
+ "tr",
151
+ {
152
+ children: props.days.slice(row * 7, row * 7 + 7).map((day) => {
153
+ const inMonth = day.startsWith(props.month.slice(0, 7));
154
+ const selected = props.selected === day || Boolean(props.range && day >= props.range.start && day <= (props.range.end ?? props.range.start));
155
+ const endpoint = props.range?.start === day || props.range?.end === day;
156
+ const available = props.isAvailable(day);
157
+ return /* @__PURE__ */ jsx(
158
+ "td",
159
+ {
160
+ "aria-selected": selected,
161
+ "data-in-range": selected && props.range !== null || void 0,
162
+ "data-range-start": props.range?.start === day || void 0,
163
+ "data-range-end": props.range?.end === day || void 0,
164
+ children: /* @__PURE__ */ jsx(
165
+ "button",
166
+ {
167
+ type: "button",
168
+ className: "gridui-date-day gridui-focus-contained gridui-hover-surface",
169
+ "data-date": day,
170
+ "data-in-month": inMonth,
171
+ "data-selected": props.selected === day || endpoint || void 0,
172
+ "aria-label": props.getLabel(day),
173
+ "aria-current": day === props.today ? "date" : void 0,
174
+ "aria-disabled": !available || void 0,
175
+ disabled: props.disabled,
176
+ tabIndex: !props.disabled && inMonth && day === props.focused ? 0 : -1,
177
+ onClick: () => {
178
+ if (available) {
179
+ props.onSelect(day);
180
+ }
181
+ },
182
+ onFocus: () => {
183
+ props.onFocus(day);
184
+ },
185
+ onPointerEnter: () => {
186
+ if (available) {
187
+ props.onHover(day);
188
+ }
189
+ },
190
+ onKeyDown: (event) => {
191
+ props.onKeyDown(event, day);
192
+ },
193
+ children: props.getDayNumber(day)
194
+ }
195
+ )
196
+ },
197
+ day
198
+ );
199
+ })
200
+ },
201
+ row
202
+ );
203
+ }) })
204
+ ]
205
+ }
206
+ );
207
+ };
208
+
209
+ // src/date-picker-panel/style.ts
210
+ import { cva } from "class-variance-authority";
211
+ var panelStyles = cva("gridui-date-panel min-w-0 bg-background text-foreground", {
212
+ variants: {
213
+ frame: {
214
+ none: "",
215
+ outline: "border border-solid border-border rounded-control"
216
+ },
217
+ size: {
218
+ standard: "gridui-date-panel-standard",
219
+ small: "gridui-date-panel-small"
220
+ }
221
+ },
222
+ defaultVariants: {
223
+ frame: "none",
224
+ size: "standard"
225
+ }
226
+ });
227
+
228
+ // src/date-picker-panel/use-panel.ts
229
+ import { useEffect, useRef, useState } from "react";
230
+ var usePanel = (props) => {
231
+ const { defaultMonth, month: controlledMonth, onMonthChange, months = 1, weekStartsOn = 1, disabled, min, max, isDateDisabled } = props;
232
+ validateConstraints(props);
233
+ parseDate(defaultMonth);
234
+ if (props.today) {
235
+ parseDate(props.today);
236
+ }
237
+ if (![
238
+ 1,
239
+ 2
240
+ ].includes(months)) {
241
+ throw new RangeError("DatePickerPanel supports one or two months.");
242
+ }
243
+ const [
244
+ localValue,
245
+ setLocalValue
246
+ ] = useState(props.defaultValue ?? null);
247
+ const value = props.value === void 0 ? localValue : props.value;
248
+ if (props.mode === "range") {
249
+ validateRange(value);
250
+ } else if (value !== null) {
251
+ parseDate(value);
252
+ }
253
+ const selected = typeof value === "string" ? value : value?.start;
254
+ const [
255
+ localMonth,
256
+ setLocalMonth
257
+ ] = useState(monthStart(defaultMonth));
258
+ const month = monthStart(controlledMonth ?? localMonth);
259
+ const [
260
+ focused,
261
+ setFocused
262
+ ] = useState(selected ?? props.today ?? month);
263
+ const [
264
+ hovered,
265
+ setHovered
266
+ ] = useState(null);
267
+ const pendingFocus = useRef(false);
268
+ const element = useRef(null);
269
+ const visibleDays = Array.from({ length: months }, (_, index) => {
270
+ return addMonths(month, index);
271
+ });
272
+ const cells = visibleDays.flatMap((visible) => {
273
+ return monthDays(visible, weekStartsOn).filter((day) => {
274
+ return day.startsWith(visible.slice(0, 7));
275
+ });
276
+ });
277
+ const eligible = (day) => {
278
+ return !disabled && !isDisabled(day, {
279
+ min,
280
+ max,
281
+ isDateDisabled
282
+ });
283
+ };
284
+ let focusDate = cells.find(eligible) ?? cells[0] ?? month;
285
+ if (props.today && cells.includes(props.today)) {
286
+ focusDate = props.today;
287
+ }
288
+ if (selected && cells.includes(selected)) {
289
+ focusDate = selected;
290
+ }
291
+ if (cells.includes(focused)) {
292
+ focusDate = focused;
293
+ }
294
+ useEffect(() => {
295
+ if (pendingFocus.current) {
296
+ const button = element.current?.querySelector(`button[data-date="${focusDate}"][data-in-month="true"]`);
297
+ if (button) {
298
+ button.focus();
299
+ pendingFocus.current = false;
300
+ }
301
+ }
302
+ }, [
303
+ focusDate,
304
+ month
305
+ ]);
306
+ const setMonth = (next) => {
307
+ const normalized = monthStart(next);
308
+ if (normalized === month) {
309
+ return;
310
+ }
311
+ if (controlledMonth === void 0) {
312
+ setLocalMonth(normalized);
313
+ }
314
+ onMonthChange?.(normalized);
315
+ };
316
+ const select = (day) => {
317
+ if (!eligible(day)) {
318
+ return;
319
+ }
320
+ if (props.mode === "range") {
321
+ const next = selectRange(value, day, props);
322
+ if (props.value === void 0) {
323
+ setLocalValue(next);
324
+ }
325
+ props.onValueChange(next);
326
+ } else {
327
+ if (props.value === void 0) {
328
+ setLocalValue(day);
329
+ }
330
+ props.onValueChange(day);
331
+ }
332
+ setHovered(null);
333
+ setFocused(day);
334
+ if (!cells.includes(day)) {
335
+ setMonth(day);
336
+ }
337
+ pendingFocus.current = true;
338
+ };
339
+ const onKeyDown = (event, day) => {
340
+ const target = keyboardDate(day, event.key, weekStartsOn, getComputedStyle(event.currentTarget).direction === "rtl", event.shiftKey);
341
+ if (!target) {
342
+ return;
343
+ }
344
+ event.preventDefault();
345
+ let next = target;
346
+ if (min && next < min) {
347
+ next = min;
348
+ }
349
+ if (max && next > max) {
350
+ next = max;
351
+ }
352
+ setFocused(next);
353
+ if (!cells.includes(next)) {
354
+ setMonth(next);
355
+ }
356
+ pendingFocus.current = true;
357
+ element.current?.querySelector(`button[data-date="${next}"][data-in-month="true"]`)?.focus();
358
+ };
359
+ const canPrevious = !disabled && (!min || addDays(month, -1) >= min);
360
+ const canNext = !disabled && (!max || addMonths(month, months) <= max);
361
+ return {
362
+ value,
363
+ month,
364
+ visibleDays,
365
+ focused: focusDate,
366
+ hovered,
367
+ setHovered,
368
+ setFocused,
369
+ element,
370
+ eligible,
371
+ select,
372
+ onKeyDown,
373
+ canPrevious,
374
+ canNext,
375
+ previous: () => {
376
+ setMonth(addMonths(month, -1));
377
+ },
378
+ next: () => {
379
+ setMonth(addMonths(month, 1));
380
+ }
381
+ };
382
+ };
383
+
384
+ // src/date-picker-panel/date-picker-panel.tsx
385
+ import { Button } from "@gridui/button";
386
+ import { cn } from "@gridui/style-utils";
387
+ import { ChevronLeft, ChevronRight } from "lucide-react";
388
+ import { useImperativeHandle } from "react";
389
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
390
+ var DatePickerPanel = (props) => {
391
+ const {
392
+ defaultMonth,
393
+ month,
394
+ onMonthChange,
395
+ months,
396
+ weekStartsOn = 1,
397
+ locale = "en-US",
398
+ today,
399
+ min,
400
+ max,
401
+ isDateDisabled,
402
+ labels,
403
+ mode,
404
+ value,
405
+ defaultValue,
406
+ onValueChange,
407
+ disabled,
408
+ size,
409
+ frame,
410
+ className,
411
+ ref,
412
+ ...restProps
413
+ } = props;
414
+ const state = usePanel({
415
+ defaultMonth,
416
+ month,
417
+ onMonthChange,
418
+ months,
419
+ weekStartsOn,
420
+ today,
421
+ min,
422
+ max,
423
+ isDateDisabled,
424
+ disabled,
425
+ mode,
426
+ value,
427
+ defaultValue,
428
+ onValueChange: (next) => {
429
+ if (mode === "range") {
430
+ onValueChange?.(next);
431
+ } else {
432
+ onValueChange?.(next);
433
+ }
434
+ }
435
+ });
436
+ useImperativeHandle(ref, () => {
437
+ return state.element.current;
438
+ });
439
+ const range = mode === "range" ? state.value : null;
440
+ let preview = range;
441
+ if (range && !range.end && state.hovered) {
442
+ const candidate = state.hovered < range.start ? {
443
+ start: state.hovered,
444
+ end: range.start
445
+ } : {
446
+ start: range.start,
447
+ end: state.hovered
448
+ };
449
+ if (rangeAllowed(candidate, props)) {
450
+ preview = candidate;
451
+ }
452
+ }
453
+ return /* @__PURE__ */ jsxs2(
454
+ "div",
455
+ {
456
+ ...restProps,
457
+ ref: state.element,
458
+ className: cn(panelStyles({
459
+ frame,
460
+ size
461
+ }), className),
462
+ "data-disabled": disabled || void 0,
463
+ children: [
464
+ /* @__PURE__ */ jsxs2(
465
+ "div",
466
+ {
467
+ className: "gridui-date-panel-navigation",
468
+ children: [
469
+ /* @__PURE__ */ jsx2(
470
+ Button,
471
+ {
472
+ variant: "quiet",
473
+ layout: "icon",
474
+ size: "small",
475
+ disabled: !state.canPrevious,
476
+ "aria-label": labels?.previousMonth ?? "Previous month",
477
+ onClick: state.previous,
478
+ children: /* @__PURE__ */ jsx2(
479
+ ChevronLeft,
480
+ {
481
+ "aria-hidden": "true"
482
+ }
483
+ )
484
+ }
485
+ ),
486
+ /* @__PURE__ */ jsxs2(
487
+ "div",
488
+ {
489
+ className: "type-ui",
490
+ "aria-live": "polite",
491
+ children: [
492
+ formatDate(state.month, locale, {
493
+ year: "numeric",
494
+ month: "long"
495
+ }),
496
+ state.visibleDays.length === 2 && ` \u2013 ${formatDate(state.visibleDays[1], locale, {
497
+ year: "numeric",
498
+ month: "long"
499
+ })}`
500
+ ]
501
+ }
502
+ ),
503
+ /* @__PURE__ */ jsx2(
504
+ Button,
505
+ {
506
+ variant: "quiet",
507
+ layout: "icon",
508
+ size: "small",
509
+ disabled: !state.canNext,
510
+ "aria-label": labels?.nextMonth ?? "Next month",
511
+ onClick: state.next,
512
+ children: /* @__PURE__ */ jsx2(
513
+ ChevronRight,
514
+ {
515
+ "aria-hidden": "true"
516
+ }
517
+ )
518
+ }
519
+ )
520
+ ]
521
+ }
522
+ ),
523
+ /* @__PURE__ */ jsx2(
524
+ "div",
525
+ {
526
+ className: "gridui-date-panel-months",
527
+ "data-months": state.visibleDays.length,
528
+ onPointerLeave: () => {
529
+ state.setHovered(null);
530
+ },
531
+ children: state.visibleDays.map((visible) => {
532
+ return /* @__PURE__ */ jsx2(
533
+ Month,
534
+ {
535
+ month: visible,
536
+ label: formatDate(visible, locale, {
537
+ year: "numeric",
538
+ month: "long"
539
+ }),
540
+ days: monthDays(visible, weekStartsOn),
541
+ weekdays: Array.from({ length: 7 }, (_, day) => {
542
+ return formatDate(addDays("2024-01-07", weekStartsOn + day), locale, { weekday: "short" });
543
+ }),
544
+ getLabel: (day) => {
545
+ return formatDate(day, locale, { dateStyle: "full" });
546
+ },
547
+ getDayNumber: (day) => {
548
+ return parseDate(day).getUTCDate();
549
+ },
550
+ focused: state.focused,
551
+ today,
552
+ selected: typeof state.value === "string" ? state.value : null,
553
+ range: preview,
554
+ rangeMode: mode === "range",
555
+ disabled: disabled === true,
556
+ isAvailable: state.eligible,
557
+ onSelect: state.select,
558
+ onFocus: state.setFocused,
559
+ onHover: state.setHovered,
560
+ onKeyDown: state.onKeyDown,
561
+ showCaption: state.visibleDays.length > 1
562
+ },
563
+ visible
564
+ );
565
+ })
566
+ }
567
+ )
568
+ ]
569
+ }
570
+ );
571
+ };
572
+
573
+ // src/shared/picker.tsx
574
+ import { Button as Button2 } from "@gridui/button";
575
+ import { useFormReset } from "@gridui/form";
576
+ import { Popover, PopoverPanel, PopoverTrigger } from "@gridui/popover";
577
+ import { cn as cn2 } from "@gridui/style-utils";
578
+ import { CalendarDays, X } from "lucide-react";
579
+ import { useEffect as useEffect2, useId, useRef as useRef2, useState as useState2 } from "react";
580
+ import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
581
+ var Picker = (props) => {
582
+ const {
583
+ mode,
584
+ label,
585
+ name,
586
+ form,
587
+ readOnly = false,
588
+ open: controlledOpen,
589
+ defaultOpen = false,
590
+ onOpenChange,
591
+ value: controlledValue,
592
+ defaultValue = null,
593
+ onValueChange,
594
+ formatValue,
595
+ pickerLabels,
596
+ footer,
597
+ clearable = true,
598
+ defaultMonth,
599
+ month,
600
+ onMonthChange,
601
+ months = mode === "range" ? 2 : 1,
602
+ weekStartsOn,
603
+ locale = "en-US",
604
+ today,
605
+ min,
606
+ max,
607
+ isDateDisabled,
608
+ labels,
609
+ disabled = false,
610
+ size = "standard",
611
+ className,
612
+ dir,
613
+ ...restProps
614
+ } = props;
615
+ parseDate(defaultMonth);
616
+ validateConstraints(props);
617
+ if (today) {
618
+ parseDate(today);
619
+ }
620
+ const [
621
+ localValue,
622
+ setLocalValue
623
+ ] = useState2(defaultValue);
624
+ const value = controlledValue === void 0 ? localValue : controlledValue;
625
+ if (mode === "single" && value !== null) {
626
+ parseDate(value);
627
+ }
628
+ if (mode === "range") {
629
+ validateRange(value);
630
+ }
631
+ const [
632
+ localOpen,
633
+ setLocalOpen
634
+ ] = useState2(defaultOpen);
635
+ const open = !disabled && !readOnly && (controlledOpen ?? localOpen);
636
+ const [
637
+ draft,
638
+ setDraft
639
+ ] = useState2(mode === "range" ? value : null);
640
+ const id = useId();
641
+ const input = useRef2(null);
642
+ const control = useRef2(null);
643
+ const panel = useRef2(null);
644
+ const initial = useRef2(defaultValue);
645
+ const returnFocus = useRef2(false);
646
+ const rangeStart = typeof value === "object" ? value?.start : void 0;
647
+ const rangeEnd = typeof value === "object" ? value?.end : void 0;
648
+ useEffect2(() => {
649
+ if (open && mode === "range") {
650
+ setDraft(rangeStart ? {
651
+ start: rangeStart,
652
+ ...rangeEnd ? { end: rangeEnd } : {}
653
+ } : null);
654
+ }
655
+ }, [
656
+ open,
657
+ mode,
658
+ rangeStart,
659
+ rangeEnd
660
+ ]);
661
+ const text = {
662
+ placeholder: mode === "range" ? "Choose dates" : "Choose date",
663
+ clear: "Clear date",
664
+ today: "Today",
665
+ apply: "Apply",
666
+ cancel: "Cancel",
667
+ ...pickerLabels
668
+ };
669
+ useFormReset(input, () => {
670
+ if (controlledValue === void 0) {
671
+ setLocalValue(initial.current);
672
+ }
673
+ setDraft(mode === "range" ? initial.current : null);
674
+ if (controlledOpen === void 0) {
675
+ setLocalOpen(false);
676
+ }
677
+ }, form);
678
+ const changeOpen = (next) => {
679
+ if (next && (disabled || readOnly || control.current?.matches(":disabled"))) {
680
+ return;
681
+ }
682
+ if (controlledOpen === void 0) {
683
+ setLocalOpen(next);
684
+ }
685
+ onOpenChange?.(next);
686
+ };
687
+ const closeFromAction = () => {
688
+ returnFocus.current = true;
689
+ changeOpen(false);
690
+ };
691
+ const change = (next) => {
692
+ if (disabled || readOnly || control.current?.matches(":disabled")) {
693
+ return;
694
+ }
695
+ if (controlledValue === void 0) {
696
+ setLocalValue(next);
697
+ }
698
+ if (mode === "single") {
699
+ onValueChange?.(next);
700
+ } else {
701
+ onValueChange?.(next);
702
+ }
703
+ };
704
+ let display = text.placeholder;
705
+ if (value !== null) {
706
+ if (mode === "single") {
707
+ display = formatValue?.(value) ?? formatDate(value, locale, { dateStyle: "medium" });
708
+ } else {
709
+ const range2 = value;
710
+ display = formatValue?.(range2) ?? `${formatDate(range2.start, locale, { dateStyle: "medium" })}${range2.end ? ` \u2013 ${formatDate(range2.end, locale, { dateStyle: "medium" })}` : ""}`;
711
+ }
712
+ }
713
+ const options = {
714
+ defaultMonth: typeof value === "string" ? value : value?.start ?? defaultMonth,
715
+ month,
716
+ onMonthChange,
717
+ months,
718
+ weekStartsOn,
719
+ locale,
720
+ today,
721
+ min,
722
+ max,
723
+ isDateDisabled,
724
+ labels,
725
+ size
726
+ };
727
+ const range = mode === "range" ? value : null;
728
+ return /* @__PURE__ */ jsxs3(
729
+ "div",
730
+ {
731
+ ...restProps,
732
+ dir,
733
+ className: cn2("gridui-date-picker min-w-0", className),
734
+ children: [
735
+ /* @__PURE__ */ jsx3(
736
+ "span",
737
+ {
738
+ id: `${id}-label`,
739
+ className: "mb-gridui-4 block type-ui",
740
+ children: label
741
+ }
742
+ ),
743
+ /* @__PURE__ */ jsxs3(
744
+ Popover,
745
+ {
746
+ open,
747
+ onOpenChange: changeOpen,
748
+ hoverEnabled: false,
749
+ children: [
750
+ /* @__PURE__ */ jsxs3(
751
+ "div",
752
+ {
753
+ className: "gridui-date-picker-field",
754
+ children: [
755
+ /* @__PURE__ */ jsxs3(
756
+ PopoverTrigger,
757
+ {
758
+ ref: control,
759
+ variant: "secondary",
760
+ size,
761
+ disabled: disabled || readOnly,
762
+ className: "gridui-date-picker-trigger w-full justify-between text-left",
763
+ "aria-labelledby": `${id}-label ${id}-value`,
764
+ children: [
765
+ /* @__PURE__ */ jsx3(
766
+ "span",
767
+ {
768
+ id: `${id}-value`,
769
+ className: cn2("min-w-0 flex-1 wrap-anywhere", value === null && "text-muted"),
770
+ children: display
771
+ }
772
+ ),
773
+ /* @__PURE__ */ jsx3(
774
+ CalendarDays,
775
+ {
776
+ "aria-hidden": "true"
777
+ }
778
+ )
779
+ ]
780
+ }
781
+ ),
782
+ clearable && value !== null && /* @__PURE__ */ jsx3(
783
+ Button2,
784
+ {
785
+ variant: "quiet",
786
+ layout: "icon",
787
+ size,
788
+ disabled: disabled || readOnly,
789
+ "aria-label": text.clear,
790
+ onClick: () => {
791
+ change(null);
792
+ setDraft(null);
793
+ },
794
+ children: /* @__PURE__ */ jsx3(
795
+ X,
796
+ {
797
+ "aria-hidden": "true"
798
+ }
799
+ )
800
+ }
801
+ )
802
+ ]
803
+ }
804
+ ),
805
+ /* @__PURE__ */ jsxs3(
806
+ PopoverPanel,
807
+ {
808
+ ref: panel,
809
+ dir,
810
+ "aria-labelledby": `${id}-label`,
811
+ className: cn2("gridui-date-picker-popup flex flex-col overflow-y-hidden p-0", months === 2 ? "w-160" : "w-80"),
812
+ onEscapeKeyDown: () => {
813
+ returnFocus.current = true;
814
+ },
815
+ onCloseAutoFocus: (event) => {
816
+ if (!returnFocus.current) {
817
+ return;
818
+ }
819
+ event.preventDefault();
820
+ const active = control.current?.ownerDocument.activeElement;
821
+ if (active === control.current?.ownerDocument.body || active === control.current || active && panel.current?.contains(active)) {
822
+ control.current?.focus();
823
+ }
824
+ returnFocus.current = false;
825
+ },
826
+ onOpenAutoFocus: (event) => {
827
+ returnFocus.current = false;
828
+ const day = panel.current?.querySelector('button[data-date][tabindex="0"]');
829
+ if (day) {
830
+ event.preventDefault();
831
+ day.focus();
832
+ }
833
+ },
834
+ children: [
835
+ /* @__PURE__ */ jsx3(
836
+ "div",
837
+ {
838
+ className: "min-h-0 overflow-y-auto overscroll-contain",
839
+ children: mode === "range" ? /* @__PURE__ */ jsx3(
840
+ DatePickerPanel,
841
+ {
842
+ ...options,
843
+ mode: "range",
844
+ value: draft,
845
+ onValueChange: setDraft
846
+ }
847
+ ) : /* @__PURE__ */ jsx3(
848
+ DatePickerPanel,
849
+ {
850
+ ...options,
851
+ value,
852
+ onValueChange: (next) => {
853
+ change(next);
854
+ closeFromAction();
855
+ }
856
+ }
857
+ )
858
+ }
859
+ ),
860
+ /* @__PURE__ */ jsxs3(
861
+ "div",
862
+ {
863
+ className: "gridui-date-picker-footer",
864
+ children: [
865
+ footer,
866
+ mode === "range" ? /* @__PURE__ */ jsxs3(Fragment, { children: [
867
+ /* @__PURE__ */ jsx3(
868
+ "span",
869
+ {
870
+ className: "type-caption text-muted",
871
+ "aria-live": "polite",
872
+ children: draft ? `${formatDate(draft.start, locale, {
873
+ month: "short",
874
+ day: "numeric"
875
+ })}${draft.end ? ` \u2013 ${formatDate(draft.end, locale, {
876
+ month: "short",
877
+ day: "numeric"
878
+ })}` : ""}` : text.placeholder
879
+ }
880
+ ),
881
+ /* @__PURE__ */ jsxs3(
882
+ "div",
883
+ {
884
+ className: "flex flex-wrap gap-gridui-8",
885
+ children: [
886
+ /* @__PURE__ */ jsx3(
887
+ Button2,
888
+ {
889
+ variant: "secondary",
890
+ size: "small",
891
+ onClick: () => {
892
+ closeFromAction();
893
+ },
894
+ children: text.cancel
895
+ }
896
+ ),
897
+ /* @__PURE__ */ jsx3(
898
+ Button2,
899
+ {
900
+ size: "small",
901
+ disabled: !draft?.end || !rangeAllowed(draft, props),
902
+ onClick: () => {
903
+ if (draft?.end && rangeAllowed(draft, props)) {
904
+ change(draft);
905
+ closeFromAction();
906
+ }
907
+ },
908
+ children: text.apply
909
+ }
910
+ )
911
+ ]
912
+ }
913
+ )
914
+ ] }) : /* @__PURE__ */ jsxs3(Fragment, { children: [
915
+ today && /* @__PURE__ */ jsx3(
916
+ Button2,
917
+ {
918
+ variant: "quiet",
919
+ size: "small",
920
+ disabled: isDisabled(today, props),
921
+ onClick: () => {
922
+ change(today);
923
+ closeFromAction();
924
+ },
925
+ children: text.today
926
+ }
927
+ ),
928
+ clearable && /* @__PURE__ */ jsx3(
929
+ Button2,
930
+ {
931
+ variant: "quiet",
932
+ size: "small",
933
+ onClick: () => {
934
+ change(null);
935
+ closeFromAction();
936
+ },
937
+ children: text.clear
938
+ }
939
+ )
940
+ ] })
941
+ ]
942
+ }
943
+ )
944
+ ]
945
+ }
946
+ )
947
+ ]
948
+ }
949
+ ),
950
+ /* @__PURE__ */ jsx3(
951
+ "input",
952
+ {
953
+ ref: input,
954
+ type: "hidden",
955
+ name: mode === "range" && name ? `${name}.start` : name,
956
+ form,
957
+ disabled,
958
+ value: mode === "range" ? range?.start ?? "" : value ?? ""
959
+ }
960
+ ),
961
+ mode === "range" && /* @__PURE__ */ jsx3(
962
+ "input",
963
+ {
964
+ type: "hidden",
965
+ name: name ? `${name}.end` : void 0,
966
+ form,
967
+ disabled,
968
+ value: range?.end ?? ""
969
+ }
970
+ )
971
+ ]
972
+ }
973
+ );
974
+ };
975
+
976
+ // src/date-picker/date-picker.tsx
977
+ import { jsx as jsx4 } from "react/jsx-runtime";
978
+ var DatePicker = (props) => {
979
+ return /* @__PURE__ */ jsx4(
980
+ Picker,
981
+ {
982
+ ...props,
983
+ mode: "single"
984
+ }
985
+ );
986
+ };
987
+
988
+ // src/date-range-picker/date-range-picker.tsx
989
+ import { jsx as jsx5 } from "react/jsx-runtime";
990
+ var DateRangePicker = (props) => {
991
+ return /* @__PURE__ */ jsx5(
992
+ Picker,
993
+ {
994
+ ...props,
995
+ mode: "range"
996
+ }
997
+ );
998
+ };
999
+ export {
1000
+ DatePicker,
1001
+ DatePickerPanel,
1002
+ DateRangePicker
1003
+ };
1004
+ //# sourceMappingURL=index.js.map