@bigbinary/neeto-atoms 1.0.145 → 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-DznpJnhm.js → DatePicker-BC9AOZJ2.js} +696 -131
- 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-CMcp7TGb.js → DatePicker-aGLz-3Bz.js} +707 -141
- 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/{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 +2 -1
- 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 +1 -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-DznpJnhm.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-CMcp7TGb.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,31 @@ 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";
|
|
36
54
|
const CALENDAR_NAVIGATION_YEAR_RANGE = 100;
|
|
37
55
|
const INITIAL_TIME_VALUE = {
|
|
38
56
|
hours: 0,
|
|
@@ -103,6 +121,33 @@ const getInitialCalendarMonth = (value, defaultValue, type) => {
|
|
|
103
121
|
};
|
|
104
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");
|
|
105
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
|
+
};
|
|
106
151
|
const formatDate = (date, formatStr) => {
|
|
107
152
|
if (!date) return "";
|
|
108
153
|
try {
|
|
@@ -261,6 +306,7 @@ const DatePickerFooter = ({
|
|
|
261
306
|
timezone,
|
|
262
307
|
onTimezoneChange,
|
|
263
308
|
nowDisabled,
|
|
309
|
+
showNow = true,
|
|
264
310
|
onNow,
|
|
265
311
|
onOk,
|
|
266
312
|
okLabel = "OK",
|
|
@@ -275,7 +321,7 @@ const DatePickerFooter = ({
|
|
|
275
321
|
className
|
|
276
322
|
),
|
|
277
323
|
children: [
|
|
278
|
-
/* @__PURE__ */ jsx(
|
|
324
|
+
showNow && /* @__PURE__ */ jsx(
|
|
279
325
|
Button,
|
|
280
326
|
{
|
|
281
327
|
variant: "ghost",
|
|
@@ -435,6 +481,443 @@ const DatePickerTrigger = forwardRef(
|
|
|
435
481
|
);
|
|
436
482
|
DatePickerTrigger.displayName = "DatePickerTrigger";
|
|
437
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
|
+
|
|
438
921
|
const MonthPicker = ({
|
|
439
922
|
value,
|
|
440
923
|
displayedYear,
|
|
@@ -507,7 +990,8 @@ const RangeCalendar = ({
|
|
|
507
990
|
endMonth,
|
|
508
991
|
onHoverIn,
|
|
509
992
|
onHoverOut,
|
|
510
|
-
numberOfMonths = 2
|
|
993
|
+
numberOfMonths = 2,
|
|
994
|
+
className
|
|
511
995
|
}) => /* @__PURE__ */ jsx("div", { onMouseOver: onHoverIn, onMouseLeave: onHoverOut, children: /* @__PURE__ */ jsx(
|
|
512
996
|
Calendar$1,
|
|
513
997
|
{
|
|
@@ -516,7 +1000,10 @@ const RangeCalendar = ({
|
|
|
516
1000
|
captionLayout: "dropdown",
|
|
517
1001
|
today: todayDate,
|
|
518
1002
|
weekStartsOn,
|
|
519
|
-
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
|
+
),
|
|
520
1007
|
month,
|
|
521
1008
|
onMonthChange,
|
|
522
1009
|
selected,
|
|
@@ -690,6 +1177,11 @@ const YearPicker = ({
|
|
|
690
1177
|
] });
|
|
691
1178
|
};
|
|
692
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
|
+
};
|
|
693
1185
|
const DatePicker = forwardRef(
|
|
694
1186
|
({
|
|
695
1187
|
value,
|
|
@@ -700,8 +1192,10 @@ const DatePicker = forwardRef(
|
|
|
700
1192
|
dateFormat = DEFAULT_DATE_FORMAT,
|
|
701
1193
|
timeFormat = DEFAULT_TIME_FORMAT,
|
|
702
1194
|
showTime = false,
|
|
703
|
-
|
|
704
|
-
|
|
1195
|
+
showNow = true,
|
|
1196
|
+
minuteStep,
|
|
1197
|
+
timeOptional = true,
|
|
1198
|
+
hasTime: hasTimeProp,
|
|
705
1199
|
minDate,
|
|
706
1200
|
maxDate,
|
|
707
1201
|
disabledDate,
|
|
@@ -714,6 +1208,7 @@ const DatePicker = forwardRef(
|
|
|
714
1208
|
allowClear = true,
|
|
715
1209
|
required = false,
|
|
716
1210
|
timezone,
|
|
1211
|
+
timezoneLabel: timezoneLabelProp,
|
|
717
1212
|
onTimezoneChange,
|
|
718
1213
|
onOk,
|
|
719
1214
|
needConfirm = false,
|
|
@@ -748,6 +1243,15 @@ const DatePicker = forwardRef(
|
|
|
748
1243
|
const [pendingDate, setPendingDate] = useState(null);
|
|
749
1244
|
const [pendingTime, setPendingTime] = useState(INITIAL_TIME_VALUE);
|
|
750
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);
|
|
751
1255
|
const currentValue = useMemo(
|
|
752
1256
|
() => value !== void 0 ? coerceDateValue(value, type) : internalValue,
|
|
753
1257
|
[value, type, internalValue]
|
|
@@ -760,10 +1264,13 @@ const DatePicker = forwardRef(
|
|
|
760
1264
|
() => toNativeDate(maxDate) ?? void 0,
|
|
761
1265
|
[maxDate]
|
|
762
1266
|
);
|
|
763
|
-
const displayFormat = getDisplayFormat(
|
|
1267
|
+
const displayFormat = getDisplayFormat(
|
|
1268
|
+
dateFormat,
|
|
1269
|
+
timeFormat,
|
|
1270
|
+
committedHasTime
|
|
1271
|
+
);
|
|
764
1272
|
const sizeConfig = SIZE_CONFIG[size];
|
|
765
1273
|
const maskEnabled = isFixedWidthFormat(displayFormat);
|
|
766
|
-
const effectiveTimePickerFormat = timePickerFormat ?? (/\ba\b/i.test(timeFormat) ? "12" : "24");
|
|
767
1274
|
const defaultPlaceholder = getDatePlaceholder(
|
|
768
1275
|
dateFormat,
|
|
769
1276
|
timeFormat,
|
|
@@ -800,38 +1307,46 @@ const DatePicker = forwardRef(
|
|
|
800
1307
|
return `${from} - ${to}`;
|
|
801
1308
|
}, [currentValue, displayFormat, type]);
|
|
802
1309
|
const [inputText, setInputText] = useState(() => getDisplayText());
|
|
803
|
-
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) => {
|
|
804
1315
|
if (date === null) {
|
|
805
1316
|
setInternalValue(null);
|
|
1317
|
+
setInternalHasTime(false);
|
|
806
1318
|
if (type === "range") {
|
|
807
|
-
|
|
1319
|
+
emitChange([null, null], ["", ""], false);
|
|
808
1320
|
} else {
|
|
809
|
-
|
|
1321
|
+
emitChange(null, "", false);
|
|
810
1322
|
}
|
|
811
1323
|
setInputText("");
|
|
812
1324
|
return;
|
|
813
1325
|
}
|
|
1326
|
+
const commitFormat = getDisplayFormat(dateFormat, timeFormat, withTime);
|
|
814
1327
|
const toSerializableDayjs = (d) => {
|
|
815
1328
|
const userTzDayjs = toUserTzDayjs(d);
|
|
816
|
-
if (
|
|
1329
|
+
if (withTime) return userTzDayjs;
|
|
817
1330
|
return userTzDayjs.hour(12).minute(0).second(0).millisecond(0);
|
|
818
1331
|
};
|
|
819
1332
|
if (Array.isArray(date)) {
|
|
820
1333
|
setInternalValue(date);
|
|
821
1334
|
const formatted2 = [
|
|
822
|
-
formatDate(date[0],
|
|
823
|
-
formatDate(date[1],
|
|
1335
|
+
formatDate(date[0], commitFormat),
|
|
1336
|
+
formatDate(date[1], commitFormat)
|
|
824
1337
|
];
|
|
825
|
-
|
|
1338
|
+
emitChange(
|
|
826
1339
|
[toSerializableDayjs(date[0]), toSerializableDayjs(date[1])],
|
|
827
|
-
formatted2
|
|
1340
|
+
formatted2,
|
|
1341
|
+
withTime
|
|
828
1342
|
);
|
|
829
1343
|
setInputText(`${formatted2[0]} - ${formatted2[1]}`);
|
|
830
1344
|
return;
|
|
831
1345
|
}
|
|
832
1346
|
setInternalValue(date);
|
|
833
|
-
|
|
834
|
-
|
|
1347
|
+
setInternalHasTime(withTime);
|
|
1348
|
+
const formatted = formatDate(date, commitFormat);
|
|
1349
|
+
emitChange(toSerializableDayjs(date), formatted, withTime);
|
|
835
1350
|
setInputText(formatted);
|
|
836
1351
|
};
|
|
837
1352
|
const {
|
|
@@ -891,30 +1406,33 @@ const DatePicker = forwardRef(
|
|
|
891
1406
|
};
|
|
892
1407
|
const commitPendingOnClose = useCallback(() => {
|
|
893
1408
|
const hasOkButton = showTime || needConfirm;
|
|
894
|
-
if (!hasOkButton || !pendingDate) {
|
|
1409
|
+
if (!hasOkButton || !pendingDate || isPendingTimeOn && hasTimeError) {
|
|
895
1410
|
setInputText(getDisplayText());
|
|
896
1411
|
return;
|
|
897
1412
|
}
|
|
898
|
-
const finalDate =
|
|
1413
|
+
const finalDate = isPendingTimeOn ? applyTimeToDate(pendingDate, pendingTime) : pendingDate;
|
|
899
1414
|
const currentSingle = type === "date" ? currentValue : null;
|
|
900
|
-
const isSame = currentSingle && finalDate.getTime() === currentSingle.getTime();
|
|
1415
|
+
const isSame = currentSingle && isPendingTimeOn === committedHasTime && finalDate.getTime() === currentSingle.getTime();
|
|
901
1416
|
if (isSame) {
|
|
902
1417
|
setInputText(getDisplayText());
|
|
903
1418
|
return;
|
|
904
1419
|
}
|
|
905
|
-
commitValue(finalDate);
|
|
1420
|
+
commitValue(finalDate, isPendingTimeOn);
|
|
906
1421
|
}, [
|
|
907
1422
|
showTime,
|
|
908
1423
|
needConfirm,
|
|
909
1424
|
pendingDate,
|
|
910
1425
|
pendingTime,
|
|
1426
|
+
isPendingTimeOn,
|
|
1427
|
+
committedHasTime,
|
|
911
1428
|
type,
|
|
912
1429
|
currentValue,
|
|
913
1430
|
getDisplayText,
|
|
914
|
-
onChange
|
|
1431
|
+
onChange,
|
|
1432
|
+
hasTimeError
|
|
915
1433
|
]);
|
|
916
1434
|
const closePopover = useCallback(() => {
|
|
917
|
-
if (type === "range" && pendingRange?.[0] && pendingRange?.[1]) {
|
|
1435
|
+
if (type === "range" && pendingRange?.[0] && pendingRange?.[1] && !(showTime && hasTimeError)) {
|
|
918
1436
|
const [fromBase, toBase] = orderRange(
|
|
919
1437
|
pendingRange[0],
|
|
920
1438
|
pendingRange[1]
|
|
@@ -935,7 +1453,8 @@ const DatePicker = forwardRef(
|
|
|
935
1453
|
pendingTime,
|
|
936
1454
|
pendingTimeTo,
|
|
937
1455
|
showTime,
|
|
938
|
-
resetTransientState
|
|
1456
|
+
resetTransientState,
|
|
1457
|
+
hasTimeError
|
|
939
1458
|
]);
|
|
940
1459
|
useOutsideClickClose({
|
|
941
1460
|
enabled: open,
|
|
@@ -946,6 +1465,7 @@ const DatePicker = forwardRef(
|
|
|
946
1465
|
const openPopover = () => {
|
|
947
1466
|
setOpen(true);
|
|
948
1467
|
resetTransientState();
|
|
1468
|
+
setIsTimeErrorRequested(false);
|
|
949
1469
|
if (type === "range") {
|
|
950
1470
|
const rangeVal = currentValue;
|
|
951
1471
|
const fromDate = rangeVal?.[0] ?? null;
|
|
@@ -958,14 +1478,19 @@ const DatePicker = forwardRef(
|
|
|
958
1478
|
return;
|
|
959
1479
|
}
|
|
960
1480
|
const dateVal = currentValue;
|
|
1481
|
+
setPendingHasTime(committedHasTime);
|
|
961
1482
|
if (!dateVal) {
|
|
962
1483
|
setPendingDate(null);
|
|
963
|
-
setPendingTime(
|
|
1484
|
+
setPendingTime(
|
|
1485
|
+
isTimeOptional ? DEFAULT_ADDED_TIME : INITIAL_TIME_VALUE
|
|
1486
|
+
);
|
|
964
1487
|
return;
|
|
965
1488
|
}
|
|
966
1489
|
setCalendarMonth(dateVal);
|
|
967
1490
|
setPendingDate(dateVal);
|
|
968
|
-
setPendingTime(
|
|
1491
|
+
setPendingTime(
|
|
1492
|
+
committedHasTime || !isTimeOptional ? dateToTimeValue(dateVal) : DEFAULT_ADDED_TIME
|
|
1493
|
+
);
|
|
969
1494
|
};
|
|
970
1495
|
const handleDateSelect = (selected) => {
|
|
971
1496
|
if (!selected) return;
|
|
@@ -978,7 +1503,9 @@ const DatePicker = forwardRef(
|
|
|
978
1503
|
setOpen(false);
|
|
979
1504
|
};
|
|
980
1505
|
const handleNow = () => {
|
|
981
|
-
const now = toBrowserLocalDate(
|
|
1506
|
+
const now = toBrowserLocalDate(
|
|
1507
|
+
showTime && minuteStep ? roundUpToMinuteStep(/* @__PURE__ */ new Date(), minuteStep) : /* @__PURE__ */ new Date()
|
|
1508
|
+
);
|
|
982
1509
|
if (calendarDisabled(now)) return;
|
|
983
1510
|
setCalendarMonth(now);
|
|
984
1511
|
if (type === "range") {
|
|
@@ -993,12 +1520,17 @@ const DatePicker = forwardRef(
|
|
|
993
1520
|
if (showTime || needConfirm) {
|
|
994
1521
|
setPendingDate(now);
|
|
995
1522
|
setPendingTime(dateToTimeValue(now));
|
|
1523
|
+
if (isTimeOptional) setPendingHasTime(true);
|
|
996
1524
|
return;
|
|
997
1525
|
}
|
|
998
1526
|
commitValue(now);
|
|
999
1527
|
setOpen(false);
|
|
1000
1528
|
};
|
|
1001
1529
|
const handleOk = () => {
|
|
1530
|
+
if (isPendingTimeOn && hasTimeError) {
|
|
1531
|
+
setIsTimeErrorRequested(true);
|
|
1532
|
+
return;
|
|
1533
|
+
}
|
|
1002
1534
|
if (type === "range" && (showTime || needConfirm)) {
|
|
1003
1535
|
const committedRange = currentValue;
|
|
1004
1536
|
const fromValue = pendingRange?.[0] ?? committedRange?.[0] ?? null;
|
|
@@ -1017,7 +1549,7 @@ const DatePicker = forwardRef(
|
|
|
1017
1549
|
const [fromBase, toBase] = orderRange(fromValue, toValue);
|
|
1018
1550
|
const fromFinal = showTime ? applyTimeToDate(fromBase, pendingTime) : fromBase;
|
|
1019
1551
|
const toFinal = showTime ? applyTimeToDate(toBase, pendingTimeTo) : toBase;
|
|
1020
|
-
commitValue([fromFinal, toFinal]);
|
|
1552
|
+
commitValue([fromFinal, toFinal], showTime);
|
|
1021
1553
|
onOk?.([fromFinal, toFinal]);
|
|
1022
1554
|
resetTransientState();
|
|
1023
1555
|
setOpen(false);
|
|
@@ -1026,8 +1558,8 @@ const DatePicker = forwardRef(
|
|
|
1026
1558
|
return;
|
|
1027
1559
|
}
|
|
1028
1560
|
if (pendingDate) {
|
|
1029
|
-
const finalDate =
|
|
1030
|
-
commitValue(finalDate);
|
|
1561
|
+
const finalDate = isPendingTimeOn ? applyTimeToDate(pendingDate, pendingTime) : pendingDate;
|
|
1562
|
+
commitValue(finalDate, isPendingTimeOn);
|
|
1031
1563
|
onOk?.(finalDate);
|
|
1032
1564
|
}
|
|
1033
1565
|
setOpen(false);
|
|
@@ -1082,10 +1614,21 @@ const DatePicker = forwardRef(
|
|
|
1082
1614
|
maskEnabled,
|
|
1083
1615
|
singleDateLen
|
|
1084
1616
|
);
|
|
1085
|
-
if (range)
|
|
1617
|
+
if (range) {
|
|
1618
|
+
commitValue(
|
|
1619
|
+
showTime && minuteStep ? [
|
|
1620
|
+
roundToMinuteStep(range[0], minuteStep),
|
|
1621
|
+
roundToMinuteStep(range[1], minuteStep)
|
|
1622
|
+
] : range
|
|
1623
|
+
);
|
|
1624
|
+
}
|
|
1086
1625
|
} else {
|
|
1087
1626
|
const parsed = parseDate(inputText, displayFormat);
|
|
1088
|
-
if (parsed)
|
|
1627
|
+
if (parsed) {
|
|
1628
|
+
commitValue(
|
|
1629
|
+
committedHasTime && minuteStep ? roundToMinuteStep(parsed, minuteStep) : parsed
|
|
1630
|
+
);
|
|
1631
|
+
}
|
|
1089
1632
|
}
|
|
1090
1633
|
setOpen(false);
|
|
1091
1634
|
};
|
|
@@ -1105,10 +1648,20 @@ const DatePicker = forwardRef(
|
|
|
1105
1648
|
const dateVal = currentValue;
|
|
1106
1649
|
if (dateVal instanceof Date) setCalendarMonth(dateVal);
|
|
1107
1650
|
}, [open]);
|
|
1651
|
+
const timezoneLabel = showTime && !onTimezoneChange ? (timezoneLabelProp ?? getTimezoneLabel(timezone)) || null : null;
|
|
1108
1652
|
const hasField = !!(label || error || helpText);
|
|
1109
1653
|
const testIdPrefix = buildTestIdPrefix(dataTestId, label);
|
|
1110
1654
|
const showFooter = showTime || needConfirm || !!onTimezoneChange;
|
|
1111
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
|
+
};
|
|
1112
1665
|
const defaultStartMonth = new Date(
|
|
1113
1666
|
todayDate.getFullYear() - CALENDAR_NAVIGATION_YEAR_RANGE,
|
|
1114
1667
|
0,
|
|
@@ -1207,99 +1760,110 @@ const DatePicker = forwardRef(
|
|
|
1207
1760
|
onPointerDownOutside: (e) => e.preventDefault(),
|
|
1208
1761
|
onInteractOutside: (e) => e.preventDefault(),
|
|
1209
1762
|
children: [
|
|
1210
|
-
/* @__PURE__ */
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
new Date(year, calendarMonth.getMonth(), 1)
|
|
1218
|
-
),
|
|
1219
|
-
onSelect: handleGridPickerSelect,
|
|
1220
|
-
isDisabled: (date) => isMonthDisabled(date, normalizedMinDate, normalizedMaxDate)
|
|
1221
|
-
}
|
|
1222
|
-
) : type === "date" && picker === "year" ? /* @__PURE__ */ jsx(
|
|
1223
|
-
YearPicker,
|
|
1224
|
-
{
|
|
1225
|
-
value: currentValue ?? null,
|
|
1226
|
-
displayedYear: calendarMonth.getFullYear(),
|
|
1227
|
-
onYearChange: (year) => setCalendarMonth(new Date(year, 0, 1)),
|
|
1228
|
-
onSelect: handleGridPickerSelect,
|
|
1229
|
-
isDisabled: (date) => isYearDisabled(date, normalizedMinDate, normalizedMaxDate)
|
|
1230
|
-
}
|
|
1231
|
-
) : type === "date" ? /* @__PURE__ */ jsx(
|
|
1232
|
-
Calendar$1,
|
|
1233
|
-
{
|
|
1234
|
-
mode: "single",
|
|
1235
|
-
captionLayout: "dropdown",
|
|
1236
|
-
today: todayDate,
|
|
1237
|
-
month: calendarMonth,
|
|
1238
|
-
onMonthChange: setCalendarMonth,
|
|
1239
|
-
selected: calendarSelected(),
|
|
1240
|
-
onSelect: handleSingleDateSelectWithPicker,
|
|
1241
|
-
disabled: calendarDisabled,
|
|
1242
|
-
weekStartsOn,
|
|
1243
|
-
classNames: weekRowHoverClassName ? { week: cn("mt-2 flex w-full", weekRowHoverClassName) } : void 0,
|
|
1244
|
-
...weekHighlightProps,
|
|
1245
|
-
startMonth: calendarStartMonth,
|
|
1246
|
-
endMonth: calendarEndMonth
|
|
1247
|
-
}
|
|
1248
|
-
) : showTime ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1249
|
-
/* @__PURE__ */ jsx(
|
|
1250
|
-
RangeCalendar,
|
|
1251
|
-
{
|
|
1252
|
-
todayDate,
|
|
1253
|
-
weekStartsOn,
|
|
1254
|
-
month: calendarMonth,
|
|
1255
|
-
onMonthChange: setCalendarMonth,
|
|
1256
|
-
selected: memoizedRangeSelected,
|
|
1257
|
-
onSelect: handleRangeSelect,
|
|
1258
|
-
disabled: calendarDisabled,
|
|
1259
|
-
startMonth: calendarStartMonth,
|
|
1260
|
-
endMonth: calendarEndMonth,
|
|
1261
|
-
onHoverIn: handleHoverIn,
|
|
1262
|
-
onHoverOut: handleHoverOut,
|
|
1263
|
-
numberOfMonths: 1
|
|
1264
|
-
}
|
|
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)
|
|
1265
1770
|
),
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
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
|
+
),
|
|
1303
1867
|
showFooter && /* @__PURE__ */ jsx(
|
|
1304
1868
|
DatePickerFooter,
|
|
1305
1869
|
{
|
|
@@ -1308,6 +1872,7 @@ const DatePicker = forwardRef(
|
|
|
1308
1872
|
timezone,
|
|
1309
1873
|
onTimezoneChange,
|
|
1310
1874
|
nowDisabled: calendarDisabled(todayDate),
|
|
1875
|
+
showNow,
|
|
1311
1876
|
onNow: handleNow,
|
|
1312
1877
|
onOk: handleOk,
|
|
1313
1878
|
okLabel: type === "range" && (showTime || needConfirm) && rangeSelectionStep === "from" ? "Next" : "OK"
|
|
@@ -1344,4 +1909,4 @@ const DatePicker = forwardRef(
|
|
|
1344
1909
|
DatePicker.displayName = "DatePicker";
|
|
1345
1910
|
|
|
1346
1911
|
export { DatePicker as D };
|
|
1347
|
-
//# sourceMappingURL=DatePicker-
|
|
1912
|
+
//# sourceMappingURL=DatePicker-BC9AOZJ2.js.map
|