@bigbinary/neeto-atoms 1.0.144 → 1.1.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.
Files changed (48) hide show
  1. package/dist/{DatePicker-edLgTark.js → DatePicker-BC9AOZJ2.js} +713 -135
  2. package/dist/DatePicker-BC9AOZJ2.js.map +1 -0
  3. package/dist/{TimePicker-BA7JhrYd.js → TimePicker-CJ8K2zea.js} +192 -17
  4. package/dist/TimePicker-CJ8K2zea.js.map +1 -0
  5. package/dist/{TranslationProvider-Dd0ITHC0.js → TranslationProvider-BJjfFEgX.js} +12 -1
  6. package/dist/{TranslationProvider-Dd0ITHC0.js.map → TranslationProvider-BJjfFEgX.js.map} +1 -1
  7. package/dist/cjs/{DatePicker-D8GQGON2.js → DatePicker-aGLz-3Bz.js} +724 -145
  8. package/dist/cjs/DatePicker-aGLz-3Bz.js.map +1 -0
  9. package/dist/cjs/{TimePicker-CVGXEgk5.js → TimePicker-BhOW7vbV.js} +217 -41
  10. package/dist/cjs/TimePicker-BhOW7vbV.js.map +1 -0
  11. package/dist/cjs/{TranslationProvider-DOeyGtbV.js → TranslationProvider-C1JDwKEN.js} +12 -1
  12. package/dist/cjs/{TranslationProvider-DOeyGtbV.js.map → TranslationProvider-C1JDwKEN.js.map} +1 -1
  13. package/dist/cjs/components/DatePicker.js +7 -6
  14. package/dist/cjs/components/DatePicker.js.map +1 -1
  15. package/dist/cjs/components/TimePicker.js +7 -7
  16. package/dist/cjs/components/TranslationProvider.js +1 -1
  17. package/dist/cjs/components/index.js +7 -7
  18. package/dist/cjs/index.js +8 -8
  19. package/dist/cjs/primitives/AlertDialog.js +39 -7
  20. package/dist/cjs/primitives/AlertDialog.js.map +1 -1
  21. package/dist/cjs/primitives/index.js +1 -1
  22. package/dist/cjs/{TimePickerPanel-DvVRLqeM.js → utils-S4yTubWZ.js} +25 -197
  23. package/dist/cjs/utils-S4yTubWZ.js.map +1 -0
  24. package/dist/components/DatePicker/DatePickerFooter.d.ts +1 -0
  25. package/dist/components/DatePicker/RangeCalendar.d.ts +4 -3
  26. package/dist/components/DatePicker/SegmentedTimeInput.d.ts +17 -0
  27. package/dist/components/DatePicker/TimeSection.d.ts +22 -0
  28. package/dist/components/DatePicker/constants.d.ts +2 -1
  29. package/dist/components/DatePicker/segmentedTime.d.ts +58 -0
  30. package/dist/components/DatePicker/types.d.ts +33 -4
  31. package/dist/components/DatePicker/utils.d.ts +3 -0
  32. package/dist/components/DatePicker.js +7 -6
  33. package/dist/components/DatePicker.js.map +1 -1
  34. package/dist/components/TimePicker.js +6 -6
  35. package/dist/components/TranslationProvider.js +1 -1
  36. package/dist/components/index.js +6 -6
  37. package/dist/index.js +8 -8
  38. package/dist/translations/index.d.ts +11 -0
  39. package/dist/utils/inputMask.d.ts +2 -1
  40. package/dist/{TimePickerPanel-n-Dz1p5N.js → utils-Ca3DmOrC.js} +22 -197
  41. package/dist/utils-Ca3DmOrC.js.map +1 -0
  42. package/package.json +1 -1
  43. package/dist/DatePicker-edLgTark.js.map +0 -1
  44. package/dist/TimePicker-BA7JhrYd.js.map +0 -1
  45. package/dist/TimePickerPanel-n-Dz1p5N.js.map +0 -1
  46. package/dist/cjs/DatePicker-D8GQGON2.js.map +0 -1
  47. package/dist/cjs/TimePicker-CVGXEgk5.js.map +0 -1
  48. package/dist/cjs/TimePickerPanel-DvVRLqeM.js.map +0 -1
@@ -5,7 +5,7 @@ var React = require('react');
5
5
  var primitives_Calendar = require('./primitives/Calendar.js');
6
6
  var primitives_Popover = require('./primitives/Popover.js');
7
7
  var utils$1 = require('./utils-BhM0B89p.js');
8
- var TimePickerPanel = require('./TimePickerPanel-DvVRLqeM.js');
8
+ var utils$2 = require('./utils-S4yTubWZ.js');
9
9
  var pureDayjs = require('dayjs');
10
10
  var customParseFormat = require('dayjs/plugin/customParseFormat');
11
11
  var utils = require('@bigbinary/neeto-commons-frontend/utils');
@@ -14,11 +14,15 @@ var primitives_Button = require('./primitives/Button.js');
14
14
  var primitives_Field = require('./primitives/Field.js');
15
15
  var createLucideIcon = require('./createLucideIcon-D0tRgV6l.js');
16
16
  var x = require('./x-Brw3FJst.js');
17
+ var reactI18next = require('react-i18next');
18
+ var primitives_Label = require('./primitives/Label.js');
19
+ var primitives_Switch = require('./primitives/Switch.js');
17
20
  var chevronLeft = require('./chevron-left-BldoOh5p.js');
18
21
  var chevronRight = require('./chevron-right-0jNdwX2Q.js');
19
22
 
20
23
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
21
24
 
25
+ var React__default = /*#__PURE__*/_interopDefault(React);
22
26
  var pureDayjs__default = /*#__PURE__*/_interopDefault(pureDayjs);
23
27
  var customParseFormat__default = /*#__PURE__*/_interopDefault(customParseFormat);
24
28
 
@@ -30,16 +34,32 @@ var customParseFormat__default = /*#__PURE__*/_interopDefault(customParseFormat)
30
34
  */
31
35
 
32
36
 
33
- const __iconNode = [
37
+ const __iconNode$1 = [
34
38
  ["path", { d: "M8 2v4", key: "1cmpym" }],
35
39
  ["path", { d: "M16 2v4", key: "4m81vk" }],
36
40
  ["rect", { width: "18", height: "18", x: "3", y: "4", rx: "2", key: "1hopcy" }],
37
41
  ["path", { d: "M3 10h18", key: "8toen8" }]
38
42
  ];
39
- const Calendar = createLucideIcon.createLucideIcon("calendar", __iconNode);
43
+ const Calendar = createLucideIcon.createLucideIcon("calendar", __iconNode$1);
44
+
45
+ /**
46
+ * @license lucide-react v0.577.0 - ISC
47
+ *
48
+ * This source code is licensed under the ISC license.
49
+ * See the LICENSE file in the root directory of this source tree.
50
+ */
51
+
52
+
53
+ const __iconNode = [
54
+ ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
55
+ ["path", { d: "M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20", key: "13o1zl" }],
56
+ ["path", { d: "M2 12h20", key: "9i4pu4" }]
57
+ ];
58
+ const Globe = createLucideIcon.createLucideIcon("globe", __iconNode);
40
59
 
41
60
  const DEFAULT_DATE_FORMAT = "dd/MM/yyyy";
42
- const DEFAULT_TIME_FORMAT = "HH:mm:ss";
61
+ const DEFAULT_TIME_FORMAT = "hh:mm A";
62
+ const CALENDAR_NAVIGATION_YEAR_RANGE = 100;
43
63
  const INITIAL_TIME_VALUE = {
44
64
  hours: 0,
45
65
  minutes: 0,
@@ -109,6 +129,33 @@ const getInitialCalendarMonth = (value, defaultValue, type) => {
109
129
  };
110
130
  const normalizeToDayjsFormat = (fmt) => fmt.replace(/\byyyy\b/g, "YYYY").replace(/\byy\b/g, "YY").replace(/\bdd\b/g, "DD").replace(/\bd\b/g, "D");
111
131
  const normalizeToDateFnsFormat = (fmt) => fmt.replace(/YYYY/g, "yyyy").replace(/YY/g, "yy").replace(/DD/g, "dd").replace(/\bD\b/g, "d");
132
+ const roundToMinuteStep = (date, step) => {
133
+ const rounded = new Date(date);
134
+ const minutes = Math.min(
135
+ 60 - step,
136
+ Math.round(date.getMinutes() / step) * step
137
+ );
138
+ rounded.setMinutes(minutes, 0, 0);
139
+ return rounded;
140
+ };
141
+ const getTimezoneLabel = (timezone) => {
142
+ const zone = timezone === "utc" ? "UTC" : utils.dayjs().$x?.$timezone ?? Intl.DateTimeFormat().resolvedOptions().timeZone;
143
+ try {
144
+ const zoneName = (timeZoneName) => new Intl.DateTimeFormat("en-US", { timeZone: zone, timeZoneName }).formatToParts(/* @__PURE__ */ new Date()).find((part) => part.type === "timeZoneName")?.value;
145
+ const name = zoneName("long") ?? zone;
146
+ const offset = (zoneName("longOffset") ?? "GMT").replace("GMT", "UTC").replace(/([+-])0?(\d+):00$/, "$1$2").replace(/([+-])0(\d)/, "$1$2");
147
+ return `${name} (${offset === "UTC" ? "UTC+0" : offset})`;
148
+ } catch {
149
+ return zone;
150
+ }
151
+ };
152
+ const roundUpToMinuteStep = (date, step) => {
153
+ const rounded = new Date(date);
154
+ const hasSeconds = date.getSeconds() > 0 || date.getMilliseconds() > 0;
155
+ const minutes = date.getMinutes() + (hasSeconds ? 1 : 0);
156
+ rounded.setMinutes(Math.ceil(minutes / step) * step, 0, 0);
157
+ return rounded;
158
+ };
112
159
  const formatDate = (date, formatStr) => {
113
160
  if (!date) return "";
114
161
  try {
@@ -267,6 +314,7 @@ const DatePickerFooter = ({
267
314
  timezone,
268
315
  onTimezoneChange,
269
316
  nowDisabled,
317
+ showNow = true,
270
318
  onNow,
271
319
  onOk,
272
320
  okLabel = "OK",
@@ -281,7 +329,7 @@ const DatePickerFooter = ({
281
329
  className
282
330
  ),
283
331
  children: [
284
- /* @__PURE__ */ jsxRuntime.jsx(
332
+ showNow && /* @__PURE__ */ jsxRuntime.jsx(
285
333
  primitives_Button.Button,
286
334
  {
287
335
  variant: "ghost",
@@ -441,6 +489,443 @@ const DatePickerTrigger = React.forwardRef(
441
489
  );
442
490
  DatePickerTrigger.displayName = "DatePickerTrigger";
443
491
 
492
+ const SEGMENT_KEYS = ["hour", "minute", "period"];
493
+ const EMPTY_SEGMENT = { text: "", done: false, auto: false };
494
+ const segment = (text, done, auto = false) => ({
495
+ text,
496
+ done,
497
+ auto
498
+ });
499
+ const clampIndex = (index) => Math.max(0, Math.min(2, index));
500
+ const withSegment = (state, key, value) => ({ ...state, [key]: value });
501
+ const snapMinute = (minutes, step = 1) => utils$2.padNumber(Math.min(60 - step, Math.round(Number(minutes) / step) * step));
502
+ const emptySegmentedTime = () => ({
503
+ hour: EMPTY_SEGMENT,
504
+ minute: EMPTY_SEGMENT,
505
+ period: EMPTY_SEGMENT,
506
+ active: 0
507
+ });
508
+ const segmentedTimeFromValue = (value, step = 1) => {
509
+ const { hour12, period } = utils$2.to12Hour(value.hours);
510
+ return {
511
+ hour: segment(String(hour12), true),
512
+ minute: segment(snapMinute(value.minutes, step), true),
513
+ period: segment(period, true),
514
+ active: 0
515
+ };
516
+ };
517
+ const isSegmentedTimeEmpty = (state) => SEGMENT_KEYS.every((key) => state[key].text === "");
518
+ const getSegmentedTimeError = (state) => {
519
+ if (isSegmentedTimeEmpty(state)) return "timeRequired";
520
+ if (!state.hour.text) return "hourRequired";
521
+ if (!state.minute.text) return "minuteRequired";
522
+ if (!state.period.text) return "periodRequired";
523
+ return null;
524
+ };
525
+ const segmentedTimeToValue = (state) => {
526
+ if (getSegmentedTimeError(state)) return null;
527
+ return {
528
+ hours: utils$2.to24Hour(Number(state.hour.text), state.period.text),
529
+ minutes: Number(state.minute.text),
530
+ seconds: 0
531
+ };
532
+ };
533
+ const finishSegment = (state, key, step) => {
534
+ const { text } = state[key];
535
+ if (!text) return state;
536
+ if (key === "hour" && text === "0") {
537
+ return withSegment(state, "hour", EMPTY_SEGMENT);
538
+ }
539
+ if (key === "minute" && text.length === 1) {
540
+ return withSegment(
541
+ state,
542
+ "minute",
543
+ segment(snapMinute(text, step), true, true)
544
+ );
545
+ }
546
+ return withSegment(state, key, { ...state[key], done: true });
547
+ };
548
+ const finishActiveSegment = (state, step = 1) => finishSegment(state, SEGMENT_KEYS[state.active], step);
549
+ const focusSegment = (state, index, step = 1) => {
550
+ const next = clampIndex(index);
551
+ const finished = next > state.active ? finishActiveSegment(state, step) : state;
552
+ return { ...finished, active: next };
553
+ };
554
+ const focusFirstEmptySegment = (state) => {
555
+ const index = SEGMENT_KEYS.findIndex((key) => !state[key].text);
556
+ return { ...state, active: index === -1 ? 2 : index };
557
+ };
558
+ const nextSegment = (state, step = 1) => state[SEGMENT_KEYS[state.active]].text ? focusSegment(state, state.active + 1, step) : state;
559
+ const previousSegment = (state) => focusSegment(state, state.active - 1);
560
+ const typeDigit = (state, digit, step = 1) => {
561
+ const advance = (next, index) => focusSegment(next, index, step);
562
+ const key = SEGMENT_KEYS[state.active];
563
+ if (key === "period") return state;
564
+ const current = state[key];
565
+ const text = current.done ? "" : current.text;
566
+ if (key === "hour") {
567
+ if (text === "") {
568
+ if (digit === "0" || digit === "1") {
569
+ return withSegment(state, "hour", segment(digit, false));
570
+ }
571
+ return advance(withSegment(state, "hour", segment(digit, true)), 1);
572
+ }
573
+ if (text === "1") {
574
+ if ("012".includes(digit)) {
575
+ return advance(
576
+ withSegment(state, "hour", segment(`1${digit}`, true)),
577
+ 1
578
+ );
579
+ }
580
+ const moved = advance(withSegment(state, "hour", segment("1", true)), 1);
581
+ return typeDigit(moved, digit, step);
582
+ }
583
+ if (digit === "0") return state;
584
+ return advance(withSegment(state, "hour", segment(digit, true)), 1);
585
+ }
586
+ if (text === "") {
587
+ if (digit >= "6") {
588
+ return advance(
589
+ withSegment(
590
+ state,
591
+ "minute",
592
+ segment(snapMinute(digit, step), true, true)
593
+ ),
594
+ 2
595
+ );
596
+ }
597
+ return withSegment(state, "minute", segment(digit, false));
598
+ }
599
+ return advance(
600
+ withSegment(state, "minute", segment(snapMinute(text + digit, step), true)),
601
+ 2
602
+ );
603
+ };
604
+ const typePeriod = (state, letter, step = 1) => withSegment(
605
+ focusSegment(state, 2, step),
606
+ "period",
607
+ segment(letter === "a" ? "AM" : "PM", true)
608
+ );
609
+ const togglePeriod = (state) => withSegment(
610
+ state,
611
+ "period",
612
+ segment(state.period.text === "AM" ? "PM" : "AM", true)
613
+ );
614
+ const stepSegment = (state, direction, step = 1) => {
615
+ const key = SEGMENT_KEYS[state.active];
616
+ if (key === "hour") {
617
+ const hour = Number(state.hour.text) || (direction > 0 ? 0 : 1);
618
+ const next = (hour - 1 + direction + 12) % 12 + 1;
619
+ return withSegment(state, "hour", segment(String(next), true));
620
+ }
621
+ if (key === "minute") {
622
+ const minute = state.minute.text === "" ? direction > 0 ? -step : 0 : Number(state.minute.text);
623
+ const stepped = direction > 0 ? Math.floor(minute / step) * step + step : Math.ceil(minute / step) * step - step;
624
+ return withSegment(
625
+ state,
626
+ "minute",
627
+ segment(utils$2.padNumber((stepped + 60) % 60), true)
628
+ );
629
+ }
630
+ return togglePeriod(state);
631
+ };
632
+ const backspace = (state) => {
633
+ const moveBack = state[SEGMENT_KEYS[state.active]].text === "" && state.active > 0;
634
+ const active = moveBack ? clampIndex(state.active - 1) : state.active;
635
+ const key = SEGMENT_KEYS[active];
636
+ const current = state[key];
637
+ const text = key === "period" || current.auto ? "" : current.text.slice(0, -1);
638
+ return { ...withSegment(state, key, segment(text, false)), active };
639
+ };
640
+ const parsePastedTime = (text, step = 1) => {
641
+ const match = text.trim().toLowerCase().match(/^(\d{1,2}):?(\d{2})?\s*([ap])?\.?m?\.?$/);
642
+ if (!match) return null;
643
+ let hours = Number(match[1]);
644
+ const minutes = Number(match[2] ?? 0);
645
+ const period = match[3];
646
+ if (minutes > 59) return null;
647
+ if (period) {
648
+ if (hours < 1 || hours > 12) return null;
649
+ hours = hours % 12 + (period === "p" ? 12 : 0);
650
+ } else if (hours > 23) {
651
+ return null;
652
+ }
653
+ return { hours, minutes: Number(snapMinute(minutes, step)), seconds: 0 };
654
+ };
655
+
656
+ const PLACEHOLDERS = ["hh", "mm", "--"];
657
+ const SegmentedTimeInput = ({
658
+ value,
659
+ minuteStep,
660
+ onChange,
661
+ onErrorChange,
662
+ invalid = false,
663
+ disabled = false,
664
+ autoFocus = false,
665
+ "aria-describedby": ariaDescribedBy
666
+ }) => {
667
+ const { t } = reactI18next.useTranslation();
668
+ const fieldRef = React.useRef(null);
669
+ const step = minuteStep ?? 1;
670
+ const [time, setTime] = React.useState(
671
+ () => segmentedTimeFromValue(value, step)
672
+ );
673
+ const lastEmittedRef = React.useRef(value);
674
+ React.useEffect(() => {
675
+ if (utils$2.timeValueEquals(value, lastEmittedRef.current)) return;
676
+ lastEmittedRef.current = value;
677
+ setTime(segmentedTimeFromValue(value, step));
678
+ }, [value]);
679
+ const error = getSegmentedTimeError(time);
680
+ React.useEffect(() => {
681
+ onErrorChange(error);
682
+ }, [error, onErrorChange]);
683
+ React.useEffect(() => {
684
+ const snapped = segmentedTimeToValue(time);
685
+ if (snapped && !utils$2.timeValueEquals(snapped, value)) {
686
+ lastEmittedRef.current = snapped;
687
+ onChange(snapped);
688
+ }
689
+ }, []);
690
+ React.useEffect(() => {
691
+ if (autoFocus) fieldRef.current?.focus();
692
+ }, [autoFocus]);
693
+ const update = (next) => {
694
+ setTime(next);
695
+ const nextValue = segmentedTimeToValue(next);
696
+ if (!nextValue || utils$2.timeValueEquals(nextValue, lastEmittedRef.current)) {
697
+ return;
698
+ }
699
+ lastEmittedRef.current = nextValue;
700
+ onChange(nextValue);
701
+ };
702
+ const consume = (event) => {
703
+ event.preventDefault();
704
+ event.stopPropagation();
705
+ };
706
+ const handleKeyDown = (event) => {
707
+ const { key } = event;
708
+ if (key === "Tab") {
709
+ if (!event.shiftKey || time.active !== 2) return;
710
+ consume(event);
711
+ update(togglePeriod(time));
712
+ return;
713
+ }
714
+ if ((event.metaKey || event.ctrlKey) && key === "Backspace") {
715
+ consume(event);
716
+ update(emptySegmentedTime());
717
+ return;
718
+ }
719
+ if (event.metaKey || event.ctrlKey || event.altKey) return;
720
+ let next = null;
721
+ if (/^[0-9]$/.test(key)) next = typeDigit(time, key, step);
722
+ else if (/^[ap]$/i.test(key)) {
723
+ next = typePeriod(time, key.toLowerCase(), step);
724
+ } else if (/^m$/i.test(key))
725
+ next = time;
726
+ else if (key === ":" || key === " " || key === "ArrowRight") {
727
+ next = nextSegment(time, step);
728
+ } else if (key === "ArrowLeft") next = previousSegment(time);
729
+ else if (key === "ArrowUp") next = stepSegment(time, 1, step);
730
+ else if (key === "ArrowDown") next = stepSegment(time, -1, step);
731
+ else if (key === "Backspace" || key === "Delete") next = backspace(time);
732
+ if (!next) return;
733
+ consume(event);
734
+ update(next);
735
+ };
736
+ const handlePaste = (event) => {
737
+ event.preventDefault();
738
+ const pasted = parsePastedTime(event.clipboardData.getData("text"), step);
739
+ if (pasted) update({ ...segmentedTimeFromValue(pasted, step), active: 2 });
740
+ };
741
+ const handleMouseDown = (event) => {
742
+ if (disabled) return;
743
+ const segmentEl = event.target.closest(
744
+ "[data-segment]"
745
+ );
746
+ if (segmentEl) {
747
+ event.preventDefault();
748
+ fieldRef.current?.focus();
749
+ update(focusSegment(time, Number(segmentEl.dataset.segment), step));
750
+ return;
751
+ }
752
+ update(focusFirstEmptySegment(time));
753
+ };
754
+ const segmentText = (index) => {
755
+ const { text, done } = time[SEGMENT_KEYS[index]];
756
+ if (!text) return PLACEHOLDERS[index];
757
+ return index < 2 && !done ? `${text}_` : text;
758
+ };
759
+ return /* @__PURE__ */ jsxRuntime.jsx(
760
+ "div",
761
+ {
762
+ ref: fieldRef,
763
+ role: "group",
764
+ tabIndex: disabled ? -1 : 0,
765
+ "aria-label": t("neetoatoms.datePicker.timeField", "Time"),
766
+ "aria-describedby": ariaDescribedBy,
767
+ "aria-invalid": invalid || void 0,
768
+ "aria-disabled": disabled || void 0,
769
+ "data-testid": "datepicker-time-input",
770
+ className: utils$1.cn(
771
+ "group/time flex h-9 w-full cursor-text items-center rounded-md border border-input bg-transparent px-3 text-sm tabular-nums outline-none select-none",
772
+ "focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
773
+ invalid && "border-destructive",
774
+ disabled && "pointer-events-none cursor-not-allowed opacity-50"
775
+ ),
776
+ onBlur: () => update(finishActiveSegment(time, step)),
777
+ onKeyDown: handleKeyDown,
778
+ onMouseDown: handleMouseDown,
779
+ onPaste: handlePaste,
780
+ children: SEGMENT_KEYS.map((key, index) => /* @__PURE__ */ jsxRuntime.jsxs(React__default.default.Fragment, { children: [
781
+ index === 1 && /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: "px-0.5 text-muted-foreground", children: ":" }),
782
+ index === 2 && /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: "w-1" }),
783
+ /* @__PURE__ */ jsxRuntime.jsx(
784
+ "span",
785
+ {
786
+ "data-segment": index,
787
+ "data-testid": `datepicker-time-${key}`,
788
+ className: utils$1.cn(
789
+ "min-w-[2ch] px-0.5 text-center whitespace-pre",
790
+ !time[key].text && "text-muted-foreground",
791
+ index === time.active && "group-focus/time:shadow-[inset_0_-1.5px_0_var(--color-muted-foreground)]"
792
+ ),
793
+ children: segmentText(index)
794
+ }
795
+ )
796
+ ] }, key))
797
+ }
798
+ );
799
+ };
800
+ SegmentedTimeInput.displayName = "SegmentedTimeInput";
801
+
802
+ const ERROR_DEFAULTS = {
803
+ timeRequired: "Enter a time, or turn off Add time.",
804
+ hourRequired: "Enter an hour.",
805
+ minuteRequired: "Enter minutes.",
806
+ periodRequired: "Type a or p for AM or PM."
807
+ };
808
+ const stepExamples = (step) => {
809
+ const pad = (minutes) => `1:${String(minutes).padStart(2, "0")}`;
810
+ if (step * 2 >= 60) return [pad(0), pad(step)];
811
+ return [pad(step), pad(30 % step === 0 && step < 30 ? 30 : step * 2)];
812
+ };
813
+ const TimeSection = ({
814
+ value,
815
+ minuteStep,
816
+ onChange,
817
+ onErrorChange,
818
+ showError,
819
+ optional,
820
+ hasTime,
821
+ onHasTimeChange,
822
+ timezoneLabel,
823
+ disabled = false,
824
+ className
825
+ }) => {
826
+ const { t } = reactI18next.useTranslation();
827
+ const id = React.useId();
828
+ const switchId = `datepicker-add-time-${id}`;
829
+ const hintId = `datepicker-time-hint-${id}`;
830
+ const errorId = `datepicker-time-error-${id}`;
831
+ const [error, setError] = React.useState(null);
832
+ const [focusOnMount, setFocusOnMount] = React.useState(false);
833
+ const handleErrorChange = React.useCallback(
834
+ (nextError) => {
835
+ setError(nextError);
836
+ onErrorChange(!!nextError);
837
+ },
838
+ [onErrorChange]
839
+ );
840
+ const handleHasTimeChange = (checked) => {
841
+ setFocusOnMount(checked);
842
+ if (!checked) {
843
+ setError(null);
844
+ onErrorChange(false);
845
+ }
846
+ onHasTimeChange(checked);
847
+ };
848
+ const isTimeOn = !optional || hasTime;
849
+ const showHint = (minuteStep ?? 1) > 1;
850
+ const errorMessage = isTimeOn && showError && error ? t(`neetoatoms.datePicker.errors.${error}`, ERROR_DEFAULTS[error]) : null;
851
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: utils$1.cn("flex flex-col gap-2", className), children: [
852
+ optional && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3", children: [
853
+ /* @__PURE__ */ jsxRuntime.jsxs(
854
+ primitives_Label.Label,
855
+ {
856
+ htmlFor: switchId,
857
+ className: "flex items-center gap-2 font-medium",
858
+ children: [
859
+ /* @__PURE__ */ jsxRuntime.jsx(utils$2.Clock, { "aria-hidden": true, className: "size-4 text-muted-foreground" }),
860
+ t("neetoatoms.datePicker.addTime", "Add time")
861
+ ]
862
+ }
863
+ ),
864
+ /* @__PURE__ */ jsxRuntime.jsx(
865
+ primitives_Switch.Switch,
866
+ {
867
+ id: switchId,
868
+ checked: hasTime,
869
+ "data-testid": "datepicker-add-time-switch",
870
+ disabled,
871
+ onCheckedChange: handleHasTimeChange
872
+ }
873
+ )
874
+ ] }),
875
+ isTimeOn && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1.5", children: [
876
+ /* @__PURE__ */ jsxRuntime.jsx(
877
+ SegmentedTimeInput,
878
+ {
879
+ "aria-describedby": [showHint ? hintId : null, errorMessage ? errorId : null].filter(Boolean).join(" ") || void 0,
880
+ autoFocus: focusOnMount,
881
+ disabled,
882
+ invalid: !!errorMessage,
883
+ minuteStep,
884
+ value,
885
+ onChange,
886
+ onErrorChange: handleErrorChange
887
+ }
888
+ ),
889
+ showHint && /* @__PURE__ */ jsxRuntime.jsx(
890
+ "p",
891
+ {
892
+ id: hintId,
893
+ className: "text-xs text-muted-foreground",
894
+ "data-testid": "datepicker-time-hint",
895
+ children: t("neetoatoms.datePicker.timeHint", {
896
+ step: minuteStep,
897
+ first: stepExamples(minuteStep)[0],
898
+ second: stepExamples(minuteStep)[1],
899
+ defaultValue: "Only {{step}}-minute steps are allowed, like {{first}} or {{second}}."
900
+ })
901
+ }
902
+ ),
903
+ errorMessage && /* @__PURE__ */ jsxRuntime.jsx(
904
+ "p",
905
+ {
906
+ id: errorId,
907
+ className: "text-xs text-destructive",
908
+ "data-testid": "datepicker-time-error",
909
+ role: "alert",
910
+ children: errorMessage
911
+ }
912
+ ),
913
+ timezoneLabel && /* @__PURE__ */ jsxRuntime.jsxs(
914
+ "p",
915
+ {
916
+ className: "mt-1 flex items-center gap-2 text-xs text-muted-foreground",
917
+ "data-testid": "datepicker-timezone",
918
+ children: [
919
+ /* @__PURE__ */ jsxRuntime.jsx(Globe, { "aria-hidden": true, className: "size-3.5 shrink-0" }),
920
+ timezoneLabel
921
+ ]
922
+ }
923
+ )
924
+ ] })
925
+ ] });
926
+ };
927
+ TimeSection.displayName = "TimeSection";
928
+
444
929
  const MonthPicker = ({
445
930
  value,
446
931
  displayedYear,
@@ -509,11 +994,12 @@ const RangeCalendar = ({
509
994
  selected,
510
995
  onSelect,
511
996
  disabled,
512
- fromDate,
513
- toDate,
997
+ startMonth,
998
+ endMonth,
514
999
  onHoverIn,
515
1000
  onHoverOut,
516
- numberOfMonths = 2
1001
+ numberOfMonths = 2,
1002
+ className
517
1003
  }) => /* @__PURE__ */ jsxRuntime.jsx("div", { onMouseOver: onHoverIn, onMouseLeave: onHoverOut, children: /* @__PURE__ */ jsxRuntime.jsx(
518
1004
  primitives_Calendar.Calendar,
519
1005
  {
@@ -522,14 +1008,17 @@ const RangeCalendar = ({
522
1008
  captionLayout: "dropdown",
523
1009
  today: todayDate,
524
1010
  weekStartsOn,
525
- className: "[&_[data-outside][data-selected=true]]:!bg-transparent [&_[data-outside][data-selected=true]]:after:!bg-transparent [&_[data-outside]_button]:!bg-transparent [&_[data-outside]_button]:!text-muted-foreground",
1011
+ className: utils$1.cn(
1012
+ "[&_[data-outside][data-selected=true]]:!bg-transparent [&_[data-outside][data-selected=true]]:after:!bg-transparent [&_[data-outside]_button]:!bg-transparent [&_[data-outside]_button]:!text-muted-foreground",
1013
+ className
1014
+ ),
526
1015
  month,
527
1016
  onMonthChange,
528
1017
  selected,
529
1018
  onSelect,
530
1019
  disabled,
531
- ...fromDate && { fromDate },
532
- ...toDate && { toDate }
1020
+ startMonth,
1021
+ endMonth
533
1022
  }
534
1023
  ) });
535
1024
 
@@ -696,6 +1185,11 @@ const YearPicker = ({
696
1185
  ] });
697
1186
  };
698
1187
 
1188
+ const TIME_SECTION_CLASS_NAME = "w-0 min-w-full border-t border-border px-3 py-3";
1189
+ const SHOW_TIME_CALENDAR_CLASS_NAME = "[--cell-size:--spacing(8.125)]";
1190
+ const DEFAULT_ADDED_TIME = { hours: 12, minutes: 0, seconds: 0 };
1191
+ const noop = () => {
1192
+ };
699
1193
  const DatePicker = React.forwardRef(
700
1194
  ({
701
1195
  value,
@@ -706,8 +1200,10 @@ const DatePicker = React.forwardRef(
706
1200
  dateFormat = DEFAULT_DATE_FORMAT,
707
1201
  timeFormat = DEFAULT_TIME_FORMAT,
708
1202
  showTime = false,
709
- timePickerFormat,
710
- showSeconds = false,
1203
+ showNow = true,
1204
+ minuteStep,
1205
+ timeOptional = true,
1206
+ hasTime: hasTimeProp,
711
1207
  minDate,
712
1208
  maxDate,
713
1209
  disabledDate,
@@ -720,6 +1216,7 @@ const DatePicker = React.forwardRef(
720
1216
  allowClear = true,
721
1217
  required = false,
722
1218
  timezone,
1219
+ timezoneLabel: timezoneLabelProp,
723
1220
  onTimezoneChange,
724
1221
  onOk,
725
1222
  needConfirm = false,
@@ -744,7 +1241,7 @@ const DatePicker = React.forwardRef(
744
1241
  const popoverContentId = React.useRef(
745
1242
  `datepicker-popover-${generatedId}`
746
1243
  ).current;
747
- const { open, setOpen } = TimePickerPanel.useControlledOpen(openProp, onOpenChange);
1244
+ const { open, setOpen } = utils$2.useControlledOpen(openProp, onOpenChange);
748
1245
  const [internalValue, setInternalValue] = React.useState(
749
1246
  coerceDateValue(defaultValue, type) ?? null
750
1247
  );
@@ -754,6 +1251,15 @@ const DatePicker = React.forwardRef(
754
1251
  const [pendingDate, setPendingDate] = React.useState(null);
755
1252
  const [pendingTime, setPendingTime] = React.useState(INITIAL_TIME_VALUE);
756
1253
  const [pendingTimeTo, setPendingTimeTo] = React.useState(INITIAL_TIME_VALUE);
1254
+ const [hasTimeError, setHasTimeError] = React.useState(false);
1255
+ const [isTimeErrorRequested, setIsTimeErrorRequested] = React.useState(false);
1256
+ const isTimeOptional = showTime && timeOptional && type === "date";
1257
+ const [internalHasTime, setInternalHasTime] = React.useState(
1258
+ () => hasTimeProp ?? (value ?? defaultValue) != null
1259
+ );
1260
+ const committedHasTime = showTime && (!isTimeOptional || (hasTimeProp ?? internalHasTime));
1261
+ const [pendingHasTime, setPendingHasTime] = React.useState(committedHasTime);
1262
+ const isPendingTimeOn = showTime && (!isTimeOptional || pendingHasTime);
757
1263
  const currentValue = React.useMemo(
758
1264
  () => value !== void 0 ? coerceDateValue(value, type) : internalValue,
759
1265
  [value, type, internalValue]
@@ -766,10 +1272,13 @@ const DatePicker = React.forwardRef(
766
1272
  () => toNativeDate(maxDate) ?? void 0,
767
1273
  [maxDate]
768
1274
  );
769
- const displayFormat = getDisplayFormat(dateFormat, timeFormat, showTime);
1275
+ const displayFormat = getDisplayFormat(
1276
+ dateFormat,
1277
+ timeFormat,
1278
+ committedHasTime
1279
+ );
770
1280
  const sizeConfig = SIZE_CONFIG[size];
771
- const maskEnabled = TimePickerPanel.isFixedWidthFormat(displayFormat);
772
- const effectiveTimePickerFormat = timePickerFormat ?? (/\ba\b/i.test(timeFormat) ? "12" : "24");
1281
+ const maskEnabled = utils$2.isFixedWidthFormat(displayFormat);
773
1282
  const defaultPlaceholder = getDatePlaceholder(
774
1283
  dateFormat,
775
1284
  timeFormat,
@@ -777,11 +1286,11 @@ const DatePicker = React.forwardRef(
777
1286
  type
778
1287
  );
779
1288
  const maskTemplate = React.useMemo(
780
- () => maskEnabled ? type === "range" ? TimePickerPanel.buildRangeMaskTemplate(displayFormat) : TimePickerPanel.buildMaskTemplate(displayFormat) : null,
1289
+ () => maskEnabled ? type === "range" ? utils$2.buildRangeMaskTemplate(displayFormat) : utils$2.buildMaskTemplate(displayFormat) : null,
781
1290
  [displayFormat, type, maskEnabled]
782
1291
  );
783
1292
  const singleDateLen = React.useMemo(
784
- () => TimePickerPanel.buildMaskTemplate(displayFormat).pattern.length,
1293
+ () => utils$2.buildMaskTemplate(displayFormat).pattern.length,
785
1294
  [displayFormat]
786
1295
  );
787
1296
  const calendarDisabled = React.useCallback(
@@ -806,38 +1315,46 @@ const DatePicker = React.forwardRef(
806
1315
  return `${from} - ${to}`;
807
1316
  }, [currentValue, displayFormat, type]);
808
1317
  const [inputText, setInputText] = React.useState(() => getDisplayText());
809
- const commitValue = (date) => {
1318
+ const emitChange = (changed, formatted, withTime) => {
1319
+ if (isTimeOptional) onChange?.(changed, formatted, { hasTime: withTime });
1320
+ else onChange?.(changed, formatted);
1321
+ };
1322
+ const commitValue = (date, withTime = committedHasTime) => {
810
1323
  if (date === null) {
811
1324
  setInternalValue(null);
1325
+ setInternalHasTime(false);
812
1326
  if (type === "range") {
813
- onChange?.([null, null], ["", ""]);
1327
+ emitChange([null, null], ["", ""], false);
814
1328
  } else {
815
- onChange?.(null, "");
1329
+ emitChange(null, "", false);
816
1330
  }
817
1331
  setInputText("");
818
1332
  return;
819
1333
  }
1334
+ const commitFormat = getDisplayFormat(dateFormat, timeFormat, withTime);
820
1335
  const toSerializableDayjs = (d) => {
821
1336
  const userTzDayjs = toUserTzDayjs(d);
822
- if (showTime) return userTzDayjs;
1337
+ if (withTime) return userTzDayjs;
823
1338
  return userTzDayjs.hour(12).minute(0).second(0).millisecond(0);
824
1339
  };
825
1340
  if (Array.isArray(date)) {
826
1341
  setInternalValue(date);
827
1342
  const formatted2 = [
828
- formatDate(date[0], displayFormat),
829
- formatDate(date[1], displayFormat)
1343
+ formatDate(date[0], commitFormat),
1344
+ formatDate(date[1], commitFormat)
830
1345
  ];
831
- onChange?.(
1346
+ emitChange(
832
1347
  [toSerializableDayjs(date[0]), toSerializableDayjs(date[1])],
833
- formatted2
1348
+ formatted2,
1349
+ withTime
834
1350
  );
835
1351
  setInputText(`${formatted2[0]} - ${formatted2[1]}`);
836
1352
  return;
837
1353
  }
838
1354
  setInternalValue(date);
839
- const formatted = formatDate(date, displayFormat);
840
- onChange?.(toSerializableDayjs(date), formatted);
1355
+ setInternalHasTime(withTime);
1356
+ const formatted = formatDate(date, commitFormat);
1357
+ emitChange(toSerializableDayjs(date), formatted, withTime);
841
1358
  setInputText(formatted);
842
1359
  };
843
1360
  const {
@@ -897,30 +1414,33 @@ const DatePicker = React.forwardRef(
897
1414
  };
898
1415
  const commitPendingOnClose = React.useCallback(() => {
899
1416
  const hasOkButton = showTime || needConfirm;
900
- if (!hasOkButton || !pendingDate) {
1417
+ if (!hasOkButton || !pendingDate || isPendingTimeOn && hasTimeError) {
901
1418
  setInputText(getDisplayText());
902
1419
  return;
903
1420
  }
904
- const finalDate = showTime ? applyTimeToDate(pendingDate, pendingTime) : pendingDate;
1421
+ const finalDate = isPendingTimeOn ? applyTimeToDate(pendingDate, pendingTime) : pendingDate;
905
1422
  const currentSingle = type === "date" ? currentValue : null;
906
- const isSame = currentSingle && finalDate.getTime() === currentSingle.getTime();
1423
+ const isSame = currentSingle && isPendingTimeOn === committedHasTime && finalDate.getTime() === currentSingle.getTime();
907
1424
  if (isSame) {
908
1425
  setInputText(getDisplayText());
909
1426
  return;
910
1427
  }
911
- commitValue(finalDate);
1428
+ commitValue(finalDate, isPendingTimeOn);
912
1429
  }, [
913
1430
  showTime,
914
1431
  needConfirm,
915
1432
  pendingDate,
916
1433
  pendingTime,
1434
+ isPendingTimeOn,
1435
+ committedHasTime,
917
1436
  type,
918
1437
  currentValue,
919
1438
  getDisplayText,
920
- onChange
1439
+ onChange,
1440
+ hasTimeError
921
1441
  ]);
922
1442
  const closePopover = React.useCallback(() => {
923
- if (type === "range" && pendingRange?.[0] && pendingRange?.[1]) {
1443
+ if (type === "range" && pendingRange?.[0] && pendingRange?.[1] && !(showTime && hasTimeError)) {
924
1444
  const [fromBase, toBase] = orderRange(
925
1445
  pendingRange[0],
926
1446
  pendingRange[1]
@@ -941,9 +1461,10 @@ const DatePicker = React.forwardRef(
941
1461
  pendingTime,
942
1462
  pendingTimeTo,
943
1463
  showTime,
944
- resetTransientState
1464
+ resetTransientState,
1465
+ hasTimeError
945
1466
  ]);
946
- TimePickerPanel.useOutsideClickClose({
1467
+ utils$2.useOutsideClickClose({
947
1468
  enabled: open,
948
1469
  containerRef,
949
1470
  popoverElementId: popoverContentId,
@@ -952,26 +1473,32 @@ const DatePicker = React.forwardRef(
952
1473
  const openPopover = () => {
953
1474
  setOpen(true);
954
1475
  resetTransientState();
1476
+ setIsTimeErrorRequested(false);
955
1477
  if (type === "range") {
956
1478
  const rangeVal = currentValue;
957
1479
  const fromDate = rangeVal?.[0] ?? null;
958
1480
  const toDate = rangeVal?.[1] ?? null;
959
1481
  setPendingDate(null);
960
1482
  setPendingTime(
961
- fromDate ? TimePickerPanel.dateToTimeValue(fromDate) : INITIAL_TIME_VALUE
1483
+ fromDate ? utils$2.dateToTimeValue(fromDate) : INITIAL_TIME_VALUE
962
1484
  );
963
- setPendingTimeTo(toDate ? TimePickerPanel.dateToTimeValue(toDate) : INITIAL_TIME_VALUE);
1485
+ setPendingTimeTo(toDate ? utils$2.dateToTimeValue(toDate) : INITIAL_TIME_VALUE);
964
1486
  return;
965
1487
  }
966
1488
  const dateVal = currentValue;
1489
+ setPendingHasTime(committedHasTime);
967
1490
  if (!dateVal) {
968
1491
  setPendingDate(null);
969
- setPendingTime(INITIAL_TIME_VALUE);
1492
+ setPendingTime(
1493
+ isTimeOptional ? DEFAULT_ADDED_TIME : INITIAL_TIME_VALUE
1494
+ );
970
1495
  return;
971
1496
  }
972
1497
  setCalendarMonth(dateVal);
973
1498
  setPendingDate(dateVal);
974
- setPendingTime(TimePickerPanel.dateToTimeValue(dateVal));
1499
+ setPendingTime(
1500
+ committedHasTime || !isTimeOptional ? utils$2.dateToTimeValue(dateVal) : DEFAULT_ADDED_TIME
1501
+ );
975
1502
  };
976
1503
  const handleDateSelect = (selected) => {
977
1504
  if (!selected) return;
@@ -984,13 +1511,15 @@ const DatePicker = React.forwardRef(
984
1511
  setOpen(false);
985
1512
  };
986
1513
  const handleNow = () => {
987
- const now = toBrowserLocalDate(/* @__PURE__ */ new Date());
1514
+ const now = toBrowserLocalDate(
1515
+ showTime && minuteStep ? roundUpToMinuteStep(/* @__PURE__ */ new Date(), minuteStep) : /* @__PURE__ */ new Date()
1516
+ );
988
1517
  if (calendarDisabled(now)) return;
989
1518
  setCalendarMonth(now);
990
1519
  if (type === "range") {
991
1520
  handleRangeSelect(void 0, now);
992
1521
  if (showTime) {
993
- const nowTime = TimePickerPanel.dateToTimeValue(now);
1522
+ const nowTime = utils$2.dateToTimeValue(now);
994
1523
  if (rangeSelectionStep === "from") setPendingTime(nowTime);
995
1524
  else setPendingTimeTo(nowTime);
996
1525
  }
@@ -998,13 +1527,18 @@ const DatePicker = React.forwardRef(
998
1527
  }
999
1528
  if (showTime || needConfirm) {
1000
1529
  setPendingDate(now);
1001
- setPendingTime(TimePickerPanel.dateToTimeValue(now));
1530
+ setPendingTime(utils$2.dateToTimeValue(now));
1531
+ if (isTimeOptional) setPendingHasTime(true);
1002
1532
  return;
1003
1533
  }
1004
1534
  commitValue(now);
1005
1535
  setOpen(false);
1006
1536
  };
1007
1537
  const handleOk = () => {
1538
+ if (isPendingTimeOn && hasTimeError) {
1539
+ setIsTimeErrorRequested(true);
1540
+ return;
1541
+ }
1008
1542
  if (type === "range" && (showTime || needConfirm)) {
1009
1543
  const committedRange = currentValue;
1010
1544
  const fromValue = pendingRange?.[0] ?? committedRange?.[0] ?? null;
@@ -1023,7 +1557,7 @@ const DatePicker = React.forwardRef(
1023
1557
  const [fromBase, toBase] = orderRange(fromValue, toValue);
1024
1558
  const fromFinal = showTime ? applyTimeToDate(fromBase, pendingTime) : fromBase;
1025
1559
  const toFinal = showTime ? applyTimeToDate(toBase, pendingTimeTo) : toBase;
1026
- commitValue([fromFinal, toFinal]);
1560
+ commitValue([fromFinal, toFinal], showTime);
1027
1561
  onOk?.([fromFinal, toFinal]);
1028
1562
  resetTransientState();
1029
1563
  setOpen(false);
@@ -1032,8 +1566,8 @@ const DatePicker = React.forwardRef(
1032
1566
  return;
1033
1567
  }
1034
1568
  if (pendingDate) {
1035
- const finalDate = showTime ? applyTimeToDate(pendingDate, pendingTime) : pendingDate;
1036
- commitValue(finalDate);
1569
+ const finalDate = isPendingTimeOn ? applyTimeToDate(pendingDate, pendingTime) : pendingDate;
1570
+ commitValue(finalDate, isPendingTimeOn);
1037
1571
  onOk?.(finalDate);
1038
1572
  }
1039
1573
  setOpen(false);
@@ -1046,7 +1580,7 @@ const DatePicker = React.forwardRef(
1046
1580
  const handleInputChange = (e) => {
1047
1581
  let text = e.target.value;
1048
1582
  if (maskTemplate) {
1049
- const masked = TimePickerPanel.applyMask(
1583
+ const masked = utils$2.applyMask(
1050
1584
  text,
1051
1585
  maskTemplate,
1052
1586
  inputText,
@@ -1067,7 +1601,7 @@ const DatePicker = React.forwardRef(
1067
1601
  setCalendarMonth(parsed);
1068
1602
  if (showTime || needConfirm) {
1069
1603
  setPendingDate(parsed);
1070
- setPendingTime(TimePickerPanel.dateToTimeValue(parsed));
1604
+ setPendingTime(utils$2.dateToTimeValue(parsed));
1071
1605
  return;
1072
1606
  }
1073
1607
  commitValue(parsed);
@@ -1088,10 +1622,21 @@ const DatePicker = React.forwardRef(
1088
1622
  maskEnabled,
1089
1623
  singleDateLen
1090
1624
  );
1091
- if (range) commitValue(range);
1625
+ if (range) {
1626
+ commitValue(
1627
+ showTime && minuteStep ? [
1628
+ roundToMinuteStep(range[0], minuteStep),
1629
+ roundToMinuteStep(range[1], minuteStep)
1630
+ ] : range
1631
+ );
1632
+ }
1092
1633
  } else {
1093
1634
  const parsed = parseDate(inputText, displayFormat);
1094
- if (parsed) commitValue(parsed);
1635
+ if (parsed) {
1636
+ commitValue(
1637
+ committedHasTime && minuteStep ? roundToMinuteStep(parsed, minuteStep) : parsed
1638
+ );
1639
+ }
1095
1640
  }
1096
1641
  setOpen(false);
1097
1642
  };
@@ -1105,16 +1650,38 @@ const DatePicker = React.forwardRef(
1105
1650
  React.useEffect(() => {
1106
1651
  if (!open) setInputText(getDisplayText());
1107
1652
  }, [getDisplayText, open]);
1108
- TimePickerPanel.useCursorRestore([{ inputRef, cursorRef: cursorPosRef }]);
1653
+ utils$2.useCursorRestore([{ inputRef, cursorRef: cursorPosRef }]);
1109
1654
  React.useEffect(() => {
1110
1655
  if (!open || type !== "date") return;
1111
1656
  const dateVal = currentValue;
1112
1657
  if (dateVal instanceof Date) setCalendarMonth(dateVal);
1113
1658
  }, [open]);
1659
+ const timezoneLabel = showTime && !onTimezoneChange ? (timezoneLabelProp ?? getTimezoneLabel(timezone)) || null : null;
1114
1660
  const hasField = !!(label || error || helpText);
1115
1661
  const testIdPrefix = fieldIds.buildTestIdPrefix(dataTestId, label);
1116
1662
  const showFooter = showTime || needConfirm || !!onTimezoneChange;
1117
1663
  const todayDate = toBrowserLocalDate(/* @__PURE__ */ new Date());
1664
+ const handleHasTimeChange = (checked) => {
1665
+ setPendingHasTime(checked);
1666
+ setIsTimeErrorRequested(false);
1667
+ if (!checked) setHasTimeError(false);
1668
+ if (checked && !pendingDate && !calendarDisabled(todayDate)) {
1669
+ setPendingDate(todayDate);
1670
+ setCalendarMonth(todayDate);
1671
+ }
1672
+ };
1673
+ const defaultStartMonth = new Date(
1674
+ todayDate.getFullYear() - CALENDAR_NAVIGATION_YEAR_RANGE,
1675
+ 0,
1676
+ 1
1677
+ );
1678
+ const defaultEndMonth = new Date(
1679
+ todayDate.getFullYear() + CALENDAR_NAVIGATION_YEAR_RANGE,
1680
+ 11,
1681
+ 1
1682
+ );
1683
+ const calendarStartMonth = normalizedMinDate ?? (calendarMonth < defaultStartMonth ? calendarMonth : defaultStartMonth);
1684
+ const calendarEndMonth = normalizedMaxDate ?? (calendarMonth > defaultEndMonth ? calendarMonth : defaultEndMonth);
1118
1685
  const handleGridPickerSelect = (date) => {
1119
1686
  commitValue(date);
1120
1687
  setOpen(false);
@@ -1201,99 +1768,110 @@ const DatePicker = React.forwardRef(
1201
1768
  onPointerDownOutside: (e) => e.preventDefault(),
1202
1769
  onInteractOutside: (e) => e.preventDefault(),
1203
1770
  children: [
1204
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: utils$1.cn("flex", showTime && "flex-row"), children: [
1205
- type === "date" && picker === "month" ? /* @__PURE__ */ jsxRuntime.jsx(
1206
- MonthPicker,
1207
- {
1208
- value: currentValue ?? null,
1209
- displayedYear: calendarMonth.getFullYear(),
1210
- onYearChange: (year) => setCalendarMonth(
1211
- new Date(year, calendarMonth.getMonth(), 1)
1212
- ),
1213
- onSelect: handleGridPickerSelect,
1214
- isDisabled: (date) => isMonthDisabled(date, normalizedMinDate, normalizedMaxDate)
1215
- }
1216
- ) : type === "date" && picker === "year" ? /* @__PURE__ */ jsxRuntime.jsx(
1217
- YearPicker,
1218
- {
1219
- value: currentValue ?? null,
1220
- displayedYear: calendarMonth.getFullYear(),
1221
- onYearChange: (year) => setCalendarMonth(new Date(year, 0, 1)),
1222
- onSelect: handleGridPickerSelect,
1223
- isDisabled: (date) => isYearDisabled(date, normalizedMinDate, normalizedMaxDate)
1224
- }
1225
- ) : type === "date" ? /* @__PURE__ */ jsxRuntime.jsx(
1226
- primitives_Calendar.Calendar,
1227
- {
1228
- mode: "single",
1229
- captionLayout: "dropdown",
1230
- today: todayDate,
1231
- month: calendarMonth,
1232
- onMonthChange: setCalendarMonth,
1233
- selected: calendarSelected(),
1234
- onSelect: handleSingleDateSelectWithPicker,
1235
- disabled: calendarDisabled,
1236
- weekStartsOn,
1237
- classNames: weekRowHoverClassName ? { week: utils$1.cn("mt-2 flex w-full", weekRowHoverClassName) } : void 0,
1238
- ...weekHighlightProps,
1239
- ...normalizedMinDate && { fromDate: normalizedMinDate },
1240
- ...normalizedMaxDate && { toDate: normalizedMaxDate }
1241
- }
1242
- ) : showTime ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1243
- /* @__PURE__ */ jsxRuntime.jsx(
1244
- RangeCalendar,
1245
- {
1246
- todayDate,
1247
- weekStartsOn,
1248
- month: calendarMonth,
1249
- onMonthChange: setCalendarMonth,
1250
- selected: memoizedRangeSelected,
1251
- onSelect: handleRangeSelect,
1252
- disabled: calendarDisabled,
1253
- fromDate: normalizedMinDate,
1254
- toDate: normalizedMaxDate,
1255
- onHoverIn: handleHoverIn,
1256
- onHoverOut: handleHoverOut,
1257
- numberOfMonths: 1
1258
- }
1771
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex", children: type === "date" && picker === "month" ? /* @__PURE__ */ jsxRuntime.jsx(
1772
+ MonthPicker,
1773
+ {
1774
+ value: currentValue ?? null,
1775
+ displayedYear: calendarMonth.getFullYear(),
1776
+ onYearChange: (year) => setCalendarMonth(
1777
+ new Date(year, calendarMonth.getMonth(), 1)
1259
1778
  ),
1260
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-inline-start border-border pt-2", children: /* @__PURE__ */ jsxRuntime.jsx(
1261
- TimePickerPanel.TimePickerPanel,
1262
- {
1263
- value: rangeSelectionStep === "from" ? pendingTime : pendingTimeTo,
1264
- onChange: rangeSelectionStep === "from" ? setPendingTime : setPendingTimeTo,
1265
- format: effectiveTimePickerFormat,
1266
- showSeconds,
1267
- disabled
1268
- }
1269
- ) })
1270
- ] }) : /* @__PURE__ */ jsxRuntime.jsx(
1271
- RangeCalendar,
1272
- {
1273
- todayDate,
1274
- weekStartsOn,
1275
- month: calendarMonth,
1276
- onMonthChange: setCalendarMonth,
1277
- selected: memoizedRangeSelected,
1278
- onSelect: handleRangeSelect,
1279
- disabled: calendarDisabled,
1280
- fromDate: normalizedMinDate,
1281
- toDate: normalizedMaxDate,
1282
- onHoverIn: handleHoverIn,
1283
- onHoverOut: handleHoverOut
1284
- }
1285
- ),
1286
- showTime && type !== "range" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-inline-start border-border", children: /* @__PURE__ */ jsxRuntime.jsx(
1287
- TimePickerPanel.TimePickerPanel,
1288
- {
1289
- value: pendingTime,
1290
- onChange: setPendingTime,
1291
- format: effectiveTimePickerFormat,
1292
- showSeconds,
1293
- disabled
1294
- }
1295
- ) })
1296
- ] }),
1779
+ onSelect: handleGridPickerSelect,
1780
+ isDisabled: (date) => isMonthDisabled(date, normalizedMinDate, normalizedMaxDate)
1781
+ }
1782
+ ) : type === "date" && picker === "year" ? /* @__PURE__ */ jsxRuntime.jsx(
1783
+ YearPicker,
1784
+ {
1785
+ value: currentValue ?? null,
1786
+ displayedYear: calendarMonth.getFullYear(),
1787
+ onYearChange: (year) => setCalendarMonth(new Date(year, 0, 1)),
1788
+ onSelect: handleGridPickerSelect,
1789
+ isDisabled: (date) => isYearDisabled(date, normalizedMinDate, normalizedMaxDate)
1790
+ }
1791
+ ) : type === "date" ? /* @__PURE__ */ jsxRuntime.jsx(
1792
+ primitives_Calendar.Calendar,
1793
+ {
1794
+ className: utils$1.cn(showTime && SHOW_TIME_CALENDAR_CLASS_NAME),
1795
+ mode: "single",
1796
+ captionLayout: "dropdown",
1797
+ today: todayDate,
1798
+ month: calendarMonth,
1799
+ onMonthChange: setCalendarMonth,
1800
+ selected: calendarSelected(),
1801
+ onSelect: handleSingleDateSelectWithPicker,
1802
+ disabled: calendarDisabled,
1803
+ weekStartsOn,
1804
+ classNames: weekRowHoverClassName ? { week: utils$1.cn("mt-2 flex w-full", weekRowHoverClassName) } : void 0,
1805
+ ...weekHighlightProps,
1806
+ startMonth: calendarStartMonth,
1807
+ endMonth: calendarEndMonth
1808
+ }
1809
+ ) : showTime ? /* @__PURE__ */ jsxRuntime.jsx(
1810
+ RangeCalendar,
1811
+ {
1812
+ className: SHOW_TIME_CALENDAR_CLASS_NAME,
1813
+ todayDate,
1814
+ weekStartsOn,
1815
+ month: calendarMonth,
1816
+ onMonthChange: setCalendarMonth,
1817
+ selected: memoizedRangeSelected,
1818
+ onSelect: handleRangeSelect,
1819
+ disabled: calendarDisabled,
1820
+ startMonth: calendarStartMonth,
1821
+ endMonth: calendarEndMonth,
1822
+ onHoverIn: handleHoverIn,
1823
+ onHoverOut: handleHoverOut,
1824
+ numberOfMonths: 1
1825
+ }
1826
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
1827
+ RangeCalendar,
1828
+ {
1829
+ todayDate,
1830
+ weekStartsOn,
1831
+ month: calendarMonth,
1832
+ onMonthChange: setCalendarMonth,
1833
+ selected: memoizedRangeSelected,
1834
+ onSelect: handleRangeSelect,
1835
+ disabled: calendarDisabled,
1836
+ startMonth: calendarStartMonth,
1837
+ endMonth: calendarEndMonth,
1838
+ onHoverIn: handleHoverIn,
1839
+ onHoverOut: handleHoverOut
1840
+ }
1841
+ ) }),
1842
+ showTime && type === "range" && /* @__PURE__ */ jsxRuntime.jsx(
1843
+ TimeSection,
1844
+ {
1845
+ className: TIME_SECTION_CLASS_NAME,
1846
+ disabled,
1847
+ hasTime: true,
1848
+ optional: false,
1849
+ showError: isTimeErrorRequested,
1850
+ timezoneLabel,
1851
+ value: rangeSelectionStep === "from" ? pendingTime : pendingTimeTo,
1852
+ onChange: rangeSelectionStep === "from" ? setPendingTime : setPendingTimeTo,
1853
+ minuteStep,
1854
+ onErrorChange: setHasTimeError,
1855
+ onHasTimeChange: noop
1856
+ },
1857
+ rangeSelectionStep
1858
+ ),
1859
+ showTime && type !== "range" && /* @__PURE__ */ jsxRuntime.jsx(
1860
+ TimeSection,
1861
+ {
1862
+ className: TIME_SECTION_CLASS_NAME,
1863
+ disabled,
1864
+ hasTime: pendingHasTime,
1865
+ optional: isTimeOptional,
1866
+ showError: isTimeErrorRequested,
1867
+ timezoneLabel,
1868
+ value: pendingTime,
1869
+ onChange: setPendingTime,
1870
+ minuteStep,
1871
+ onErrorChange: setHasTimeError,
1872
+ onHasTimeChange: handleHasTimeChange
1873
+ }
1874
+ ),
1297
1875
  showFooter && /* @__PURE__ */ jsxRuntime.jsx(
1298
1876
  DatePickerFooter,
1299
1877
  {
@@ -1302,6 +1880,7 @@ const DatePicker = React.forwardRef(
1302
1880
  timezone,
1303
1881
  onTimezoneChange,
1304
1882
  nowDisabled: calendarDisabled(todayDate),
1883
+ showNow,
1305
1884
  onNow: handleNow,
1306
1885
  onOk: handleOk,
1307
1886
  okLabel: type === "range" && (showTime || needConfirm) && rangeSelectionStep === "from" ? "Next" : "OK"
@@ -1338,4 +1917,4 @@ const DatePicker = React.forwardRef(
1338
1917
  DatePicker.displayName = "DatePicker";
1339
1918
 
1340
1919
  exports.DatePicker = DatePicker;
1341
- //# sourceMappingURL=DatePicker-D8GQGON2.js.map
1920
+ //# sourceMappingURL=DatePicker-aGLz-3Bz.js.map