@initbase/react-datetime-picker 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.mjs ADDED
@@ -0,0 +1,1194 @@
1
+ // src/components/datetime/DatePicker.tsx
2
+ import { useState as useState4 } from "react";
3
+
4
+ // src/components/datetime/Popover.tsx
5
+ import { useRef as useRef2, useState as useState2, useLayoutEffect } from "react";
6
+
7
+ // src/components/datetime/hooks.ts
8
+ import { useState, useCallback, useEffect } from "react";
9
+ function useControlled(value, defaultValue, onChange) {
10
+ const isControlled = value !== void 0;
11
+ const [internalValue, setInternalValue] = useState(defaultValue ?? value);
12
+ useEffect(() => {
13
+ if (isControlled) {
14
+ setInternalValue(value);
15
+ }
16
+ }, [isControlled, value]);
17
+ const setValue = useCallback(
18
+ (newValue) => {
19
+ if (!isControlled) {
20
+ setInternalValue(newValue);
21
+ }
22
+ onChange?.(newValue);
23
+ },
24
+ [isControlled, onChange]
25
+ );
26
+ return [isControlled ? value : internalValue, setValue];
27
+ }
28
+ function useClickOutside(ref, handler, enabled = true) {
29
+ useEffect(() => {
30
+ if (!enabled) return;
31
+ function handleClick(event) {
32
+ if (ref.current && !ref.current.contains(event.target)) {
33
+ handler();
34
+ }
35
+ }
36
+ function handleKeyDown(event) {
37
+ if (event.key === "Escape") {
38
+ handler();
39
+ }
40
+ }
41
+ document.addEventListener("mousedown", handleClick);
42
+ document.addEventListener("keydown", handleKeyDown);
43
+ return () => {
44
+ document.removeEventListener("mousedown", handleClick);
45
+ document.removeEventListener("keydown", handleKeyDown);
46
+ };
47
+ }, [ref, handler, enabled]);
48
+ }
49
+
50
+ // src/components/datetime/Popover.tsx
51
+ import { jsx } from "react/jsx-runtime";
52
+ function getPositionClass(pos) {
53
+ return `rdp-popover--${pos}`;
54
+ }
55
+ function Popover({
56
+ open,
57
+ onClose,
58
+ children,
59
+ className = "",
60
+ style,
61
+ position = "flexible"
62
+ }) {
63
+ const ref = useRef2(null);
64
+ const [resolvedPosition, setResolvedPosition] = useState2(position);
65
+ const [visible, setVisible] = useState2(false);
66
+ useClickOutside(ref, onClose, open);
67
+ useLayoutEffect(() => {
68
+ if (!open) {
69
+ setVisible(false);
70
+ setResolvedPosition(position);
71
+ return;
72
+ }
73
+ if (position !== "flexible") {
74
+ setResolvedPosition(position);
75
+ setVisible(true);
76
+ return;
77
+ }
78
+ setVisible(false);
79
+ setResolvedPosition("bottom");
80
+ requestAnimationFrame(() => {
81
+ if (!ref.current) return;
82
+ const popoverRect = ref.current.getBoundingClientRect();
83
+ const wrapper = ref.current.parentElement;
84
+ if (!wrapper) {
85
+ setVisible(true);
86
+ return;
87
+ }
88
+ const wrapperRect = wrapper.getBoundingClientRect();
89
+ const viewportHeight = window.innerHeight;
90
+ const spaceBelow = viewportHeight - wrapperRect.bottom;
91
+ const spaceAbove = wrapperRect.top;
92
+ if (spaceBelow >= popoverRect.height || spaceBelow >= spaceAbove) {
93
+ setResolvedPosition("bottom");
94
+ } else if (spaceAbove >= popoverRect.height) {
95
+ setResolvedPosition("top");
96
+ }
97
+ setVisible(true);
98
+ });
99
+ }, [open, position]);
100
+ if (!open) return null;
101
+ const positionClass = getPositionClass(resolvedPosition);
102
+ return /* @__PURE__ */ jsx(
103
+ "div",
104
+ {
105
+ ref,
106
+ className: `rdp-popover ${positionClass} ${className}`,
107
+ style: { ...style, visibility: visible ? "visible" : "hidden" },
108
+ role: "dialog",
109
+ children
110
+ }
111
+ );
112
+ }
113
+
114
+ // src/components/datetime/Calendar.tsx
115
+ import { useState as useState3 } from "react";
116
+
117
+ // src/components/datetime/IconButton.tsx
118
+ import { jsx as jsx2 } from "react/jsx-runtime";
119
+ function IconButton({
120
+ onClick,
121
+ disabled = false,
122
+ className = "",
123
+ children,
124
+ ariaLabel
125
+ }) {
126
+ return /* @__PURE__ */ jsx2(
127
+ "button",
128
+ {
129
+ type: "button",
130
+ onClick,
131
+ disabled,
132
+ className,
133
+ "aria-label": ariaLabel,
134
+ children
135
+ }
136
+ );
137
+ }
138
+
139
+ // src/components/datetime/utils.ts
140
+ function getDaysInMonth(year, month) {
141
+ return new Date(year, month + 1, 0).getDate();
142
+ }
143
+ function getFirstDayOfMonth(year, month) {
144
+ return new Date(year, month, 1).getDay();
145
+ }
146
+ function getCalendarWeeks(year, month) {
147
+ const weeks = [];
148
+ const firstDay = getFirstDayOfMonth(year, month);
149
+ const daysInMonth = getDaysInMonth(year, month);
150
+ const prevMonthDays = getDaysInMonth(year, month - 1);
151
+ let currentWeek = [];
152
+ for (let i = firstDay - 1; i >= 0; i--) {
153
+ currentWeek.push(new Date(year, month - 1, prevMonthDays - i));
154
+ }
155
+ for (let day = 1; day <= daysInMonth; day++) {
156
+ currentWeek.push(new Date(year, month, day));
157
+ if (currentWeek.length === 7) {
158
+ weeks.push(currentWeek);
159
+ currentWeek = [];
160
+ }
161
+ }
162
+ if (currentWeek.length > 0) {
163
+ const remaining = 7 - currentWeek.length;
164
+ for (let i = 1; i <= remaining; i++) {
165
+ currentWeek.push(new Date(year, month + 1, i));
166
+ }
167
+ weeks.push(currentWeek);
168
+ }
169
+ return weeks;
170
+ }
171
+ function isSameDay(a, b) {
172
+ if (!a || !b) return false;
173
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
174
+ }
175
+ function isInRange(day, range) {
176
+ const [start, end] = range;
177
+ if (!start || !end) return false;
178
+ const d = day.getTime();
179
+ return d >= start.getTime() && d <= end.getTime();
180
+ }
181
+ function clampDate(date, min, max) {
182
+ if (min && date.getTime() < min.getTime()) return new Date(min);
183
+ if (max && date.getTime() > max.getTime()) return new Date(max);
184
+ return date;
185
+ }
186
+ function isDateDisabled(date, min, max) {
187
+ if (min && date.getTime() < new Date(min.getFullYear(), min.getMonth(), min.getDate()).getTime())
188
+ return true;
189
+ if (max && date.getTime() > new Date(max.getFullYear(), max.getMonth(), max.getDate()).getTime())
190
+ return true;
191
+ return false;
192
+ }
193
+ function isSameMonth(a, b) {
194
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth();
195
+ }
196
+ function formatDateValue(date, locale = "en-US") {
197
+ return date.toLocaleDateString(locale, {
198
+ year: "numeric",
199
+ month: "short",
200
+ day: "numeric"
201
+ });
202
+ }
203
+ function formatTimeValue(date, locale = "en-US", showSeconds = false) {
204
+ return date.toLocaleTimeString(locale, {
205
+ hour: "2-digit",
206
+ minute: "2-digit",
207
+ second: showSeconds ? "2-digit" : void 0
208
+ });
209
+ }
210
+ function formatDateTimeValue(date, locale = "en-US", showSeconds = false) {
211
+ return `${formatDateValue(date, locale)} ${formatTimeValue(date, locale, showSeconds)}`;
212
+ }
213
+ var REFERENCE_SUNDAY = new Date(2023, 0, 1);
214
+ function getWeekdayLabels(locale = "en-US") {
215
+ const labels = [];
216
+ for (let i = 0; i < 7; i++) {
217
+ const d = new Date(REFERENCE_SUNDAY);
218
+ d.setDate(d.getDate() + i);
219
+ labels.push(d.toLocaleDateString(locale, { weekday: "short" }));
220
+ }
221
+ return labels;
222
+ }
223
+ function getMonthLabel(date, locale = "en-US") {
224
+ return date.toLocaleDateString(locale, { month: "long" });
225
+ }
226
+ function getAMPeriodLabels(locale = "en-US") {
227
+ const format = new Intl.DateTimeFormat(locale, {
228
+ hour: "numeric",
229
+ hour12: true
230
+ });
231
+ const amParts = format.formatToParts(new Date(2020, 0, 1, 9, 0, 0));
232
+ const pmParts = format.formatToParts(new Date(2020, 0, 1, 21, 0, 0));
233
+ const amLabel = amParts.find((p) => p.type === "dayPeriod")?.value ?? "AM";
234
+ const pmLabel = pmParts.find((p) => p.type === "dayPeriod")?.value ?? "PM";
235
+ return [amLabel, pmLabel];
236
+ }
237
+ function generateTimeItems(count, step = 1) {
238
+ const items = [];
239
+ for (let i = 0; i < count; i += step) {
240
+ items.push(String(i).padStart(2, "0"));
241
+ }
242
+ return items;
243
+ }
244
+
245
+ // src/components/datetime/Calendar.tsx
246
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
247
+ function Calendar({
248
+ value,
249
+ highlightRange,
250
+ onSelect,
251
+ month: controlledMonth,
252
+ defaultMonth,
253
+ onMonthChange,
254
+ min,
255
+ max,
256
+ className = "",
257
+ style,
258
+ locale = "en-US"
259
+ }) {
260
+ const [internalMonth, setInternalMonth] = useState3(
261
+ () => defaultMonth ?? value ?? /* @__PURE__ */ new Date()
262
+ );
263
+ const month = controlledMonth ?? internalMonth;
264
+ const setMonth = (d) => {
265
+ if (!controlledMonth) setInternalMonth(d);
266
+ onMonthChange?.(d);
267
+ };
268
+ const weeks = getCalendarWeeks(month.getFullYear(), month.getMonth());
269
+ const goToPrevMonth = () => {
270
+ setMonth(new Date(month.getFullYear(), month.getMonth() - 1, 1));
271
+ };
272
+ const goToNextMonth = () => {
273
+ setMonth(new Date(month.getFullYear(), month.getMonth() + 1, 1));
274
+ };
275
+ const prevDisabled = min != null && month.getFullYear() <= min.getFullYear() && month.getMonth() <= min.getMonth();
276
+ const nextDisabled = max != null && month.getFullYear() >= max.getFullYear() && month.getMonth() >= max.getMonth();
277
+ const rangeStart = highlightRange?.[0] ?? null;
278
+ const rangeEnd = highlightRange?.[1] ?? null;
279
+ return /* @__PURE__ */ jsxs("div", { className: `rdp-calendar ${className}`, style, children: [
280
+ /* @__PURE__ */ jsxs("div", { className: "rdp-calendar-header", children: [
281
+ /* @__PURE__ */ jsx3(
282
+ IconButton,
283
+ {
284
+ onClick: goToPrevMonth,
285
+ disabled: prevDisabled,
286
+ className: "rdp-calendar-nav-btn",
287
+ ariaLabel: "Previous month",
288
+ children: /* @__PURE__ */ jsx3(ChevronLeft, {})
289
+ }
290
+ ),
291
+ /* @__PURE__ */ jsxs("span", { className: "rdp-calendar-month-year", children: [
292
+ getMonthLabel(month, locale),
293
+ " ",
294
+ month.getFullYear()
295
+ ] }),
296
+ /* @__PURE__ */ jsx3(
297
+ IconButton,
298
+ {
299
+ onClick: goToNextMonth,
300
+ disabled: nextDisabled,
301
+ className: "rdp-calendar-nav-btn",
302
+ ariaLabel: "Next month",
303
+ children: /* @__PURE__ */ jsx3(ChevronRight, {})
304
+ }
305
+ )
306
+ ] }),
307
+ /* @__PURE__ */ jsxs("div", { className: "rdp-calendar-grid", children: [
308
+ getWeekdayLabels(locale).map((label) => /* @__PURE__ */ jsx3("div", { className: "rdp-calendar-weekday", children: label }, label)),
309
+ weeks.map(
310
+ (week, wi) => week.map((day, di) => {
311
+ const inCurrentMonth = isSameMonth(day, month);
312
+ const isSelected = isSameDay(day, value ?? null);
313
+ const isToday = isSameDay(day, /* @__PURE__ */ new Date());
314
+ const disabled = isDateDisabled(day, min, max);
315
+ const range = highlightRange ? [rangeStart, rangeEnd] : null;
316
+ const inRange = range && range[0] && range[1] ? isInRange(day, range) : false;
317
+ const isRangeStart = isSameDay(day, rangeStart);
318
+ const isRangeEnd = isSameDay(day, rangeEnd);
319
+ let cls = "rdp-calendar-day";
320
+ if (!inCurrentMonth) cls += " rdp-calendar-day--outside";
321
+ if (isSelected) cls += " rdp-calendar-day--selected";
322
+ if (isToday) cls += " rdp-calendar-day--today";
323
+ if (inRange && !isRangeStart && !isRangeEnd) cls += " rdp-calendar-day--in-range";
324
+ if (isRangeStart) cls += " rdp-calendar-day--range-start";
325
+ if (isRangeEnd) cls += " rdp-calendar-day--range-end";
326
+ return /* @__PURE__ */ jsx3(
327
+ "button",
328
+ {
329
+ type: "button",
330
+ className: cls,
331
+ disabled,
332
+ onClick: () => onSelect(day),
333
+ "aria-label": day.toLocaleDateString(locale),
334
+ children: day.getDate()
335
+ },
336
+ `${wi}-${di}`
337
+ );
338
+ })
339
+ )
340
+ ] })
341
+ ] });
342
+ }
343
+ function ChevronLeft() {
344
+ return /* @__PURE__ */ jsx3("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx3("polyline", { points: "15 18 9 12 15 6" }) });
345
+ }
346
+ function ChevronRight() {
347
+ return /* @__PURE__ */ jsx3("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx3("polyline", { points: "9 18 15 12 9 6" }) });
348
+ }
349
+
350
+ // src/components/datetime/InputTrigger.tsx
351
+ import { useRef as useRef3 } from "react";
352
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
353
+ function InputTrigger({
354
+ value,
355
+ displayValue,
356
+ placeholder,
357
+ className = "",
358
+ style,
359
+ onClick,
360
+ open,
361
+ onClose,
362
+ children,
363
+ renderTrigger
364
+ }) {
365
+ const ref = useRef3(null);
366
+ return /* @__PURE__ */ jsxs2("div", { ref, className: `rdp-wrapper ${className}`, style, children: [
367
+ renderTrigger ? renderTrigger({ value: value ?? null, displayValue, placeholder, onClick, open, onClose }) : /* @__PURE__ */ jsxs2(
368
+ "button",
369
+ {
370
+ type: "button",
371
+ className: "rdp-trigger",
372
+ onClick,
373
+ children: [
374
+ /* @__PURE__ */ jsx4("span", { style: { flex: 1, textAlign: "left", color: displayValue ? "inherit" : "var(--rdp-text-muted)" }, children: displayValue || placeholder || "Select..." }),
375
+ /* @__PURE__ */ jsx4(CalendarIcon, {})
376
+ ]
377
+ }
378
+ ),
379
+ children
380
+ ] });
381
+ }
382
+ function RangeInputTrigger({
383
+ value,
384
+ displayValues,
385
+ placeholder,
386
+ className = "",
387
+ style,
388
+ onClick,
389
+ open,
390
+ onClose,
391
+ children,
392
+ renderTrigger
393
+ }) {
394
+ const ref = useRef3(null);
395
+ return /* @__PURE__ */ jsxs2("div", { ref, className: `rdp-wrapper ${className}`, style, children: [
396
+ renderTrigger ? renderTrigger({ value: value ?? [null, null], displayValues, placeholder, onClick, open, onClose }) : /* @__PURE__ */ jsxs2(
397
+ "div",
398
+ {
399
+ className: "rdp-trigger rdp-trigger--range",
400
+ onClick,
401
+ role: "button",
402
+ tabIndex: 0,
403
+ onKeyDown: (e) => {
404
+ if (e.key === "Enter" || e.key === " ") {
405
+ e.preventDefault();
406
+ onClick();
407
+ }
408
+ },
409
+ children: [
410
+ /* @__PURE__ */ jsx4(
411
+ "input",
412
+ {
413
+ type: "text",
414
+ readOnly: true,
415
+ value: displayValues[0],
416
+ placeholder: placeholder?.[0] ?? "Start"
417
+ }
418
+ ),
419
+ /* @__PURE__ */ jsx4("span", { className: "rdp-trigger-range-separator", children: "\u2013" }),
420
+ /* @__PURE__ */ jsx4(
421
+ "input",
422
+ {
423
+ type: "text",
424
+ readOnly: true,
425
+ value: displayValues[1],
426
+ placeholder: placeholder?.[1] ?? "End"
427
+ }
428
+ ),
429
+ /* @__PURE__ */ jsx4(CalendarIcon, {})
430
+ ]
431
+ }
432
+ ),
433
+ children
434
+ ] });
435
+ }
436
+ function CalendarIcon() {
437
+ return /* @__PURE__ */ jsxs2("svg", { className: "rdp-trigger-icon", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [
438
+ /* @__PURE__ */ jsx4("rect", { x: "1.5", y: "2.5", width: "13", height: "12", rx: "2" }),
439
+ /* @__PURE__ */ jsx4("line", { x1: "1.5", y1: "6", x2: "14.5", y2: "6" }),
440
+ /* @__PURE__ */ jsx4("line", { x1: "5", y1: "1", x2: "5", y2: "4" }),
441
+ /* @__PURE__ */ jsx4("line", { x1: "11", y1: "1", x2: "11", y2: "4" })
442
+ ] });
443
+ }
444
+
445
+ // src/components/datetime/DatePicker.tsx
446
+ import { jsx as jsx5 } from "react/jsx-runtime";
447
+ function DatePicker({
448
+ value: controlledValue,
449
+ onChange,
450
+ defaultValue,
451
+ locale = "en-US",
452
+ className = "",
453
+ style,
454
+ placeholder = "Select date",
455
+ position,
456
+ min,
457
+ max,
458
+ renderTrigger
459
+ }) {
460
+ const [value, setValue] = useControlled(controlledValue, defaultValue ?? null, onChange);
461
+ const [open, setOpen] = useState4(false);
462
+ const handleSelect = (date) => {
463
+ const clamped = clampDate(date, min, max);
464
+ setValue(clamped);
465
+ setOpen(false);
466
+ };
467
+ const displayValue = value ? formatDateValue(value, locale) : "";
468
+ return /* @__PURE__ */ jsx5(
469
+ InputTrigger,
470
+ {
471
+ value,
472
+ displayValue,
473
+ placeholder,
474
+ className,
475
+ style,
476
+ onClick: () => setOpen((o) => !o),
477
+ open,
478
+ onClose: () => setOpen(false),
479
+ renderTrigger,
480
+ children: /* @__PURE__ */ jsx5(Popover, { open, onClose: () => setOpen(false), position, children: /* @__PURE__ */ jsx5(
481
+ Calendar,
482
+ {
483
+ value,
484
+ onSelect: handleSelect,
485
+ min,
486
+ max,
487
+ locale,
488
+ defaultMonth: value ?? void 0
489
+ }
490
+ ) })
491
+ }
492
+ );
493
+ }
494
+
495
+ // src/components/datetime/TimePicker.tsx
496
+ import { useState as useState5, useMemo } from "react";
497
+
498
+ // src/components/datetime/TimeColumn.tsx
499
+ import { useRef as useRef4, useEffect as useEffect2 } from "react";
500
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
501
+ function TimeColumn({
502
+ count,
503
+ step,
504
+ value,
505
+ onChange,
506
+ label,
507
+ formatValue
508
+ }) {
509
+ const containerRef = useRef4(null);
510
+ const items = generateTimeItems(count, step);
511
+ const selectedIndex = items.findIndex(
512
+ (item) => parseInt(item, 10) === value
513
+ );
514
+ useEffect2(() => {
515
+ if (containerRef.current && selectedIndex >= 0) {
516
+ const child = containerRef.current.children[selectedIndex + 1];
517
+ if (child) {
518
+ child.scrollIntoView({ block: "center", behavior: "smooth" });
519
+ }
520
+ }
521
+ }, [selectedIndex]);
522
+ return /* @__PURE__ */ jsxs3("div", { children: [
523
+ label && /* @__PURE__ */ jsx6("div", { className: "rdp-time-label", children: label }),
524
+ /* @__PURE__ */ jsx6("div", { className: "rdp-time-column", ref: containerRef, children: items.map((item) => {
525
+ const num = parseInt(item, 10);
526
+ const isSelected = num === value;
527
+ return /* @__PURE__ */ jsx6(
528
+ "button",
529
+ {
530
+ type: "button",
531
+ className: `rdp-time-item ${isSelected ? "rdp-time-item--selected" : ""}`,
532
+ onClick: () => onChange(num),
533
+ children: formatValue ? formatValue(num) : item
534
+ },
535
+ item
536
+ );
537
+ }) })
538
+ ] });
539
+ }
540
+
541
+ // src/components/datetime/TimePicker.tsx
542
+ import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
543
+ function TimePicker({
544
+ value: controlledValue,
545
+ onChange,
546
+ defaultValue,
547
+ locale = "en-US",
548
+ className = "",
549
+ style,
550
+ placeholder = "Select time",
551
+ position,
552
+ step = 1,
553
+ showSeconds = false,
554
+ timeFormat = "24h",
555
+ min,
556
+ max,
557
+ renderTrigger
558
+ }) {
559
+ const [value, setValue] = useControlled(controlledValue, defaultValue ?? null, onChange);
560
+ const [open, setOpen] = useState5(false);
561
+ const handleHourChange = (hour) => {
562
+ const newDate = value ? new Date(value) : /* @__PURE__ */ new Date();
563
+ newDate.setHours(hour);
564
+ const clamped = clampDate(newDate, min, max);
565
+ setValue(clamped);
566
+ };
567
+ const handleMinuteChange = (minute) => {
568
+ const newDate = value ? new Date(value) : /* @__PURE__ */ new Date();
569
+ newDate.setMinutes(minute);
570
+ const clamped = clampDate(newDate, min, max);
571
+ setValue(clamped);
572
+ };
573
+ const handleSecondChange = (second) => {
574
+ const newDate = value ? new Date(value) : /* @__PURE__ */ new Date();
575
+ newDate.setSeconds(second);
576
+ const clamped = clampDate(newDate, min, max);
577
+ setValue(clamped);
578
+ };
579
+ const displayValue = value ? formatTimeValue(value, locale, showSeconds) : "";
580
+ const current = value ?? /* @__PURE__ */ new Date();
581
+ const [amLabel, pmLabel] = useMemo(() => getAMPeriodLabels(locale), [locale]);
582
+ const formatHour = (h) => {
583
+ if (timeFormat === "12h") {
584
+ const display = h === 0 ? 12 : h > 12 ? h - 12 : h;
585
+ return `${display} ${h < 12 ? amLabel : pmLabel}`;
586
+ }
587
+ return String(h).padStart(2, "0");
588
+ };
589
+ return /* @__PURE__ */ jsx7(
590
+ InputTrigger,
591
+ {
592
+ value,
593
+ displayValue,
594
+ placeholder,
595
+ className,
596
+ style,
597
+ onClick: () => setOpen((o) => !o),
598
+ open,
599
+ onClose: () => setOpen(false),
600
+ renderTrigger,
601
+ children: /* @__PURE__ */ jsx7(Popover, { open, onClose: () => setOpen(false), position, children: /* @__PURE__ */ jsxs4("div", { className: "rdp-time-picker", children: [
602
+ /* @__PURE__ */ jsx7(
603
+ TimeColumn,
604
+ {
605
+ count: 24,
606
+ step: 1,
607
+ value: current.getHours(),
608
+ onChange: handleHourChange,
609
+ label: "Hour",
610
+ formatValue: formatHour
611
+ }
612
+ ),
613
+ /* @__PURE__ */ jsx7(
614
+ TimeColumn,
615
+ {
616
+ count: 60,
617
+ step,
618
+ value: current.getMinutes(),
619
+ onChange: handleMinuteChange,
620
+ label: "Minute"
621
+ }
622
+ ),
623
+ showSeconds && /* @__PURE__ */ jsx7(
624
+ TimeColumn,
625
+ {
626
+ count: 60,
627
+ step: 1,
628
+ value: current.getSeconds(),
629
+ onChange: handleSecondChange,
630
+ label: "Second"
631
+ }
632
+ )
633
+ ] }) })
634
+ }
635
+ );
636
+ }
637
+
638
+ // src/components/datetime/DateTimePicker.tsx
639
+ import { useState as useState6, useMemo as useMemo2 } from "react";
640
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
641
+ function DateTimePicker({
642
+ value: controlledValue,
643
+ onChange,
644
+ defaultValue,
645
+ locale = "en-US",
646
+ className = "",
647
+ style,
648
+ placeholder = "Select date & time",
649
+ position,
650
+ step = 1,
651
+ showSeconds = false,
652
+ timeFormat = "24h",
653
+ min,
654
+ max,
655
+ renderTrigger
656
+ }) {
657
+ const [value, setValue] = useControlled(controlledValue, defaultValue ?? null, onChange);
658
+ const [open, setOpen] = useState6(false);
659
+ const [showCalendar, setShowCalendar] = useState6(true);
660
+ const handleDateSelect = (date) => {
661
+ const current2 = value ? new Date(value) : /* @__PURE__ */ new Date();
662
+ const newDate = new Date(date);
663
+ newDate.setHours(current2.getHours(), current2.getMinutes(), current2.getSeconds());
664
+ setValue(clampDate(newDate, min, max));
665
+ };
666
+ const handleHourChange = (hour) => {
667
+ const current2 = value ? new Date(value) : /* @__PURE__ */ new Date();
668
+ current2.setHours(hour);
669
+ setValue(clampDate(current2, min, max));
670
+ };
671
+ const handleMinuteChange = (minute) => {
672
+ const current2 = value ? new Date(value) : /* @__PURE__ */ new Date();
673
+ current2.setMinutes(minute);
674
+ setValue(clampDate(current2, min, max));
675
+ };
676
+ const handleSecondChange = (second) => {
677
+ const current2 = value ? new Date(value) : /* @__PURE__ */ new Date();
678
+ current2.setSeconds(second);
679
+ setValue(clampDate(current2, min, max));
680
+ };
681
+ const handleClose = () => {
682
+ setOpen(false);
683
+ setShowCalendar(true);
684
+ };
685
+ const displayValue = value ? formatDateTimeValue(value, locale, showSeconds) : "";
686
+ const current = value ?? /* @__PURE__ */ new Date();
687
+ const [amLabel, pmLabel] = useMemo2(() => getAMPeriodLabels(locale), [locale]);
688
+ const formatHour = (h) => {
689
+ if (timeFormat === "12h") {
690
+ const display = h === 0 ? 12 : h > 12 ? h - 12 : h;
691
+ return `${display} ${h < 12 ? amLabel : pmLabel}`;
692
+ }
693
+ return String(h).padStart(2, "0");
694
+ };
695
+ return /* @__PURE__ */ jsx8(
696
+ InputTrigger,
697
+ {
698
+ value,
699
+ displayValue,
700
+ placeholder,
701
+ className,
702
+ style,
703
+ onClick: () => setOpen((o) => !o),
704
+ open,
705
+ onClose: handleClose,
706
+ renderTrigger,
707
+ children: /* @__PURE__ */ jsx8(Popover, { open, onClose: handleClose, position, children: /* @__PURE__ */ jsxs5("div", { className: "rdp-datetime-layout", children: [
708
+ /* @__PURE__ */ jsx8(
709
+ Calendar,
710
+ {
711
+ value,
712
+ onSelect: handleDateSelect,
713
+ min,
714
+ max,
715
+ locale,
716
+ defaultMonth: value ?? void 0
717
+ }
718
+ ),
719
+ /* @__PURE__ */ jsxs5("div", { className: "rdp-time-picker", children: [
720
+ /* @__PURE__ */ jsx8(
721
+ TimeColumn,
722
+ {
723
+ count: 24,
724
+ step: 1,
725
+ value: current.getHours(),
726
+ onChange: handleHourChange,
727
+ label: "Hour",
728
+ formatValue: formatHour
729
+ }
730
+ ),
731
+ /* @__PURE__ */ jsx8(
732
+ TimeColumn,
733
+ {
734
+ count: 60,
735
+ step,
736
+ value: current.getMinutes(),
737
+ onChange: handleMinuteChange,
738
+ label: "Minute"
739
+ }
740
+ ),
741
+ showSeconds && /* @__PURE__ */ jsx8(
742
+ TimeColumn,
743
+ {
744
+ count: 60,
745
+ step: 1,
746
+ value: current.getSeconds(),
747
+ onChange: handleSecondChange,
748
+ label: "Second"
749
+ }
750
+ )
751
+ ] })
752
+ ] }) })
753
+ }
754
+ );
755
+ }
756
+
757
+ // src/components/datetime/DateRangePicker.tsx
758
+ import { useState as useState7, useCallback as useCallback2 } from "react";
759
+ import { jsx as jsx9 } from "react/jsx-runtime";
760
+ function DateRangePicker({
761
+ value: controlledValue,
762
+ onChange,
763
+ defaultValue,
764
+ locale = "en-US",
765
+ className = "",
766
+ style,
767
+ placeholder,
768
+ position,
769
+ min,
770
+ max,
771
+ renderTrigger
772
+ }) {
773
+ const [range, setRange] = useState7(
774
+ controlledValue ?? defaultValue ?? [null, null]
775
+ );
776
+ const [open, setOpen] = useState7(false);
777
+ const [pendingStart, setPendingStart] = useState7(null);
778
+ const isControlled = controlledValue !== void 0;
779
+ const currentRange = isControlled ? controlledValue : range;
780
+ const emit = (newRange) => {
781
+ if (!isControlled) setRange(newRange);
782
+ onChange?.(newRange);
783
+ };
784
+ const handleSelect = useCallback2(
785
+ (date) => {
786
+ const clamped = clampDate(date, min, max);
787
+ if (!pendingStart) {
788
+ setPendingStart(clamped);
789
+ emit([clamped, null]);
790
+ } else {
791
+ if (clamped.getTime() < pendingStart.getTime()) {
792
+ emit([clamped, pendingStart]);
793
+ } else {
794
+ emit([pendingStart, clamped]);
795
+ }
796
+ setPendingStart(null);
797
+ setOpen(false);
798
+ }
799
+ },
800
+ [pendingStart, min, max, emit]
801
+ );
802
+ const handleOpen = () => {
803
+ setPendingStart(null);
804
+ setOpen((o) => !o);
805
+ };
806
+ const handleClose = () => {
807
+ setOpen(false);
808
+ setPendingStart(null);
809
+ };
810
+ const displayValues = [
811
+ currentRange[0] ? formatDateValue(currentRange[0], locale) : "",
812
+ currentRange[1] ? formatDateValue(currentRange[1], locale) : ""
813
+ ];
814
+ const highlightRange = pendingStart ? [pendingStart, currentRange[1]] : [currentRange[0], currentRange[1]];
815
+ return /* @__PURE__ */ jsx9(
816
+ RangeInputTrigger,
817
+ {
818
+ value: currentRange,
819
+ displayValues,
820
+ placeholder: placeholder ?? ["Start date", "End date"],
821
+ className,
822
+ style,
823
+ onClick: handleOpen,
824
+ open,
825
+ onClose: handleClose,
826
+ renderTrigger,
827
+ children: /* @__PURE__ */ jsx9(Popover, { open, onClose: handleClose, position, children: /* @__PURE__ */ jsx9(
828
+ Calendar,
829
+ {
830
+ value: pendingStart ?? currentRange[0],
831
+ highlightRange,
832
+ onSelect: handleSelect,
833
+ min,
834
+ max,
835
+ locale,
836
+ defaultMonth: currentRange[0] ?? pendingStart ?? void 0
837
+ }
838
+ ) })
839
+ }
840
+ );
841
+ }
842
+
843
+ // src/components/datetime/TimeRangePicker.tsx
844
+ import { useState as useState8, useMemo as useMemo3 } from "react";
845
+ import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
846
+ function TimeRangePicker({
847
+ value: controlledValue,
848
+ onChange,
849
+ defaultValue,
850
+ locale = "en-US",
851
+ className = "",
852
+ style,
853
+ placeholder,
854
+ position,
855
+ step = 1,
856
+ showSeconds = false,
857
+ timeFormat = "24h",
858
+ min,
859
+ max,
860
+ renderTrigger
861
+ }) {
862
+ const [range, setRange] = useState8(
863
+ controlledValue ?? defaultValue ?? [null, null]
864
+ );
865
+ const [open, setOpen] = useState8(false);
866
+ const isControlled = controlledValue !== void 0;
867
+ const currentRange = isControlled ? controlledValue : range;
868
+ const emit = (newRange) => {
869
+ if (!isControlled) setRange(newRange);
870
+ onChange?.(newRange);
871
+ };
872
+ const getStart = () => {
873
+ const d = currentRange[0] ? new Date(currentRange[0]) : /* @__PURE__ */ new Date();
874
+ d.setSeconds(0, 0);
875
+ return d;
876
+ };
877
+ const getEnd = () => {
878
+ const d = currentRange[1] ? new Date(currentRange[1]) : /* @__PURE__ */ new Date();
879
+ d.setHours(23, 0, 0, 0);
880
+ return d;
881
+ };
882
+ const updateStartTime = (hours, minutes, seconds = 0) => {
883
+ const start2 = getStart();
884
+ start2.setHours(hours, minutes, seconds);
885
+ emit([clampDate(start2, min, max), currentRange[1]]);
886
+ };
887
+ const updateEndTime = (hours, minutes, seconds = 0) => {
888
+ const end2 = getEnd();
889
+ end2.setHours(hours, minutes, seconds);
890
+ emit([currentRange[0], clampDate(end2, min, max)]);
891
+ };
892
+ const displayValues = [
893
+ currentRange[0] ? formatTimeValue(currentRange[0], locale, showSeconds) : "",
894
+ currentRange[1] ? formatTimeValue(currentRange[1], locale, showSeconds) : ""
895
+ ];
896
+ const start = getStart();
897
+ const end = getEnd();
898
+ const [amLabel, pmLabel] = useMemo3(() => getAMPeriodLabels(locale), [locale]);
899
+ const formatHour = (h) => {
900
+ if (timeFormat === "12h") {
901
+ const display = h === 0 ? 12 : h > 12 ? h - 12 : h;
902
+ return `${display} ${h < 12 ? amLabel : pmLabel}`;
903
+ }
904
+ return String(h).padStart(2, "0");
905
+ };
906
+ return /* @__PURE__ */ jsx10(
907
+ RangeInputTrigger,
908
+ {
909
+ value: currentRange,
910
+ displayValues,
911
+ placeholder: placeholder ?? ["Start time", "End time"],
912
+ className,
913
+ style,
914
+ onClick: () => setOpen((o) => !o),
915
+ open,
916
+ onClose: () => setOpen(false),
917
+ renderTrigger,
918
+ children: /* @__PURE__ */ jsx10(Popover, { open, onClose: () => setOpen(false), position, children: /* @__PURE__ */ jsxs6("div", { style: { display: "flex", gap: 16 }, children: [
919
+ /* @__PURE__ */ jsxs6("div", { className: "rdp-datetime-range-panel", children: [
920
+ /* @__PURE__ */ jsx10("div", { className: "rdp-datetime-range-label", children: "Start" }),
921
+ /* @__PURE__ */ jsxs6("div", { className: "rdp-time-picker", children: [
922
+ /* @__PURE__ */ jsx10(
923
+ TimeColumn,
924
+ {
925
+ count: 24,
926
+ step: 1,
927
+ value: start.getHours(),
928
+ onChange: (h) => updateStartTime(h, start.getMinutes(), start.getSeconds()),
929
+ label: "Hour",
930
+ formatValue: formatHour
931
+ }
932
+ ),
933
+ /* @__PURE__ */ jsx10(
934
+ TimeColumn,
935
+ {
936
+ count: 60,
937
+ step,
938
+ value: start.getMinutes(),
939
+ onChange: (m) => updateStartTime(start.getHours(), m, start.getSeconds()),
940
+ label: "Minute"
941
+ }
942
+ ),
943
+ showSeconds && /* @__PURE__ */ jsx10(
944
+ TimeColumn,
945
+ {
946
+ count: 60,
947
+ step: 1,
948
+ value: start.getSeconds(),
949
+ onChange: (s) => updateStartTime(start.getHours(), start.getMinutes(), s),
950
+ label: "Second"
951
+ }
952
+ )
953
+ ] })
954
+ ] }),
955
+ /* @__PURE__ */ jsxs6("div", { className: "rdp-datetime-range-panel", children: [
956
+ /* @__PURE__ */ jsx10("div", { className: "rdp-datetime-range-label", children: "End" }),
957
+ /* @__PURE__ */ jsxs6("div", { className: "rdp-time-picker", children: [
958
+ /* @__PURE__ */ jsx10(
959
+ TimeColumn,
960
+ {
961
+ count: 24,
962
+ step: 1,
963
+ value: end.getHours(),
964
+ onChange: (h) => updateEndTime(h, end.getMinutes(), end.getSeconds()),
965
+ label: "Hour",
966
+ formatValue: formatHour
967
+ }
968
+ ),
969
+ /* @__PURE__ */ jsx10(
970
+ TimeColumn,
971
+ {
972
+ count: 60,
973
+ step,
974
+ value: end.getMinutes(),
975
+ onChange: (m) => updateEndTime(end.getHours(), m, end.getSeconds()),
976
+ label: "Minute"
977
+ }
978
+ ),
979
+ showSeconds && /* @__PURE__ */ jsx10(
980
+ TimeColumn,
981
+ {
982
+ count: 60,
983
+ step: 1,
984
+ value: end.getSeconds(),
985
+ onChange: (s) => updateEndTime(end.getHours(), end.getMinutes(), s),
986
+ label: "Second"
987
+ }
988
+ )
989
+ ] })
990
+ ] })
991
+ ] }) })
992
+ }
993
+ );
994
+ }
995
+
996
+ // src/components/datetime/DateTimeRangePicker.tsx
997
+ import { useState as useState9, useCallback as useCallback3, useMemo as useMemo4 } from "react";
998
+ import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
999
+ function DateTimeRangePicker({
1000
+ value: controlledValue,
1001
+ onChange,
1002
+ defaultValue,
1003
+ locale = "en-US",
1004
+ className = "",
1005
+ style,
1006
+ placeholder,
1007
+ position,
1008
+ step = 1,
1009
+ showSeconds = false,
1010
+ timeFormat = "24h",
1011
+ min,
1012
+ max,
1013
+ renderTrigger
1014
+ }) {
1015
+ const [range, setRange] = useState9(
1016
+ controlledValue ?? defaultValue ?? [null, null]
1017
+ );
1018
+ const [open, setOpen] = useState9(false);
1019
+ const [pendingStart, setPendingStart] = useState9(null);
1020
+ const [editingSide, setEditingSide] = useState9("date");
1021
+ const isControlled = controlledValue !== void 0;
1022
+ const currentRange = isControlled ? controlledValue : range;
1023
+ const emit = (newRange) => {
1024
+ if (!isControlled) setRange(newRange);
1025
+ onChange?.(newRange);
1026
+ };
1027
+ const handleDateSelect = useCallback3(
1028
+ (date) => {
1029
+ if (!pendingStart) {
1030
+ setPendingStart(date);
1031
+ emit([date, null]);
1032
+ } else {
1033
+ if (date.getTime() < pendingStart.getTime()) {
1034
+ emit([date, pendingStart]);
1035
+ } else {
1036
+ emit([pendingStart, date]);
1037
+ }
1038
+ setPendingStart(null);
1039
+ setEditingSide("start-time");
1040
+ }
1041
+ },
1042
+ [pendingStart, emit]
1043
+ );
1044
+ const getStart = () => {
1045
+ const d = currentRange[0] ? new Date(currentRange[0]) : /* @__PURE__ */ new Date();
1046
+ return d;
1047
+ };
1048
+ const getEnd = () => {
1049
+ const d = currentRange[1] ? new Date(currentRange[1]) : /* @__PURE__ */ new Date();
1050
+ d.setHours(23, 0, 0, 0);
1051
+ return d;
1052
+ };
1053
+ const updateStartTime = (hours, minutes, seconds = 0) => {
1054
+ const start2 = getStart();
1055
+ start2.setHours(hours, minutes, seconds);
1056
+ emit([clampDate(start2, min, max), currentRange[1]]);
1057
+ };
1058
+ const updateEndTime = (hours, minutes, seconds = 0) => {
1059
+ const end2 = getEnd();
1060
+ end2.setHours(hours, minutes, seconds);
1061
+ emit([currentRange[0], clampDate(end2, min, max)]);
1062
+ };
1063
+ const handleClose = () => {
1064
+ setOpen(false);
1065
+ setPendingStart(null);
1066
+ setEditingSide("date");
1067
+ };
1068
+ const displayValues = [
1069
+ currentRange[0] ? formatDateTimeValue(currentRange[0], locale, showSeconds) : "",
1070
+ currentRange[1] ? formatDateTimeValue(currentRange[1], locale, showSeconds) : ""
1071
+ ];
1072
+ const highlightRange = pendingStart ? [pendingStart, currentRange[1]] : [currentRange[0], currentRange[1]];
1073
+ const start = getStart();
1074
+ const end = getEnd();
1075
+ const [amLabel, pmLabel] = useMemo4(() => getAMPeriodLabels(locale), [locale]);
1076
+ const formatHour = (h) => {
1077
+ if (timeFormat === "12h") {
1078
+ const display = h === 0 ? 12 : h > 12 ? h - 12 : h;
1079
+ return `${display} ${h < 12 ? amLabel : pmLabel}`;
1080
+ }
1081
+ return String(h).padStart(2, "0");
1082
+ };
1083
+ return /* @__PURE__ */ jsx11(
1084
+ RangeInputTrigger,
1085
+ {
1086
+ value: currentRange,
1087
+ displayValues,
1088
+ placeholder: placeholder ?? ["Start date & time", "End date & time"],
1089
+ className,
1090
+ style,
1091
+ onClick: () => setOpen((o) => !o),
1092
+ open,
1093
+ onClose: handleClose,
1094
+ renderTrigger,
1095
+ children: /* @__PURE__ */ jsx11(Popover, { open, onClose: handleClose, position, style: { width: "max-content" }, children: /* @__PURE__ */ jsxs7("div", { style: { display: "flex", gap: 16, flexWrap: "wrap" }, children: [
1096
+ /* @__PURE__ */ jsx11(
1097
+ Calendar,
1098
+ {
1099
+ value: pendingStart ?? currentRange[0],
1100
+ highlightRange,
1101
+ onSelect: handleDateSelect,
1102
+ min,
1103
+ max,
1104
+ locale,
1105
+ defaultMonth: currentRange[0] ?? pendingStart ?? void 0
1106
+ }
1107
+ ),
1108
+ !pendingStart && currentRange[0] && currentRange[1] && /* @__PURE__ */ jsxs7("div", { style: { display: "flex", gap: 16 }, children: [
1109
+ /* @__PURE__ */ jsxs7("div", { className: "rdp-datetime-range-panel", children: [
1110
+ /* @__PURE__ */ jsx11("div", { className: "rdp-datetime-range-label", children: "Start Time" }),
1111
+ /* @__PURE__ */ jsxs7("div", { className: "rdp-time-picker", children: [
1112
+ /* @__PURE__ */ jsx11(
1113
+ TimeColumn,
1114
+ {
1115
+ count: 24,
1116
+ step: 1,
1117
+ value: start.getHours(),
1118
+ onChange: (h) => updateStartTime(h, start.getMinutes(), start.getSeconds()),
1119
+ label: "Hour",
1120
+ formatValue: formatHour
1121
+ }
1122
+ ),
1123
+ /* @__PURE__ */ jsx11(
1124
+ TimeColumn,
1125
+ {
1126
+ count: 60,
1127
+ step,
1128
+ value: start.getMinutes(),
1129
+ onChange: (m) => updateStartTime(start.getHours(), m, start.getSeconds()),
1130
+ label: "Minute"
1131
+ }
1132
+ ),
1133
+ showSeconds && /* @__PURE__ */ jsx11(
1134
+ TimeColumn,
1135
+ {
1136
+ count: 60,
1137
+ step: 1,
1138
+ value: start.getSeconds(),
1139
+ onChange: (s) => updateStartTime(start.getHours(), start.getMinutes(), s),
1140
+ label: "Second"
1141
+ }
1142
+ )
1143
+ ] })
1144
+ ] }),
1145
+ /* @__PURE__ */ jsxs7("div", { className: "rdp-datetime-range-panel", children: [
1146
+ /* @__PURE__ */ jsx11("div", { className: "rdp-datetime-range-label", children: "End Time" }),
1147
+ /* @__PURE__ */ jsxs7("div", { className: "rdp-time-picker", children: [
1148
+ /* @__PURE__ */ jsx11(
1149
+ TimeColumn,
1150
+ {
1151
+ count: 24,
1152
+ step: 1,
1153
+ value: end.getHours(),
1154
+ onChange: (h) => updateEndTime(h, end.getMinutes(), end.getSeconds()),
1155
+ label: "Hour",
1156
+ formatValue: formatHour
1157
+ }
1158
+ ),
1159
+ /* @__PURE__ */ jsx11(
1160
+ TimeColumn,
1161
+ {
1162
+ count: 60,
1163
+ step,
1164
+ value: end.getMinutes(),
1165
+ onChange: (m) => updateEndTime(end.getHours(), m, end.getSeconds()),
1166
+ label: "Minute"
1167
+ }
1168
+ ),
1169
+ showSeconds && /* @__PURE__ */ jsx11(
1170
+ TimeColumn,
1171
+ {
1172
+ count: 60,
1173
+ step: 1,
1174
+ value: end.getSeconds(),
1175
+ onChange: (s) => updateEndTime(end.getHours(), end.getMinutes(), s),
1176
+ label: "Second"
1177
+ }
1178
+ )
1179
+ ] })
1180
+ ] })
1181
+ ] })
1182
+ ] }) })
1183
+ }
1184
+ );
1185
+ }
1186
+ export {
1187
+ DatePicker,
1188
+ DateRangePicker,
1189
+ DateTimePicker,
1190
+ DateTimeRangePicker,
1191
+ TimePicker,
1192
+ TimeRangePicker
1193
+ };
1194
+ //# sourceMappingURL=index.mjs.map