This commit is contained in:
@@ -12,7 +12,6 @@ import {
|
|||||||
type FocusEvent,
|
type FocusEvent,
|
||||||
type FocusEventHandler,
|
type FocusEventHandler,
|
||||||
type KeyboardEvent as ReactKeyboardEvent,
|
type KeyboardEvent as ReactKeyboardEvent,
|
||||||
type MutableRefObject,
|
|
||||||
type ReactNode,
|
type ReactNode,
|
||||||
type Ref,
|
type Ref,
|
||||||
useCallback,
|
useCallback,
|
||||||
@@ -143,7 +142,7 @@ function createDateTimeFromPickerValue(value: PickerValue): Date {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function startOfDay(value: Date): Date {
|
function startOfDay(value: Date): Date {
|
||||||
const candidate = new Date(value.getTime());
|
const candidate = new Date(value);
|
||||||
candidate.setHours(0, 0, 0, 0);
|
candidate.setHours(0, 0, 0, 0);
|
||||||
return candidate;
|
return candidate;
|
||||||
}
|
}
|
||||||
@@ -256,7 +255,7 @@ function assignRef(ref: Ref<HTMLInputElement> | undefined, node: HTMLInputElemen
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
(ref as MutableRefObject<HTMLInputElement | null>).current = node;
|
(ref as { current: HTMLInputElement | null }).current = node;
|
||||||
}
|
}
|
||||||
|
|
||||||
function tokenizeFormat(format: string): RawFormatPart[] {
|
function tokenizeFormat(format: string): RawFormatPart[] {
|
||||||
@@ -616,7 +615,11 @@ function isWithinRange(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function applySegmentDigits(baseValue: PickerValue, kind: SegmentKind, digits: string): PickerValue {
|
function applySegmentDigits(
|
||||||
|
baseValue: PickerValue,
|
||||||
|
kind: SegmentKind,
|
||||||
|
digits: string,
|
||||||
|
): PickerValue {
|
||||||
const parsedDigits = Number(digits);
|
const parsedDigits = Number(digits);
|
||||||
if (!Number.isFinite(parsedDigits)) {
|
if (!Number.isFinite(parsedDigits)) {
|
||||||
return clonePickerValue(baseValue);
|
return clonePickerValue(baseValue);
|
||||||
@@ -644,7 +647,8 @@ function applySegmentDigits(baseValue: PickerValue, kind: SegmentKind, digits: s
|
|||||||
const maxDayForCurrentMonth = daysInMonth(year, month);
|
const maxDayForCurrentMonth = daysInMonth(year, month);
|
||||||
day = clampNumber(day, 1, maxDayForCurrentMonth);
|
day = clampNumber(day, 1, maxDayForCurrentMonth);
|
||||||
|
|
||||||
const nextDate = createValidatedDate(year, month, day) ?? createDateAtLocalMidnight(year, month - 1, day);
|
const nextDate =
|
||||||
|
createValidatedDate(year, month, day) ?? createDateAtLocalMidnight(year, month - 1, day);
|
||||||
return {
|
return {
|
||||||
date: nextDate,
|
date: nextDate,
|
||||||
hour,
|
hour,
|
||||||
@@ -770,9 +774,11 @@ export function DatePicker({
|
|||||||
const inputWrapperRef = useRef<HTMLDivElement | null>(null);
|
const inputWrapperRef = useRef<HTMLDivElement | null>(null);
|
||||||
const popupRef = useRef<HTMLDivElement | null>(null);
|
const popupRef = useRef<HTMLDivElement | null>(null);
|
||||||
const changeHandledRef = useRef(false);
|
const changeHandledRef = useRef(false);
|
||||||
const bufferedDigitsRef = useRef<{ segmentIndex: number; digits: string; timestamp: number } | null>(
|
const bufferedDigitsRef = useRef<{
|
||||||
null,
|
segmentIndex: number;
|
||||||
);
|
digits: string;
|
||||||
|
timestamp: number;
|
||||||
|
} | null>(null);
|
||||||
const activeSegmentIndexRef = useRef(0);
|
const activeSegmentIndexRef = useRef(0);
|
||||||
const pendingSelectionTimerRef = useRef<number | null>(null);
|
const pendingSelectionTimerRef = useRef<number | null>(null);
|
||||||
|
|
||||||
@@ -897,7 +903,7 @@ export function DatePicker({
|
|||||||
const monthGrid = useMemo(() => buildMonthGrid(viewMonth, weekStart), [viewMonth, weekStart]);
|
const monthGrid = useMemo(() => buildMonthGrid(viewMonth, weekStart), [viewMonth, weekStart]);
|
||||||
|
|
||||||
const recalculatePopupPosition = useCallback(() => {
|
const recalculatePopupPosition = useCallback(() => {
|
||||||
if (!isOpen || !inputWrapperRef.current || !popupRef.current || typeof window === 'undefined') {
|
if (!isOpen || !inputWrapperRef.current || !popupRef.current || !globalThis.window) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -920,7 +926,10 @@ export function DatePicker({
|
|||||||
? anchorRect.top - popupRect.height - POPUP_GAP
|
? anchorRect.top - popupRect.height - POPUP_GAP
|
||||||
: anchorRect.bottom + POPUP_GAP;
|
: anchorRect.bottom + POPUP_GAP;
|
||||||
|
|
||||||
top = Math.max(POPUP_MARGIN, Math.min(top, viewportHeight - POPUP_MARGIN - popupRect.height));
|
top = Math.max(
|
||||||
|
POPUP_MARGIN,
|
||||||
|
Math.min(top, viewportHeight - POPUP_MARGIN - popupRect.height),
|
||||||
|
);
|
||||||
|
|
||||||
setPopupPosition({
|
setPopupPosition({
|
||||||
top,
|
top,
|
||||||
@@ -937,7 +946,7 @@ export function DatePicker({
|
|||||||
|
|
||||||
recalculatePopupPosition();
|
recalculatePopupPosition();
|
||||||
|
|
||||||
if (typeof window === 'undefined') {
|
if (!globalThis.window) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -965,7 +974,10 @@ export function DatePicker({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (popupRef.current?.contains(eventTarget) || inputWrapperRef.current?.contains(eventTarget)) {
|
if (
|
||||||
|
popupRef.current?.contains(eventTarget) ||
|
||||||
|
inputWrapperRef.current?.contains(eventTarget)
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -992,7 +1004,7 @@ export function DatePicker({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
if (pendingSelectionTimerRef.current != null) {
|
if (pendingSelectionTimerRef.current != null) {
|
||||||
window.clearTimeout(pendingSelectionTimerRef.current);
|
globalThis.window.clearTimeout(pendingSelectionTimerRef.current);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
@@ -1007,15 +1019,15 @@ export function DatePicker({
|
|||||||
const clampedIndex = clampNumber(segmentIndex, 0, segments.length - 1);
|
const clampedIndex = clampNumber(segmentIndex, 0, segments.length - 1);
|
||||||
activeSegmentIndexRef.current = clampedIndex;
|
activeSegmentIndexRef.current = clampedIndex;
|
||||||
|
|
||||||
if (typeof window === 'undefined') {
|
if (!globalThis.window) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pendingSelectionTimerRef.current != null) {
|
if (pendingSelectionTimerRef.current != null) {
|
||||||
window.clearTimeout(pendingSelectionTimerRef.current);
|
globalThis.window.clearTimeout(pendingSelectionTimerRef.current);
|
||||||
}
|
}
|
||||||
|
|
||||||
pendingSelectionTimerRef.current = window.setTimeout(() => {
|
pendingSelectionTimerRef.current = globalThis.window.setTimeout(() => {
|
||||||
const inputNode = internalInputRef.current;
|
const inputNode = internalInputRef.current;
|
||||||
const targetSegment = segments[clampedIndex];
|
const targetSegment = segments[clampedIndex];
|
||||||
if (!inputNode || !targetSegment) {
|
if (!inputNode || !targetSegment) {
|
||||||
@@ -1109,7 +1121,8 @@ export function DatePicker({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseValue = parsePickerValueWithFormat(displayValue, formatConfig) ?? clampedSelectedValue;
|
const baseValue =
|
||||||
|
parsePickerValueWithFormat(displayValue, formatConfig) ?? clampedSelectedValue;
|
||||||
const nextUnclamped = applySegmentDigits(baseValue, segment.kind, digits);
|
const nextUnclamped = applySegmentDigits(baseValue, segment.kind, digits);
|
||||||
const nextClamped = clampPickerToRange(
|
const nextClamped = clampPickerToRange(
|
||||||
nextUnclamped,
|
nextUnclamped,
|
||||||
@@ -1145,7 +1158,7 @@ export function DatePicker({
|
|||||||
(segmentIndex: number, moveToNext: boolean) => {
|
(segmentIndex: number, moveToNext: boolean) => {
|
||||||
const buffered = bufferedDigitsRef.current;
|
const buffered = bufferedDigitsRef.current;
|
||||||
const segment = formatConfig.segments[segmentIndex];
|
const segment = formatConfig.segments[segmentIndex];
|
||||||
if (!buffered || buffered.segmentIndex !== segmentIndex || !segment) {
|
if (buffered?.segmentIndex !== segmentIndex || !segment) {
|
||||||
if (moveToNext) {
|
if (moveToNext) {
|
||||||
selectSegment(segmentIndex + 1);
|
selectSegment(segmentIndex + 1);
|
||||||
}
|
}
|
||||||
@@ -1234,7 +1247,12 @@ export function DatePicker({
|
|||||||
hour: selectedHour,
|
hour: selectedHour,
|
||||||
minute: selectedMinute,
|
minute: selectedMinute,
|
||||||
};
|
};
|
||||||
const nextValue = clampPickerToRange(candidate, normalizedMinValue, normalizedMaxValue, type);
|
const nextValue = clampPickerToRange(
|
||||||
|
candidate,
|
||||||
|
normalizedMinValue,
|
||||||
|
normalizedMaxValue,
|
||||||
|
type,
|
||||||
|
);
|
||||||
commitValue(formatPickerValueWithFormat(nextValue, formatConfig));
|
commitValue(formatPickerValueWithFormat(nextValue, formatConfig));
|
||||||
setViewMonth(startOfMonth(normalizedDate));
|
setViewMonth(startOfMonth(normalizedDate));
|
||||||
},
|
},
|
||||||
@@ -1341,7 +1359,12 @@ export function DatePicker({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const clamped = clampPickerToRange(parsed, normalizedMinValue, normalizedMaxValue, type);
|
const clamped = clampPickerToRange(
|
||||||
|
parsed,
|
||||||
|
normalizedMinValue,
|
||||||
|
normalizedMaxValue,
|
||||||
|
type,
|
||||||
|
);
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
commitValue(formatPickerValueWithFormat(clamped, formatConfig));
|
commitValue(formatPickerValueWithFormat(clamped, formatConfig));
|
||||||
},
|
},
|
||||||
@@ -1663,7 +1686,11 @@ export function DatePicker({
|
|||||||
<div className="datepicker-time-root">
|
<div className="datepicker-time-root">
|
||||||
<div className="datepicker-time-column">
|
<div className="datepicker-time-column">
|
||||||
<span className="datepicker-time-title">Hours</span>
|
<span className="datepicker-time-title">Hours</span>
|
||||||
<div className="datepicker-time-list" role="listbox" aria-label="Hours">
|
<div
|
||||||
|
className="datepicker-time-list"
|
||||||
|
role="listbox"
|
||||||
|
aria-label="Hours"
|
||||||
|
>
|
||||||
{HOURS.map((hour) => {
|
{HOURS.map((hour) => {
|
||||||
const hourDisabled = !isHourSelectableForRange(
|
const hourDisabled = !isHourSelectableForRange(
|
||||||
selectedDate,
|
selectedDate,
|
||||||
@@ -1717,7 +1744,8 @@ export function DatePicker({
|
|||||||
type="button"
|
type="button"
|
||||||
className={joinClassNames(
|
className={joinClassNames(
|
||||||
'datepicker-time-option',
|
'datepicker-time-option',
|
||||||
minute === selectedMinute && 'is-selected',
|
minute === selectedMinute &&
|
||||||
|
'is-selected',
|
||||||
)}
|
)}
|
||||||
onClick={() => handleMinuteCommit(minute)}
|
onClick={() => handleMinuteCommit(minute)}
|
||||||
disabled={minuteDisabled}
|
disabled={minuteDisabled}
|
||||||
|
|||||||
Reference in New Issue
Block a user