@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.
- package/dist/{DatePicker-edLgTark.js → DatePicker-BC9AOZJ2.js} +713 -135
- package/dist/DatePicker-BC9AOZJ2.js.map +1 -0
- package/dist/{TimePicker-BA7JhrYd.js → TimePicker-CJ8K2zea.js} +192 -17
- package/dist/TimePicker-CJ8K2zea.js.map +1 -0
- package/dist/{TranslationProvider-Dd0ITHC0.js → TranslationProvider-BJjfFEgX.js} +12 -1
- package/dist/{TranslationProvider-Dd0ITHC0.js.map → TranslationProvider-BJjfFEgX.js.map} +1 -1
- package/dist/cjs/{DatePicker-D8GQGON2.js → DatePicker-aGLz-3Bz.js} +724 -145
- package/dist/cjs/DatePicker-aGLz-3Bz.js.map +1 -0
- package/dist/cjs/{TimePicker-CVGXEgk5.js → TimePicker-BhOW7vbV.js} +217 -41
- package/dist/cjs/TimePicker-BhOW7vbV.js.map +1 -0
- package/dist/cjs/{TranslationProvider-DOeyGtbV.js → TranslationProvider-C1JDwKEN.js} +12 -1
- package/dist/cjs/{TranslationProvider-DOeyGtbV.js.map → TranslationProvider-C1JDwKEN.js.map} +1 -1
- package/dist/cjs/components/DatePicker.js +7 -6
- package/dist/cjs/components/DatePicker.js.map +1 -1
- package/dist/cjs/components/TimePicker.js +7 -7
- package/dist/cjs/components/TranslationProvider.js +1 -1
- package/dist/cjs/components/index.js +7 -7
- package/dist/cjs/index.js +8 -8
- package/dist/cjs/primitives/AlertDialog.js +39 -7
- package/dist/cjs/primitives/AlertDialog.js.map +1 -1
- package/dist/cjs/primitives/index.js +1 -1
- package/dist/cjs/{TimePickerPanel-DvVRLqeM.js → utils-S4yTubWZ.js} +25 -197
- package/dist/cjs/utils-S4yTubWZ.js.map +1 -0
- package/dist/components/DatePicker/DatePickerFooter.d.ts +1 -0
- package/dist/components/DatePicker/RangeCalendar.d.ts +4 -3
- package/dist/components/DatePicker/SegmentedTimeInput.d.ts +17 -0
- package/dist/components/DatePicker/TimeSection.d.ts +22 -0
- package/dist/components/DatePicker/constants.d.ts +2 -1
- package/dist/components/DatePicker/segmentedTime.d.ts +58 -0
- package/dist/components/DatePicker/types.d.ts +33 -4
- package/dist/components/DatePicker/utils.d.ts +3 -0
- package/dist/components/DatePicker.js +7 -6
- package/dist/components/DatePicker.js.map +1 -1
- package/dist/components/TimePicker.js +6 -6
- package/dist/components/TranslationProvider.js +1 -1
- package/dist/components/index.js +6 -6
- package/dist/index.js +8 -8
- package/dist/translations/index.d.ts +11 -0
- package/dist/utils/inputMask.d.ts +2 -1
- package/dist/{TimePickerPanel-n-Dz1p5N.js → utils-Ca3DmOrC.js} +22 -197
- package/dist/utils-Ca3DmOrC.js.map +1 -0
- package/package.json +1 -1
- package/dist/DatePicker-edLgTark.js.map +0 -1
- package/dist/TimePicker-BA7JhrYd.js.map +0 -1
- package/dist/TimePickerPanel-n-Dz1p5N.js.map +0 -1
- package/dist/cjs/DatePicker-D8GQGON2.js.map +0 -1
- package/dist/cjs/TimePicker-CVGXEgk5.js.map +0 -1
- package/dist/cjs/TimePickerPanel-DvVRLqeM.js.map +0 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { jsxs, jsx
|
|
2
|
-
import { forwardRef,
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import React__default, { forwardRef, useRef, useState, useEffect, useId, useCallback, useMemo } from 'react';
|
|
3
3
|
import { Calendar as Calendar$1 } from './primitives/Calendar.js';
|
|
4
4
|
import { PopoverAnchor, Popover, PopoverTrigger, PopoverContent } from './primitives/Popover.js';
|
|
5
5
|
import { c as cn } from './utils-BJnb9o5c.js';
|
|
6
|
-
import { u as useControlledOpen,
|
|
6
|
+
import { t as to12Hour, a as to24Hour, p as padNumber, d as timeValueEquals, C as Clock, u as useControlledOpen, q as isFixedWidthFormat, r as buildRangeMaskTemplate, h as buildMaskTemplate, e as useOutsideClickClose, j as useCursorRestore, m as applyMask, s as dateToTimeValue } from './utils-Ca3DmOrC.js';
|
|
7
7
|
import pureDayjs from 'dayjs';
|
|
8
8
|
import customParseFormat from 'dayjs/plugin/customParseFormat';
|
|
9
9
|
import { dayjs } from '@bigbinary/neeto-commons-frontend/utils';
|
|
@@ -12,6 +12,9 @@ import { Button } from './primitives/Button.js';
|
|
|
12
12
|
import { Field, FieldLabel, FieldContent, FieldError, FieldDescription } from './primitives/Field.js';
|
|
13
13
|
import { c as createLucideIcon } from './createLucideIcon-C8ycilSN.js';
|
|
14
14
|
import { X } from './x-_o2T3n6D.js';
|
|
15
|
+
import { useTranslation } from 'react-i18next';
|
|
16
|
+
import { Label } from './primitives/Label.js';
|
|
17
|
+
import { Switch } from './primitives/Switch.js';
|
|
15
18
|
import { C as ChevronLeft } from './chevron-left-BDoT8E2-.js';
|
|
16
19
|
import { C as ChevronRight } from './chevron-right-DQnrO-ek.js';
|
|
17
20
|
|
|
@@ -23,16 +26,32 @@ import { C as ChevronRight } from './chevron-right-DQnrO-ek.js';
|
|
|
23
26
|
*/
|
|
24
27
|
|
|
25
28
|
|
|
26
|
-
const __iconNode = [
|
|
29
|
+
const __iconNode$1 = [
|
|
27
30
|
["path", { d: "M8 2v4", key: "1cmpym" }],
|
|
28
31
|
["path", { d: "M16 2v4", key: "4m81vk" }],
|
|
29
32
|
["rect", { width: "18", height: "18", x: "3", y: "4", rx: "2", key: "1hopcy" }],
|
|
30
33
|
["path", { d: "M3 10h18", key: "8toen8" }]
|
|
31
34
|
];
|
|
32
|
-
const Calendar = createLucideIcon("calendar", __iconNode);
|
|
35
|
+
const Calendar = createLucideIcon("calendar", __iconNode$1);
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @license lucide-react v0.577.0 - ISC
|
|
39
|
+
*
|
|
40
|
+
* This source code is licensed under the ISC license.
|
|
41
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
const __iconNode = [
|
|
46
|
+
["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
|
|
47
|
+
["path", { d: "M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20", key: "13o1zl" }],
|
|
48
|
+
["path", { d: "M2 12h20", key: "9i4pu4" }]
|
|
49
|
+
];
|
|
50
|
+
const Globe = createLucideIcon("globe", __iconNode);
|
|
33
51
|
|
|
34
52
|
const DEFAULT_DATE_FORMAT = "dd/MM/yyyy";
|
|
35
|
-
const DEFAULT_TIME_FORMAT = "
|
|
53
|
+
const DEFAULT_TIME_FORMAT = "hh:mm A";
|
|
54
|
+
const CALENDAR_NAVIGATION_YEAR_RANGE = 100;
|
|
36
55
|
const INITIAL_TIME_VALUE = {
|
|
37
56
|
hours: 0,
|
|
38
57
|
minutes: 0,
|
|
@@ -102,6 +121,33 @@ const getInitialCalendarMonth = (value, defaultValue, type) => {
|
|
|
102
121
|
};
|
|
103
122
|
const normalizeToDayjsFormat = (fmt) => fmt.replace(/\byyyy\b/g, "YYYY").replace(/\byy\b/g, "YY").replace(/\bdd\b/g, "DD").replace(/\bd\b/g, "D");
|
|
104
123
|
const normalizeToDateFnsFormat = (fmt) => fmt.replace(/YYYY/g, "yyyy").replace(/YY/g, "yy").replace(/DD/g, "dd").replace(/\bD\b/g, "d");
|
|
124
|
+
const roundToMinuteStep = (date, step) => {
|
|
125
|
+
const rounded = new Date(date);
|
|
126
|
+
const minutes = Math.min(
|
|
127
|
+
60 - step,
|
|
128
|
+
Math.round(date.getMinutes() / step) * step
|
|
129
|
+
);
|
|
130
|
+
rounded.setMinutes(minutes, 0, 0);
|
|
131
|
+
return rounded;
|
|
132
|
+
};
|
|
133
|
+
const getTimezoneLabel = (timezone) => {
|
|
134
|
+
const zone = timezone === "utc" ? "UTC" : dayjs().$x?.$timezone ?? Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
135
|
+
try {
|
|
136
|
+
const zoneName = (timeZoneName) => new Intl.DateTimeFormat("en-US", { timeZone: zone, timeZoneName }).formatToParts(/* @__PURE__ */ new Date()).find((part) => part.type === "timeZoneName")?.value;
|
|
137
|
+
const name = zoneName("long") ?? zone;
|
|
138
|
+
const offset = (zoneName("longOffset") ?? "GMT").replace("GMT", "UTC").replace(/([+-])0?(\d+):00$/, "$1$2").replace(/([+-])0(\d)/, "$1$2");
|
|
139
|
+
return `${name} (${offset === "UTC" ? "UTC+0" : offset})`;
|
|
140
|
+
} catch {
|
|
141
|
+
return zone;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
const roundUpToMinuteStep = (date, step) => {
|
|
145
|
+
const rounded = new Date(date);
|
|
146
|
+
const hasSeconds = date.getSeconds() > 0 || date.getMilliseconds() > 0;
|
|
147
|
+
const minutes = date.getMinutes() + (hasSeconds ? 1 : 0);
|
|
148
|
+
rounded.setMinutes(Math.ceil(minutes / step) * step, 0, 0);
|
|
149
|
+
return rounded;
|
|
150
|
+
};
|
|
105
151
|
const formatDate = (date, formatStr) => {
|
|
106
152
|
if (!date) return "";
|
|
107
153
|
try {
|
|
@@ -260,6 +306,7 @@ const DatePickerFooter = ({
|
|
|
260
306
|
timezone,
|
|
261
307
|
onTimezoneChange,
|
|
262
308
|
nowDisabled,
|
|
309
|
+
showNow = true,
|
|
263
310
|
onNow,
|
|
264
311
|
onOk,
|
|
265
312
|
okLabel = "OK",
|
|
@@ -274,7 +321,7 @@ const DatePickerFooter = ({
|
|
|
274
321
|
className
|
|
275
322
|
),
|
|
276
323
|
children: [
|
|
277
|
-
/* @__PURE__ */ jsx(
|
|
324
|
+
showNow && /* @__PURE__ */ jsx(
|
|
278
325
|
Button,
|
|
279
326
|
{
|
|
280
327
|
variant: "ghost",
|
|
@@ -434,6 +481,443 @@ const DatePickerTrigger = forwardRef(
|
|
|
434
481
|
);
|
|
435
482
|
DatePickerTrigger.displayName = "DatePickerTrigger";
|
|
436
483
|
|
|
484
|
+
const SEGMENT_KEYS = ["hour", "minute", "period"];
|
|
485
|
+
const EMPTY_SEGMENT = { text: "", done: false, auto: false };
|
|
486
|
+
const segment = (text, done, auto = false) => ({
|
|
487
|
+
text,
|
|
488
|
+
done,
|
|
489
|
+
auto
|
|
490
|
+
});
|
|
491
|
+
const clampIndex = (index) => Math.max(0, Math.min(2, index));
|
|
492
|
+
const withSegment = (state, key, value) => ({ ...state, [key]: value });
|
|
493
|
+
const snapMinute = (minutes, step = 1) => padNumber(Math.min(60 - step, Math.round(Number(minutes) / step) * step));
|
|
494
|
+
const emptySegmentedTime = () => ({
|
|
495
|
+
hour: EMPTY_SEGMENT,
|
|
496
|
+
minute: EMPTY_SEGMENT,
|
|
497
|
+
period: EMPTY_SEGMENT,
|
|
498
|
+
active: 0
|
|
499
|
+
});
|
|
500
|
+
const segmentedTimeFromValue = (value, step = 1) => {
|
|
501
|
+
const { hour12, period } = to12Hour(value.hours);
|
|
502
|
+
return {
|
|
503
|
+
hour: segment(String(hour12), true),
|
|
504
|
+
minute: segment(snapMinute(value.minutes, step), true),
|
|
505
|
+
period: segment(period, true),
|
|
506
|
+
active: 0
|
|
507
|
+
};
|
|
508
|
+
};
|
|
509
|
+
const isSegmentedTimeEmpty = (state) => SEGMENT_KEYS.every((key) => state[key].text === "");
|
|
510
|
+
const getSegmentedTimeError = (state) => {
|
|
511
|
+
if (isSegmentedTimeEmpty(state)) return "timeRequired";
|
|
512
|
+
if (!state.hour.text) return "hourRequired";
|
|
513
|
+
if (!state.minute.text) return "minuteRequired";
|
|
514
|
+
if (!state.period.text) return "periodRequired";
|
|
515
|
+
return null;
|
|
516
|
+
};
|
|
517
|
+
const segmentedTimeToValue = (state) => {
|
|
518
|
+
if (getSegmentedTimeError(state)) return null;
|
|
519
|
+
return {
|
|
520
|
+
hours: to24Hour(Number(state.hour.text), state.period.text),
|
|
521
|
+
minutes: Number(state.minute.text),
|
|
522
|
+
seconds: 0
|
|
523
|
+
};
|
|
524
|
+
};
|
|
525
|
+
const finishSegment = (state, key, step) => {
|
|
526
|
+
const { text } = state[key];
|
|
527
|
+
if (!text) return state;
|
|
528
|
+
if (key === "hour" && text === "0") {
|
|
529
|
+
return withSegment(state, "hour", EMPTY_SEGMENT);
|
|
530
|
+
}
|
|
531
|
+
if (key === "minute" && text.length === 1) {
|
|
532
|
+
return withSegment(
|
|
533
|
+
state,
|
|
534
|
+
"minute",
|
|
535
|
+
segment(snapMinute(text, step), true, true)
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
return withSegment(state, key, { ...state[key], done: true });
|
|
539
|
+
};
|
|
540
|
+
const finishActiveSegment = (state, step = 1) => finishSegment(state, SEGMENT_KEYS[state.active], step);
|
|
541
|
+
const focusSegment = (state, index, step = 1) => {
|
|
542
|
+
const next = clampIndex(index);
|
|
543
|
+
const finished = next > state.active ? finishActiveSegment(state, step) : state;
|
|
544
|
+
return { ...finished, active: next };
|
|
545
|
+
};
|
|
546
|
+
const focusFirstEmptySegment = (state) => {
|
|
547
|
+
const index = SEGMENT_KEYS.findIndex((key) => !state[key].text);
|
|
548
|
+
return { ...state, active: index === -1 ? 2 : index };
|
|
549
|
+
};
|
|
550
|
+
const nextSegment = (state, step = 1) => state[SEGMENT_KEYS[state.active]].text ? focusSegment(state, state.active + 1, step) : state;
|
|
551
|
+
const previousSegment = (state) => focusSegment(state, state.active - 1);
|
|
552
|
+
const typeDigit = (state, digit, step = 1) => {
|
|
553
|
+
const advance = (next, index) => focusSegment(next, index, step);
|
|
554
|
+
const key = SEGMENT_KEYS[state.active];
|
|
555
|
+
if (key === "period") return state;
|
|
556
|
+
const current = state[key];
|
|
557
|
+
const text = current.done ? "" : current.text;
|
|
558
|
+
if (key === "hour") {
|
|
559
|
+
if (text === "") {
|
|
560
|
+
if (digit === "0" || digit === "1") {
|
|
561
|
+
return withSegment(state, "hour", segment(digit, false));
|
|
562
|
+
}
|
|
563
|
+
return advance(withSegment(state, "hour", segment(digit, true)), 1);
|
|
564
|
+
}
|
|
565
|
+
if (text === "1") {
|
|
566
|
+
if ("012".includes(digit)) {
|
|
567
|
+
return advance(
|
|
568
|
+
withSegment(state, "hour", segment(`1${digit}`, true)),
|
|
569
|
+
1
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
const moved = advance(withSegment(state, "hour", segment("1", true)), 1);
|
|
573
|
+
return typeDigit(moved, digit, step);
|
|
574
|
+
}
|
|
575
|
+
if (digit === "0") return state;
|
|
576
|
+
return advance(withSegment(state, "hour", segment(digit, true)), 1);
|
|
577
|
+
}
|
|
578
|
+
if (text === "") {
|
|
579
|
+
if (digit >= "6") {
|
|
580
|
+
return advance(
|
|
581
|
+
withSegment(
|
|
582
|
+
state,
|
|
583
|
+
"minute",
|
|
584
|
+
segment(snapMinute(digit, step), true, true)
|
|
585
|
+
),
|
|
586
|
+
2
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
return withSegment(state, "minute", segment(digit, false));
|
|
590
|
+
}
|
|
591
|
+
return advance(
|
|
592
|
+
withSegment(state, "minute", segment(snapMinute(text + digit, step), true)),
|
|
593
|
+
2
|
|
594
|
+
);
|
|
595
|
+
};
|
|
596
|
+
const typePeriod = (state, letter, step = 1) => withSegment(
|
|
597
|
+
focusSegment(state, 2, step),
|
|
598
|
+
"period",
|
|
599
|
+
segment(letter === "a" ? "AM" : "PM", true)
|
|
600
|
+
);
|
|
601
|
+
const togglePeriod = (state) => withSegment(
|
|
602
|
+
state,
|
|
603
|
+
"period",
|
|
604
|
+
segment(state.period.text === "AM" ? "PM" : "AM", true)
|
|
605
|
+
);
|
|
606
|
+
const stepSegment = (state, direction, step = 1) => {
|
|
607
|
+
const key = SEGMENT_KEYS[state.active];
|
|
608
|
+
if (key === "hour") {
|
|
609
|
+
const hour = Number(state.hour.text) || (direction > 0 ? 0 : 1);
|
|
610
|
+
const next = (hour - 1 + direction + 12) % 12 + 1;
|
|
611
|
+
return withSegment(state, "hour", segment(String(next), true));
|
|
612
|
+
}
|
|
613
|
+
if (key === "minute") {
|
|
614
|
+
const minute = state.minute.text === "" ? direction > 0 ? -step : 0 : Number(state.minute.text);
|
|
615
|
+
const stepped = direction > 0 ? Math.floor(minute / step) * step + step : Math.ceil(minute / step) * step - step;
|
|
616
|
+
return withSegment(
|
|
617
|
+
state,
|
|
618
|
+
"minute",
|
|
619
|
+
segment(padNumber((stepped + 60) % 60), true)
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
return togglePeriod(state);
|
|
623
|
+
};
|
|
624
|
+
const backspace = (state) => {
|
|
625
|
+
const moveBack = state[SEGMENT_KEYS[state.active]].text === "" && state.active > 0;
|
|
626
|
+
const active = moveBack ? clampIndex(state.active - 1) : state.active;
|
|
627
|
+
const key = SEGMENT_KEYS[active];
|
|
628
|
+
const current = state[key];
|
|
629
|
+
const text = key === "period" || current.auto ? "" : current.text.slice(0, -1);
|
|
630
|
+
return { ...withSegment(state, key, segment(text, false)), active };
|
|
631
|
+
};
|
|
632
|
+
const parsePastedTime = (text, step = 1) => {
|
|
633
|
+
const match = text.trim().toLowerCase().match(/^(\d{1,2}):?(\d{2})?\s*([ap])?\.?m?\.?$/);
|
|
634
|
+
if (!match) return null;
|
|
635
|
+
let hours = Number(match[1]);
|
|
636
|
+
const minutes = Number(match[2] ?? 0);
|
|
637
|
+
const period = match[3];
|
|
638
|
+
if (minutes > 59) return null;
|
|
639
|
+
if (period) {
|
|
640
|
+
if (hours < 1 || hours > 12) return null;
|
|
641
|
+
hours = hours % 12 + (period === "p" ? 12 : 0);
|
|
642
|
+
} else if (hours > 23) {
|
|
643
|
+
return null;
|
|
644
|
+
}
|
|
645
|
+
return { hours, minutes: Number(snapMinute(minutes, step)), seconds: 0 };
|
|
646
|
+
};
|
|
647
|
+
|
|
648
|
+
const PLACEHOLDERS = ["hh", "mm", "--"];
|
|
649
|
+
const SegmentedTimeInput = ({
|
|
650
|
+
value,
|
|
651
|
+
minuteStep,
|
|
652
|
+
onChange,
|
|
653
|
+
onErrorChange,
|
|
654
|
+
invalid = false,
|
|
655
|
+
disabled = false,
|
|
656
|
+
autoFocus = false,
|
|
657
|
+
"aria-describedby": ariaDescribedBy
|
|
658
|
+
}) => {
|
|
659
|
+
const { t } = useTranslation();
|
|
660
|
+
const fieldRef = useRef(null);
|
|
661
|
+
const step = minuteStep ?? 1;
|
|
662
|
+
const [time, setTime] = useState(
|
|
663
|
+
() => segmentedTimeFromValue(value, step)
|
|
664
|
+
);
|
|
665
|
+
const lastEmittedRef = useRef(value);
|
|
666
|
+
useEffect(() => {
|
|
667
|
+
if (timeValueEquals(value, lastEmittedRef.current)) return;
|
|
668
|
+
lastEmittedRef.current = value;
|
|
669
|
+
setTime(segmentedTimeFromValue(value, step));
|
|
670
|
+
}, [value]);
|
|
671
|
+
const error = getSegmentedTimeError(time);
|
|
672
|
+
useEffect(() => {
|
|
673
|
+
onErrorChange(error);
|
|
674
|
+
}, [error, onErrorChange]);
|
|
675
|
+
useEffect(() => {
|
|
676
|
+
const snapped = segmentedTimeToValue(time);
|
|
677
|
+
if (snapped && !timeValueEquals(snapped, value)) {
|
|
678
|
+
lastEmittedRef.current = snapped;
|
|
679
|
+
onChange(snapped);
|
|
680
|
+
}
|
|
681
|
+
}, []);
|
|
682
|
+
useEffect(() => {
|
|
683
|
+
if (autoFocus) fieldRef.current?.focus();
|
|
684
|
+
}, [autoFocus]);
|
|
685
|
+
const update = (next) => {
|
|
686
|
+
setTime(next);
|
|
687
|
+
const nextValue = segmentedTimeToValue(next);
|
|
688
|
+
if (!nextValue || timeValueEquals(nextValue, lastEmittedRef.current)) {
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
lastEmittedRef.current = nextValue;
|
|
692
|
+
onChange(nextValue);
|
|
693
|
+
};
|
|
694
|
+
const consume = (event) => {
|
|
695
|
+
event.preventDefault();
|
|
696
|
+
event.stopPropagation();
|
|
697
|
+
};
|
|
698
|
+
const handleKeyDown = (event) => {
|
|
699
|
+
const { key } = event;
|
|
700
|
+
if (key === "Tab") {
|
|
701
|
+
if (!event.shiftKey || time.active !== 2) return;
|
|
702
|
+
consume(event);
|
|
703
|
+
update(togglePeriod(time));
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
if ((event.metaKey || event.ctrlKey) && key === "Backspace") {
|
|
707
|
+
consume(event);
|
|
708
|
+
update(emptySegmentedTime());
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
if (event.metaKey || event.ctrlKey || event.altKey) return;
|
|
712
|
+
let next = null;
|
|
713
|
+
if (/^[0-9]$/.test(key)) next = typeDigit(time, key, step);
|
|
714
|
+
else if (/^[ap]$/i.test(key)) {
|
|
715
|
+
next = typePeriod(time, key.toLowerCase(), step);
|
|
716
|
+
} else if (/^m$/i.test(key))
|
|
717
|
+
next = time;
|
|
718
|
+
else if (key === ":" || key === " " || key === "ArrowRight") {
|
|
719
|
+
next = nextSegment(time, step);
|
|
720
|
+
} else if (key === "ArrowLeft") next = previousSegment(time);
|
|
721
|
+
else if (key === "ArrowUp") next = stepSegment(time, 1, step);
|
|
722
|
+
else if (key === "ArrowDown") next = stepSegment(time, -1, step);
|
|
723
|
+
else if (key === "Backspace" || key === "Delete") next = backspace(time);
|
|
724
|
+
if (!next) return;
|
|
725
|
+
consume(event);
|
|
726
|
+
update(next);
|
|
727
|
+
};
|
|
728
|
+
const handlePaste = (event) => {
|
|
729
|
+
event.preventDefault();
|
|
730
|
+
const pasted = parsePastedTime(event.clipboardData.getData("text"), step);
|
|
731
|
+
if (pasted) update({ ...segmentedTimeFromValue(pasted, step), active: 2 });
|
|
732
|
+
};
|
|
733
|
+
const handleMouseDown = (event) => {
|
|
734
|
+
if (disabled) return;
|
|
735
|
+
const segmentEl = event.target.closest(
|
|
736
|
+
"[data-segment]"
|
|
737
|
+
);
|
|
738
|
+
if (segmentEl) {
|
|
739
|
+
event.preventDefault();
|
|
740
|
+
fieldRef.current?.focus();
|
|
741
|
+
update(focusSegment(time, Number(segmentEl.dataset.segment), step));
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
update(focusFirstEmptySegment(time));
|
|
745
|
+
};
|
|
746
|
+
const segmentText = (index) => {
|
|
747
|
+
const { text, done } = time[SEGMENT_KEYS[index]];
|
|
748
|
+
if (!text) return PLACEHOLDERS[index];
|
|
749
|
+
return index < 2 && !done ? `${text}_` : text;
|
|
750
|
+
};
|
|
751
|
+
return /* @__PURE__ */ jsx(
|
|
752
|
+
"div",
|
|
753
|
+
{
|
|
754
|
+
ref: fieldRef,
|
|
755
|
+
role: "group",
|
|
756
|
+
tabIndex: disabled ? -1 : 0,
|
|
757
|
+
"aria-label": t("neetoatoms.datePicker.timeField", "Time"),
|
|
758
|
+
"aria-describedby": ariaDescribedBy,
|
|
759
|
+
"aria-invalid": invalid || void 0,
|
|
760
|
+
"aria-disabled": disabled || void 0,
|
|
761
|
+
"data-testid": "datepicker-time-input",
|
|
762
|
+
className: cn(
|
|
763
|
+
"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",
|
|
764
|
+
"focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
|
|
765
|
+
invalid && "border-destructive",
|
|
766
|
+
disabled && "pointer-events-none cursor-not-allowed opacity-50"
|
|
767
|
+
),
|
|
768
|
+
onBlur: () => update(finishActiveSegment(time, step)),
|
|
769
|
+
onKeyDown: handleKeyDown,
|
|
770
|
+
onMouseDown: handleMouseDown,
|
|
771
|
+
onPaste: handlePaste,
|
|
772
|
+
children: SEGMENT_KEYS.map((key, index) => /* @__PURE__ */ jsxs(React__default.Fragment, { children: [
|
|
773
|
+
index === 1 && /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "px-0.5 text-muted-foreground", children: ":" }),
|
|
774
|
+
index === 2 && /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "w-1" }),
|
|
775
|
+
/* @__PURE__ */ jsx(
|
|
776
|
+
"span",
|
|
777
|
+
{
|
|
778
|
+
"data-segment": index,
|
|
779
|
+
"data-testid": `datepicker-time-${key}`,
|
|
780
|
+
className: cn(
|
|
781
|
+
"min-w-[2ch] px-0.5 text-center whitespace-pre",
|
|
782
|
+
!time[key].text && "text-muted-foreground",
|
|
783
|
+
index === time.active && "group-focus/time:shadow-[inset_0_-1.5px_0_var(--color-muted-foreground)]"
|
|
784
|
+
),
|
|
785
|
+
children: segmentText(index)
|
|
786
|
+
}
|
|
787
|
+
)
|
|
788
|
+
] }, key))
|
|
789
|
+
}
|
|
790
|
+
);
|
|
791
|
+
};
|
|
792
|
+
SegmentedTimeInput.displayName = "SegmentedTimeInput";
|
|
793
|
+
|
|
794
|
+
const ERROR_DEFAULTS = {
|
|
795
|
+
timeRequired: "Enter a time, or turn off Add time.",
|
|
796
|
+
hourRequired: "Enter an hour.",
|
|
797
|
+
minuteRequired: "Enter minutes.",
|
|
798
|
+
periodRequired: "Type a or p for AM or PM."
|
|
799
|
+
};
|
|
800
|
+
const stepExamples = (step) => {
|
|
801
|
+
const pad = (minutes) => `1:${String(minutes).padStart(2, "0")}`;
|
|
802
|
+
if (step * 2 >= 60) return [pad(0), pad(step)];
|
|
803
|
+
return [pad(step), pad(30 % step === 0 && step < 30 ? 30 : step * 2)];
|
|
804
|
+
};
|
|
805
|
+
const TimeSection = ({
|
|
806
|
+
value,
|
|
807
|
+
minuteStep,
|
|
808
|
+
onChange,
|
|
809
|
+
onErrorChange,
|
|
810
|
+
showError,
|
|
811
|
+
optional,
|
|
812
|
+
hasTime,
|
|
813
|
+
onHasTimeChange,
|
|
814
|
+
timezoneLabel,
|
|
815
|
+
disabled = false,
|
|
816
|
+
className
|
|
817
|
+
}) => {
|
|
818
|
+
const { t } = useTranslation();
|
|
819
|
+
const id = useId();
|
|
820
|
+
const switchId = `datepicker-add-time-${id}`;
|
|
821
|
+
const hintId = `datepicker-time-hint-${id}`;
|
|
822
|
+
const errorId = `datepicker-time-error-${id}`;
|
|
823
|
+
const [error, setError] = useState(null);
|
|
824
|
+
const [focusOnMount, setFocusOnMount] = useState(false);
|
|
825
|
+
const handleErrorChange = useCallback(
|
|
826
|
+
(nextError) => {
|
|
827
|
+
setError(nextError);
|
|
828
|
+
onErrorChange(!!nextError);
|
|
829
|
+
},
|
|
830
|
+
[onErrorChange]
|
|
831
|
+
);
|
|
832
|
+
const handleHasTimeChange = (checked) => {
|
|
833
|
+
setFocusOnMount(checked);
|
|
834
|
+
if (!checked) {
|
|
835
|
+
setError(null);
|
|
836
|
+
onErrorChange(false);
|
|
837
|
+
}
|
|
838
|
+
onHasTimeChange(checked);
|
|
839
|
+
};
|
|
840
|
+
const isTimeOn = !optional || hasTime;
|
|
841
|
+
const showHint = (minuteStep ?? 1) > 1;
|
|
842
|
+
const errorMessage = isTimeOn && showError && error ? t(`neetoatoms.datePicker.errors.${error}`, ERROR_DEFAULTS[error]) : null;
|
|
843
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-2", className), children: [
|
|
844
|
+
optional && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3", children: [
|
|
845
|
+
/* @__PURE__ */ jsxs(
|
|
846
|
+
Label,
|
|
847
|
+
{
|
|
848
|
+
htmlFor: switchId,
|
|
849
|
+
className: "flex items-center gap-2 font-medium",
|
|
850
|
+
children: [
|
|
851
|
+
/* @__PURE__ */ jsx(Clock, { "aria-hidden": true, className: "size-4 text-muted-foreground" }),
|
|
852
|
+
t("neetoatoms.datePicker.addTime", "Add time")
|
|
853
|
+
]
|
|
854
|
+
}
|
|
855
|
+
),
|
|
856
|
+
/* @__PURE__ */ jsx(
|
|
857
|
+
Switch,
|
|
858
|
+
{
|
|
859
|
+
id: switchId,
|
|
860
|
+
checked: hasTime,
|
|
861
|
+
"data-testid": "datepicker-add-time-switch",
|
|
862
|
+
disabled,
|
|
863
|
+
onCheckedChange: handleHasTimeChange
|
|
864
|
+
}
|
|
865
|
+
)
|
|
866
|
+
] }),
|
|
867
|
+
isTimeOn && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
|
|
868
|
+
/* @__PURE__ */ jsx(
|
|
869
|
+
SegmentedTimeInput,
|
|
870
|
+
{
|
|
871
|
+
"aria-describedby": [showHint ? hintId : null, errorMessage ? errorId : null].filter(Boolean).join(" ") || void 0,
|
|
872
|
+
autoFocus: focusOnMount,
|
|
873
|
+
disabled,
|
|
874
|
+
invalid: !!errorMessage,
|
|
875
|
+
minuteStep,
|
|
876
|
+
value,
|
|
877
|
+
onChange,
|
|
878
|
+
onErrorChange: handleErrorChange
|
|
879
|
+
}
|
|
880
|
+
),
|
|
881
|
+
showHint && /* @__PURE__ */ jsx(
|
|
882
|
+
"p",
|
|
883
|
+
{
|
|
884
|
+
id: hintId,
|
|
885
|
+
className: "text-xs text-muted-foreground",
|
|
886
|
+
"data-testid": "datepicker-time-hint",
|
|
887
|
+
children: t("neetoatoms.datePicker.timeHint", {
|
|
888
|
+
step: minuteStep,
|
|
889
|
+
first: stepExamples(minuteStep)[0],
|
|
890
|
+
second: stepExamples(minuteStep)[1],
|
|
891
|
+
defaultValue: "Only {{step}}-minute steps are allowed, like {{first}} or {{second}}."
|
|
892
|
+
})
|
|
893
|
+
}
|
|
894
|
+
),
|
|
895
|
+
errorMessage && /* @__PURE__ */ jsx(
|
|
896
|
+
"p",
|
|
897
|
+
{
|
|
898
|
+
id: errorId,
|
|
899
|
+
className: "text-xs text-destructive",
|
|
900
|
+
"data-testid": "datepicker-time-error",
|
|
901
|
+
role: "alert",
|
|
902
|
+
children: errorMessage
|
|
903
|
+
}
|
|
904
|
+
),
|
|
905
|
+
timezoneLabel && /* @__PURE__ */ jsxs(
|
|
906
|
+
"p",
|
|
907
|
+
{
|
|
908
|
+
className: "mt-1 flex items-center gap-2 text-xs text-muted-foreground",
|
|
909
|
+
"data-testid": "datepicker-timezone",
|
|
910
|
+
children: [
|
|
911
|
+
/* @__PURE__ */ jsx(Globe, { "aria-hidden": true, className: "size-3.5 shrink-0" }),
|
|
912
|
+
timezoneLabel
|
|
913
|
+
]
|
|
914
|
+
}
|
|
915
|
+
)
|
|
916
|
+
] })
|
|
917
|
+
] });
|
|
918
|
+
};
|
|
919
|
+
TimeSection.displayName = "TimeSection";
|
|
920
|
+
|
|
437
921
|
const MonthPicker = ({
|
|
438
922
|
value,
|
|
439
923
|
displayedYear,
|
|
@@ -502,11 +986,12 @@ const RangeCalendar = ({
|
|
|
502
986
|
selected,
|
|
503
987
|
onSelect,
|
|
504
988
|
disabled,
|
|
505
|
-
|
|
506
|
-
|
|
989
|
+
startMonth,
|
|
990
|
+
endMonth,
|
|
507
991
|
onHoverIn,
|
|
508
992
|
onHoverOut,
|
|
509
|
-
numberOfMonths = 2
|
|
993
|
+
numberOfMonths = 2,
|
|
994
|
+
className
|
|
510
995
|
}) => /* @__PURE__ */ jsx("div", { onMouseOver: onHoverIn, onMouseLeave: onHoverOut, children: /* @__PURE__ */ jsx(
|
|
511
996
|
Calendar$1,
|
|
512
997
|
{
|
|
@@ -515,14 +1000,17 @@ const RangeCalendar = ({
|
|
|
515
1000
|
captionLayout: "dropdown",
|
|
516
1001
|
today: todayDate,
|
|
517
1002
|
weekStartsOn,
|
|
518
|
-
className:
|
|
1003
|
+
className: cn(
|
|
1004
|
+
"[&_[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",
|
|
1005
|
+
className
|
|
1006
|
+
),
|
|
519
1007
|
month,
|
|
520
1008
|
onMonthChange,
|
|
521
1009
|
selected,
|
|
522
1010
|
onSelect,
|
|
523
1011
|
disabled,
|
|
524
|
-
|
|
525
|
-
|
|
1012
|
+
startMonth,
|
|
1013
|
+
endMonth
|
|
526
1014
|
}
|
|
527
1015
|
) });
|
|
528
1016
|
|
|
@@ -689,6 +1177,11 @@ const YearPicker = ({
|
|
|
689
1177
|
] });
|
|
690
1178
|
};
|
|
691
1179
|
|
|
1180
|
+
const TIME_SECTION_CLASS_NAME = "w-0 min-w-full border-t border-border px-3 py-3";
|
|
1181
|
+
const SHOW_TIME_CALENDAR_CLASS_NAME = "[--cell-size:--spacing(8.125)]";
|
|
1182
|
+
const DEFAULT_ADDED_TIME = { hours: 12, minutes: 0, seconds: 0 };
|
|
1183
|
+
const noop = () => {
|
|
1184
|
+
};
|
|
692
1185
|
const DatePicker = forwardRef(
|
|
693
1186
|
({
|
|
694
1187
|
value,
|
|
@@ -699,8 +1192,10 @@ const DatePicker = forwardRef(
|
|
|
699
1192
|
dateFormat = DEFAULT_DATE_FORMAT,
|
|
700
1193
|
timeFormat = DEFAULT_TIME_FORMAT,
|
|
701
1194
|
showTime = false,
|
|
702
|
-
|
|
703
|
-
|
|
1195
|
+
showNow = true,
|
|
1196
|
+
minuteStep,
|
|
1197
|
+
timeOptional = true,
|
|
1198
|
+
hasTime: hasTimeProp,
|
|
704
1199
|
minDate,
|
|
705
1200
|
maxDate,
|
|
706
1201
|
disabledDate,
|
|
@@ -713,6 +1208,7 @@ const DatePicker = forwardRef(
|
|
|
713
1208
|
allowClear = true,
|
|
714
1209
|
required = false,
|
|
715
1210
|
timezone,
|
|
1211
|
+
timezoneLabel: timezoneLabelProp,
|
|
716
1212
|
onTimezoneChange,
|
|
717
1213
|
onOk,
|
|
718
1214
|
needConfirm = false,
|
|
@@ -747,6 +1243,15 @@ const DatePicker = forwardRef(
|
|
|
747
1243
|
const [pendingDate, setPendingDate] = useState(null);
|
|
748
1244
|
const [pendingTime, setPendingTime] = useState(INITIAL_TIME_VALUE);
|
|
749
1245
|
const [pendingTimeTo, setPendingTimeTo] = useState(INITIAL_TIME_VALUE);
|
|
1246
|
+
const [hasTimeError, setHasTimeError] = useState(false);
|
|
1247
|
+
const [isTimeErrorRequested, setIsTimeErrorRequested] = useState(false);
|
|
1248
|
+
const isTimeOptional = showTime && timeOptional && type === "date";
|
|
1249
|
+
const [internalHasTime, setInternalHasTime] = useState(
|
|
1250
|
+
() => hasTimeProp ?? (value ?? defaultValue) != null
|
|
1251
|
+
);
|
|
1252
|
+
const committedHasTime = showTime && (!isTimeOptional || (hasTimeProp ?? internalHasTime));
|
|
1253
|
+
const [pendingHasTime, setPendingHasTime] = useState(committedHasTime);
|
|
1254
|
+
const isPendingTimeOn = showTime && (!isTimeOptional || pendingHasTime);
|
|
750
1255
|
const currentValue = useMemo(
|
|
751
1256
|
() => value !== void 0 ? coerceDateValue(value, type) : internalValue,
|
|
752
1257
|
[value, type, internalValue]
|
|
@@ -759,10 +1264,13 @@ const DatePicker = forwardRef(
|
|
|
759
1264
|
() => toNativeDate(maxDate) ?? void 0,
|
|
760
1265
|
[maxDate]
|
|
761
1266
|
);
|
|
762
|
-
const displayFormat = getDisplayFormat(
|
|
1267
|
+
const displayFormat = getDisplayFormat(
|
|
1268
|
+
dateFormat,
|
|
1269
|
+
timeFormat,
|
|
1270
|
+
committedHasTime
|
|
1271
|
+
);
|
|
763
1272
|
const sizeConfig = SIZE_CONFIG[size];
|
|
764
1273
|
const maskEnabled = isFixedWidthFormat(displayFormat);
|
|
765
|
-
const effectiveTimePickerFormat = timePickerFormat ?? (/\ba\b/i.test(timeFormat) ? "12" : "24");
|
|
766
1274
|
const defaultPlaceholder = getDatePlaceholder(
|
|
767
1275
|
dateFormat,
|
|
768
1276
|
timeFormat,
|
|
@@ -799,38 +1307,46 @@ const DatePicker = forwardRef(
|
|
|
799
1307
|
return `${from} - ${to}`;
|
|
800
1308
|
}, [currentValue, displayFormat, type]);
|
|
801
1309
|
const [inputText, setInputText] = useState(() => getDisplayText());
|
|
802
|
-
const
|
|
1310
|
+
const emitChange = (changed, formatted, withTime) => {
|
|
1311
|
+
if (isTimeOptional) onChange?.(changed, formatted, { hasTime: withTime });
|
|
1312
|
+
else onChange?.(changed, formatted);
|
|
1313
|
+
};
|
|
1314
|
+
const commitValue = (date, withTime = committedHasTime) => {
|
|
803
1315
|
if (date === null) {
|
|
804
1316
|
setInternalValue(null);
|
|
1317
|
+
setInternalHasTime(false);
|
|
805
1318
|
if (type === "range") {
|
|
806
|
-
|
|
1319
|
+
emitChange([null, null], ["", ""], false);
|
|
807
1320
|
} else {
|
|
808
|
-
|
|
1321
|
+
emitChange(null, "", false);
|
|
809
1322
|
}
|
|
810
1323
|
setInputText("");
|
|
811
1324
|
return;
|
|
812
1325
|
}
|
|
1326
|
+
const commitFormat = getDisplayFormat(dateFormat, timeFormat, withTime);
|
|
813
1327
|
const toSerializableDayjs = (d) => {
|
|
814
1328
|
const userTzDayjs = toUserTzDayjs(d);
|
|
815
|
-
if (
|
|
1329
|
+
if (withTime) return userTzDayjs;
|
|
816
1330
|
return userTzDayjs.hour(12).minute(0).second(0).millisecond(0);
|
|
817
1331
|
};
|
|
818
1332
|
if (Array.isArray(date)) {
|
|
819
1333
|
setInternalValue(date);
|
|
820
1334
|
const formatted2 = [
|
|
821
|
-
formatDate(date[0],
|
|
822
|
-
formatDate(date[1],
|
|
1335
|
+
formatDate(date[0], commitFormat),
|
|
1336
|
+
formatDate(date[1], commitFormat)
|
|
823
1337
|
];
|
|
824
|
-
|
|
1338
|
+
emitChange(
|
|
825
1339
|
[toSerializableDayjs(date[0]), toSerializableDayjs(date[1])],
|
|
826
|
-
formatted2
|
|
1340
|
+
formatted2,
|
|
1341
|
+
withTime
|
|
827
1342
|
);
|
|
828
1343
|
setInputText(`${formatted2[0]} - ${formatted2[1]}`);
|
|
829
1344
|
return;
|
|
830
1345
|
}
|
|
831
1346
|
setInternalValue(date);
|
|
832
|
-
|
|
833
|
-
|
|
1347
|
+
setInternalHasTime(withTime);
|
|
1348
|
+
const formatted = formatDate(date, commitFormat);
|
|
1349
|
+
emitChange(toSerializableDayjs(date), formatted, withTime);
|
|
834
1350
|
setInputText(formatted);
|
|
835
1351
|
};
|
|
836
1352
|
const {
|
|
@@ -890,30 +1406,33 @@ const DatePicker = forwardRef(
|
|
|
890
1406
|
};
|
|
891
1407
|
const commitPendingOnClose = useCallback(() => {
|
|
892
1408
|
const hasOkButton = showTime || needConfirm;
|
|
893
|
-
if (!hasOkButton || !pendingDate) {
|
|
1409
|
+
if (!hasOkButton || !pendingDate || isPendingTimeOn && hasTimeError) {
|
|
894
1410
|
setInputText(getDisplayText());
|
|
895
1411
|
return;
|
|
896
1412
|
}
|
|
897
|
-
const finalDate =
|
|
1413
|
+
const finalDate = isPendingTimeOn ? applyTimeToDate(pendingDate, pendingTime) : pendingDate;
|
|
898
1414
|
const currentSingle = type === "date" ? currentValue : null;
|
|
899
|
-
const isSame = currentSingle && finalDate.getTime() === currentSingle.getTime();
|
|
1415
|
+
const isSame = currentSingle && isPendingTimeOn === committedHasTime && finalDate.getTime() === currentSingle.getTime();
|
|
900
1416
|
if (isSame) {
|
|
901
1417
|
setInputText(getDisplayText());
|
|
902
1418
|
return;
|
|
903
1419
|
}
|
|
904
|
-
commitValue(finalDate);
|
|
1420
|
+
commitValue(finalDate, isPendingTimeOn);
|
|
905
1421
|
}, [
|
|
906
1422
|
showTime,
|
|
907
1423
|
needConfirm,
|
|
908
1424
|
pendingDate,
|
|
909
1425
|
pendingTime,
|
|
1426
|
+
isPendingTimeOn,
|
|
1427
|
+
committedHasTime,
|
|
910
1428
|
type,
|
|
911
1429
|
currentValue,
|
|
912
1430
|
getDisplayText,
|
|
913
|
-
onChange
|
|
1431
|
+
onChange,
|
|
1432
|
+
hasTimeError
|
|
914
1433
|
]);
|
|
915
1434
|
const closePopover = useCallback(() => {
|
|
916
|
-
if (type === "range" && pendingRange?.[0] && pendingRange?.[1]) {
|
|
1435
|
+
if (type === "range" && pendingRange?.[0] && pendingRange?.[1] && !(showTime && hasTimeError)) {
|
|
917
1436
|
const [fromBase, toBase] = orderRange(
|
|
918
1437
|
pendingRange[0],
|
|
919
1438
|
pendingRange[1]
|
|
@@ -934,7 +1453,8 @@ const DatePicker = forwardRef(
|
|
|
934
1453
|
pendingTime,
|
|
935
1454
|
pendingTimeTo,
|
|
936
1455
|
showTime,
|
|
937
|
-
resetTransientState
|
|
1456
|
+
resetTransientState,
|
|
1457
|
+
hasTimeError
|
|
938
1458
|
]);
|
|
939
1459
|
useOutsideClickClose({
|
|
940
1460
|
enabled: open,
|
|
@@ -945,6 +1465,7 @@ const DatePicker = forwardRef(
|
|
|
945
1465
|
const openPopover = () => {
|
|
946
1466
|
setOpen(true);
|
|
947
1467
|
resetTransientState();
|
|
1468
|
+
setIsTimeErrorRequested(false);
|
|
948
1469
|
if (type === "range") {
|
|
949
1470
|
const rangeVal = currentValue;
|
|
950
1471
|
const fromDate = rangeVal?.[0] ?? null;
|
|
@@ -957,14 +1478,19 @@ const DatePicker = forwardRef(
|
|
|
957
1478
|
return;
|
|
958
1479
|
}
|
|
959
1480
|
const dateVal = currentValue;
|
|
1481
|
+
setPendingHasTime(committedHasTime);
|
|
960
1482
|
if (!dateVal) {
|
|
961
1483
|
setPendingDate(null);
|
|
962
|
-
setPendingTime(
|
|
1484
|
+
setPendingTime(
|
|
1485
|
+
isTimeOptional ? DEFAULT_ADDED_TIME : INITIAL_TIME_VALUE
|
|
1486
|
+
);
|
|
963
1487
|
return;
|
|
964
1488
|
}
|
|
965
1489
|
setCalendarMonth(dateVal);
|
|
966
1490
|
setPendingDate(dateVal);
|
|
967
|
-
setPendingTime(
|
|
1491
|
+
setPendingTime(
|
|
1492
|
+
committedHasTime || !isTimeOptional ? dateToTimeValue(dateVal) : DEFAULT_ADDED_TIME
|
|
1493
|
+
);
|
|
968
1494
|
};
|
|
969
1495
|
const handleDateSelect = (selected) => {
|
|
970
1496
|
if (!selected) return;
|
|
@@ -977,7 +1503,9 @@ const DatePicker = forwardRef(
|
|
|
977
1503
|
setOpen(false);
|
|
978
1504
|
};
|
|
979
1505
|
const handleNow = () => {
|
|
980
|
-
const now = toBrowserLocalDate(
|
|
1506
|
+
const now = toBrowserLocalDate(
|
|
1507
|
+
showTime && minuteStep ? roundUpToMinuteStep(/* @__PURE__ */ new Date(), minuteStep) : /* @__PURE__ */ new Date()
|
|
1508
|
+
);
|
|
981
1509
|
if (calendarDisabled(now)) return;
|
|
982
1510
|
setCalendarMonth(now);
|
|
983
1511
|
if (type === "range") {
|
|
@@ -992,12 +1520,17 @@ const DatePicker = forwardRef(
|
|
|
992
1520
|
if (showTime || needConfirm) {
|
|
993
1521
|
setPendingDate(now);
|
|
994
1522
|
setPendingTime(dateToTimeValue(now));
|
|
1523
|
+
if (isTimeOptional) setPendingHasTime(true);
|
|
995
1524
|
return;
|
|
996
1525
|
}
|
|
997
1526
|
commitValue(now);
|
|
998
1527
|
setOpen(false);
|
|
999
1528
|
};
|
|
1000
1529
|
const handleOk = () => {
|
|
1530
|
+
if (isPendingTimeOn && hasTimeError) {
|
|
1531
|
+
setIsTimeErrorRequested(true);
|
|
1532
|
+
return;
|
|
1533
|
+
}
|
|
1001
1534
|
if (type === "range" && (showTime || needConfirm)) {
|
|
1002
1535
|
const committedRange = currentValue;
|
|
1003
1536
|
const fromValue = pendingRange?.[0] ?? committedRange?.[0] ?? null;
|
|
@@ -1016,7 +1549,7 @@ const DatePicker = forwardRef(
|
|
|
1016
1549
|
const [fromBase, toBase] = orderRange(fromValue, toValue);
|
|
1017
1550
|
const fromFinal = showTime ? applyTimeToDate(fromBase, pendingTime) : fromBase;
|
|
1018
1551
|
const toFinal = showTime ? applyTimeToDate(toBase, pendingTimeTo) : toBase;
|
|
1019
|
-
commitValue([fromFinal, toFinal]);
|
|
1552
|
+
commitValue([fromFinal, toFinal], showTime);
|
|
1020
1553
|
onOk?.([fromFinal, toFinal]);
|
|
1021
1554
|
resetTransientState();
|
|
1022
1555
|
setOpen(false);
|
|
@@ -1025,8 +1558,8 @@ const DatePicker = forwardRef(
|
|
|
1025
1558
|
return;
|
|
1026
1559
|
}
|
|
1027
1560
|
if (pendingDate) {
|
|
1028
|
-
const finalDate =
|
|
1029
|
-
commitValue(finalDate);
|
|
1561
|
+
const finalDate = isPendingTimeOn ? applyTimeToDate(pendingDate, pendingTime) : pendingDate;
|
|
1562
|
+
commitValue(finalDate, isPendingTimeOn);
|
|
1030
1563
|
onOk?.(finalDate);
|
|
1031
1564
|
}
|
|
1032
1565
|
setOpen(false);
|
|
@@ -1081,10 +1614,21 @@ const DatePicker = forwardRef(
|
|
|
1081
1614
|
maskEnabled,
|
|
1082
1615
|
singleDateLen
|
|
1083
1616
|
);
|
|
1084
|
-
if (range)
|
|
1617
|
+
if (range) {
|
|
1618
|
+
commitValue(
|
|
1619
|
+
showTime && minuteStep ? [
|
|
1620
|
+
roundToMinuteStep(range[0], minuteStep),
|
|
1621
|
+
roundToMinuteStep(range[1], minuteStep)
|
|
1622
|
+
] : range
|
|
1623
|
+
);
|
|
1624
|
+
}
|
|
1085
1625
|
} else {
|
|
1086
1626
|
const parsed = parseDate(inputText, displayFormat);
|
|
1087
|
-
if (parsed)
|
|
1627
|
+
if (parsed) {
|
|
1628
|
+
commitValue(
|
|
1629
|
+
committedHasTime && minuteStep ? roundToMinuteStep(parsed, minuteStep) : parsed
|
|
1630
|
+
);
|
|
1631
|
+
}
|
|
1088
1632
|
}
|
|
1089
1633
|
setOpen(false);
|
|
1090
1634
|
};
|
|
@@ -1104,10 +1648,32 @@ const DatePicker = forwardRef(
|
|
|
1104
1648
|
const dateVal = currentValue;
|
|
1105
1649
|
if (dateVal instanceof Date) setCalendarMonth(dateVal);
|
|
1106
1650
|
}, [open]);
|
|
1651
|
+
const timezoneLabel = showTime && !onTimezoneChange ? (timezoneLabelProp ?? getTimezoneLabel(timezone)) || null : null;
|
|
1107
1652
|
const hasField = !!(label || error || helpText);
|
|
1108
1653
|
const testIdPrefix = buildTestIdPrefix(dataTestId, label);
|
|
1109
1654
|
const showFooter = showTime || needConfirm || !!onTimezoneChange;
|
|
1110
1655
|
const todayDate = toBrowserLocalDate(/* @__PURE__ */ new Date());
|
|
1656
|
+
const handleHasTimeChange = (checked) => {
|
|
1657
|
+
setPendingHasTime(checked);
|
|
1658
|
+
setIsTimeErrorRequested(false);
|
|
1659
|
+
if (!checked) setHasTimeError(false);
|
|
1660
|
+
if (checked && !pendingDate && !calendarDisabled(todayDate)) {
|
|
1661
|
+
setPendingDate(todayDate);
|
|
1662
|
+
setCalendarMonth(todayDate);
|
|
1663
|
+
}
|
|
1664
|
+
};
|
|
1665
|
+
const defaultStartMonth = new Date(
|
|
1666
|
+
todayDate.getFullYear() - CALENDAR_NAVIGATION_YEAR_RANGE,
|
|
1667
|
+
0,
|
|
1668
|
+
1
|
|
1669
|
+
);
|
|
1670
|
+
const defaultEndMonth = new Date(
|
|
1671
|
+
todayDate.getFullYear() + CALENDAR_NAVIGATION_YEAR_RANGE,
|
|
1672
|
+
11,
|
|
1673
|
+
1
|
|
1674
|
+
);
|
|
1675
|
+
const calendarStartMonth = normalizedMinDate ?? (calendarMonth < defaultStartMonth ? calendarMonth : defaultStartMonth);
|
|
1676
|
+
const calendarEndMonth = normalizedMaxDate ?? (calendarMonth > defaultEndMonth ? calendarMonth : defaultEndMonth);
|
|
1111
1677
|
const handleGridPickerSelect = (date) => {
|
|
1112
1678
|
commitValue(date);
|
|
1113
1679
|
setOpen(false);
|
|
@@ -1194,99 +1760,110 @@ const DatePicker = forwardRef(
|
|
|
1194
1760
|
onPointerDownOutside: (e) => e.preventDefault(),
|
|
1195
1761
|
onInteractOutside: (e) => e.preventDefault(),
|
|
1196
1762
|
children: [
|
|
1197
|
-
/* @__PURE__ */
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
new Date(year, calendarMonth.getMonth(), 1)
|
|
1205
|
-
),
|
|
1206
|
-
onSelect: handleGridPickerSelect,
|
|
1207
|
-
isDisabled: (date) => isMonthDisabled(date, normalizedMinDate, normalizedMaxDate)
|
|
1208
|
-
}
|
|
1209
|
-
) : type === "date" && picker === "year" ? /* @__PURE__ */ jsx(
|
|
1210
|
-
YearPicker,
|
|
1211
|
-
{
|
|
1212
|
-
value: currentValue ?? null,
|
|
1213
|
-
displayedYear: calendarMonth.getFullYear(),
|
|
1214
|
-
onYearChange: (year) => setCalendarMonth(new Date(year, 0, 1)),
|
|
1215
|
-
onSelect: handleGridPickerSelect,
|
|
1216
|
-
isDisabled: (date) => isYearDisabled(date, normalizedMinDate, normalizedMaxDate)
|
|
1217
|
-
}
|
|
1218
|
-
) : type === "date" ? /* @__PURE__ */ jsx(
|
|
1219
|
-
Calendar$1,
|
|
1220
|
-
{
|
|
1221
|
-
mode: "single",
|
|
1222
|
-
captionLayout: "dropdown",
|
|
1223
|
-
today: todayDate,
|
|
1224
|
-
month: calendarMonth,
|
|
1225
|
-
onMonthChange: setCalendarMonth,
|
|
1226
|
-
selected: calendarSelected(),
|
|
1227
|
-
onSelect: handleSingleDateSelectWithPicker,
|
|
1228
|
-
disabled: calendarDisabled,
|
|
1229
|
-
weekStartsOn,
|
|
1230
|
-
classNames: weekRowHoverClassName ? { week: cn("mt-2 flex w-full", weekRowHoverClassName) } : void 0,
|
|
1231
|
-
...weekHighlightProps,
|
|
1232
|
-
...normalizedMinDate && { fromDate: normalizedMinDate },
|
|
1233
|
-
...normalizedMaxDate && { toDate: normalizedMaxDate }
|
|
1234
|
-
}
|
|
1235
|
-
) : showTime ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1236
|
-
/* @__PURE__ */ jsx(
|
|
1237
|
-
RangeCalendar,
|
|
1238
|
-
{
|
|
1239
|
-
todayDate,
|
|
1240
|
-
weekStartsOn,
|
|
1241
|
-
month: calendarMonth,
|
|
1242
|
-
onMonthChange: setCalendarMonth,
|
|
1243
|
-
selected: memoizedRangeSelected,
|
|
1244
|
-
onSelect: handleRangeSelect,
|
|
1245
|
-
disabled: calendarDisabled,
|
|
1246
|
-
fromDate: normalizedMinDate,
|
|
1247
|
-
toDate: normalizedMaxDate,
|
|
1248
|
-
onHoverIn: handleHoverIn,
|
|
1249
|
-
onHoverOut: handleHoverOut,
|
|
1250
|
-
numberOfMonths: 1
|
|
1251
|
-
}
|
|
1763
|
+
/* @__PURE__ */ jsx("div", { className: "flex", children: type === "date" && picker === "month" ? /* @__PURE__ */ jsx(
|
|
1764
|
+
MonthPicker,
|
|
1765
|
+
{
|
|
1766
|
+
value: currentValue ?? null,
|
|
1767
|
+
displayedYear: calendarMonth.getFullYear(),
|
|
1768
|
+
onYearChange: (year) => setCalendarMonth(
|
|
1769
|
+
new Date(year, calendarMonth.getMonth(), 1)
|
|
1252
1770
|
),
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1771
|
+
onSelect: handleGridPickerSelect,
|
|
1772
|
+
isDisabled: (date) => isMonthDisabled(date, normalizedMinDate, normalizedMaxDate)
|
|
1773
|
+
}
|
|
1774
|
+
) : type === "date" && picker === "year" ? /* @__PURE__ */ jsx(
|
|
1775
|
+
YearPicker,
|
|
1776
|
+
{
|
|
1777
|
+
value: currentValue ?? null,
|
|
1778
|
+
displayedYear: calendarMonth.getFullYear(),
|
|
1779
|
+
onYearChange: (year) => setCalendarMonth(new Date(year, 0, 1)),
|
|
1780
|
+
onSelect: handleGridPickerSelect,
|
|
1781
|
+
isDisabled: (date) => isYearDisabled(date, normalizedMinDate, normalizedMaxDate)
|
|
1782
|
+
}
|
|
1783
|
+
) : type === "date" ? /* @__PURE__ */ jsx(
|
|
1784
|
+
Calendar$1,
|
|
1785
|
+
{
|
|
1786
|
+
className: cn(showTime && SHOW_TIME_CALENDAR_CLASS_NAME),
|
|
1787
|
+
mode: "single",
|
|
1788
|
+
captionLayout: "dropdown",
|
|
1789
|
+
today: todayDate,
|
|
1790
|
+
month: calendarMonth,
|
|
1791
|
+
onMonthChange: setCalendarMonth,
|
|
1792
|
+
selected: calendarSelected(),
|
|
1793
|
+
onSelect: handleSingleDateSelectWithPicker,
|
|
1794
|
+
disabled: calendarDisabled,
|
|
1795
|
+
weekStartsOn,
|
|
1796
|
+
classNames: weekRowHoverClassName ? { week: cn("mt-2 flex w-full", weekRowHoverClassName) } : void 0,
|
|
1797
|
+
...weekHighlightProps,
|
|
1798
|
+
startMonth: calendarStartMonth,
|
|
1799
|
+
endMonth: calendarEndMonth
|
|
1800
|
+
}
|
|
1801
|
+
) : showTime ? /* @__PURE__ */ jsx(
|
|
1802
|
+
RangeCalendar,
|
|
1803
|
+
{
|
|
1804
|
+
className: SHOW_TIME_CALENDAR_CLASS_NAME,
|
|
1805
|
+
todayDate,
|
|
1806
|
+
weekStartsOn,
|
|
1807
|
+
month: calendarMonth,
|
|
1808
|
+
onMonthChange: setCalendarMonth,
|
|
1809
|
+
selected: memoizedRangeSelected,
|
|
1810
|
+
onSelect: handleRangeSelect,
|
|
1811
|
+
disabled: calendarDisabled,
|
|
1812
|
+
startMonth: calendarStartMonth,
|
|
1813
|
+
endMonth: calendarEndMonth,
|
|
1814
|
+
onHoverIn: handleHoverIn,
|
|
1815
|
+
onHoverOut: handleHoverOut,
|
|
1816
|
+
numberOfMonths: 1
|
|
1817
|
+
}
|
|
1818
|
+
) : /* @__PURE__ */ jsx(
|
|
1819
|
+
RangeCalendar,
|
|
1820
|
+
{
|
|
1821
|
+
todayDate,
|
|
1822
|
+
weekStartsOn,
|
|
1823
|
+
month: calendarMonth,
|
|
1824
|
+
onMonthChange: setCalendarMonth,
|
|
1825
|
+
selected: memoizedRangeSelected,
|
|
1826
|
+
onSelect: handleRangeSelect,
|
|
1827
|
+
disabled: calendarDisabled,
|
|
1828
|
+
startMonth: calendarStartMonth,
|
|
1829
|
+
endMonth: calendarEndMonth,
|
|
1830
|
+
onHoverIn: handleHoverIn,
|
|
1831
|
+
onHoverOut: handleHoverOut
|
|
1832
|
+
}
|
|
1833
|
+
) }),
|
|
1834
|
+
showTime && type === "range" && /* @__PURE__ */ jsx(
|
|
1835
|
+
TimeSection,
|
|
1836
|
+
{
|
|
1837
|
+
className: TIME_SECTION_CLASS_NAME,
|
|
1838
|
+
disabled,
|
|
1839
|
+
hasTime: true,
|
|
1840
|
+
optional: false,
|
|
1841
|
+
showError: isTimeErrorRequested,
|
|
1842
|
+
timezoneLabel,
|
|
1843
|
+
value: rangeSelectionStep === "from" ? pendingTime : pendingTimeTo,
|
|
1844
|
+
onChange: rangeSelectionStep === "from" ? setPendingTime : setPendingTimeTo,
|
|
1845
|
+
minuteStep,
|
|
1846
|
+
onErrorChange: setHasTimeError,
|
|
1847
|
+
onHasTimeChange: noop
|
|
1848
|
+
},
|
|
1849
|
+
rangeSelectionStep
|
|
1850
|
+
),
|
|
1851
|
+
showTime && type !== "range" && /* @__PURE__ */ jsx(
|
|
1852
|
+
TimeSection,
|
|
1853
|
+
{
|
|
1854
|
+
className: TIME_SECTION_CLASS_NAME,
|
|
1855
|
+
disabled,
|
|
1856
|
+
hasTime: pendingHasTime,
|
|
1857
|
+
optional: isTimeOptional,
|
|
1858
|
+
showError: isTimeErrorRequested,
|
|
1859
|
+
timezoneLabel,
|
|
1860
|
+
value: pendingTime,
|
|
1861
|
+
onChange: setPendingTime,
|
|
1862
|
+
minuteStep,
|
|
1863
|
+
onErrorChange: setHasTimeError,
|
|
1864
|
+
onHasTimeChange: handleHasTimeChange
|
|
1865
|
+
}
|
|
1866
|
+
),
|
|
1290
1867
|
showFooter && /* @__PURE__ */ jsx(
|
|
1291
1868
|
DatePickerFooter,
|
|
1292
1869
|
{
|
|
@@ -1295,6 +1872,7 @@ const DatePicker = forwardRef(
|
|
|
1295
1872
|
timezone,
|
|
1296
1873
|
onTimezoneChange,
|
|
1297
1874
|
nowDisabled: calendarDisabled(todayDate),
|
|
1875
|
+
showNow,
|
|
1298
1876
|
onNow: handleNow,
|
|
1299
1877
|
onOk: handleOk,
|
|
1300
1878
|
okLabel: type === "range" && (showTime || needConfirm) && rangeSelectionStep === "from" ? "Next" : "OK"
|
|
@@ -1331,4 +1909,4 @@ const DatePicker = forwardRef(
|
|
|
1331
1909
|
DatePicker.displayName = "DatePicker";
|
|
1332
1910
|
|
|
1333
1911
|
export { DatePicker as D };
|
|
1334
|
-
//# sourceMappingURL=DatePicker-
|
|
1912
|
+
//# sourceMappingURL=DatePicker-BC9AOZJ2.js.map
|