Skip to content

Commit 2a3198e

Browse files
Merge pull request #6113 from Hacker0x01/fix/issue-5459-partial-date-navigation
fix: Navigate calendar view when typing partial dates (#5459)
2 parents 5634c50 + 07acf3f commit 2a3198e

File tree

3 files changed

+139
-0
lines changed

3 files changed

+139
-0
lines changed

src/date_utils.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,40 @@ export function parseDate(
150150
return null;
151151
}
152152

153+
/**
154+
* Parses a partial date string for calendar navigation purposes.
155+
* Unlike parseDate, this function attempts to extract whatever date
156+
* information is available (year, month) from a partial input,
157+
* returning a date suitable for navigating the calendar view.
158+
*
159+
* @param value - The date string to parse.
160+
* @param refDate - The reference date to use for missing components.
161+
* @returns - A date for navigation or null if no date info could be extracted.
162+
*/
163+
export function parseDateForNavigation(
164+
value: string,
165+
refDate: Date = newDate(),
166+
): Date | null {
167+
if (!value) return null;
168+
169+
// Try to extract a 4-digit year from the input
170+
const yearMatch = value.match(/\b(1\d{3}|2\d{3})\b/);
171+
if (!yearMatch || !yearMatch[1]) return null;
172+
173+
const year = parseInt(yearMatch[1], 10);
174+
175+
// Try to extract a month (1-12) from the input
176+
// Look for patterns like "03/", "/03", "03-", "-03" or standalone "03" at start
177+
const monthMatch = value.match(/(?:^|[/\-\s])?(0?[1-9]|1[0-2])(?:[/\-\s]|$)/);
178+
const month =
179+
monthMatch && monthMatch[1]
180+
? parseInt(monthMatch[1], 10) - 1
181+
: refDate.getMonth();
182+
183+
// Return a date with the extracted year and month, using day 1
184+
return new Date(year, month, 1);
185+
}
186+
153187
// ** Date "Reflection" **
154188

155189
export { isDate, set };

src/index.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
getEffectiveMinDate,
2929
getEffectiveMaxDate,
3030
parseDate,
31+
parseDateForNavigation,
3132
formatDate,
3233
safeDateFormat,
3334
safeDateRangeFormat,
@@ -764,6 +765,21 @@ export class DatePicker extends Component<DatePickerProps, DatePickerState> {
764765
// Update selection if either (1) date was successfully parsed, or (2) input field is empty
765766
if (date || !value) {
766767
this.setSelected(date, event, true);
768+
} else if (!this.props.inline) {
769+
// If full date parsing failed but we have partial input,
770+
// try to extract date info for calendar navigation
771+
const navDate = parseDateForNavigation(
772+
value,
773+
this.state.preSelection ?? undefined,
774+
);
775+
// Only update preSelection if navDate is valid and within min/max bounds
776+
if (
777+
navDate &&
778+
(!this.props.minDate || !isBefore(navDate, this.props.minDate)) &&
779+
(!this.props.maxDate || !isAfter(navDate, this.props.maxDate))
780+
) {
781+
this.setState({ preSelection: navDate });
782+
}
767783
}
768784
}
769785
};

src/test/datepicker_test.test.tsx

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2466,6 +2466,95 @@ describe("DatePicker", () => {
24662466
expect(instance!.state.preSelection?.getMonth()).toBe(1); // February (0-indexed)
24672467
expect(instance!.state.preSelection?.getDate()).toBe(10);
24682468
});
2469+
it("should update calendar view when typing a partial date (year only)", () => {
2470+
let instance: DatePicker | null = null;
2471+
2472+
render(
2473+
<DatePicker
2474+
ref={(node) => {
2475+
instance = node;
2476+
}}
2477+
selected={newDate("2024-06-15")}
2478+
onChange={jest.fn()}
2479+
dateFormat="MM/dd/yyyy"
2480+
open
2481+
/>,
2482+
);
2483+
expect(instance).toBeTruthy();
2484+
2485+
// Verify initial calendar shows June 2024
2486+
expect(instance!.state.preSelection?.getFullYear()).toBe(2024);
2487+
expect(instance!.state.preSelection?.getMonth()).toBe(5); // June
2488+
2489+
// Type a partial date - just a year (use 2000 which won't match as a month)
2490+
fireEvent.change(instance!.input!, {
2491+
target: {
2492+
value: "2000",
2493+
},
2494+
});
2495+
2496+
// Calendar should navigate to 2000 with the same month (June) from the selected date
2497+
expect(instance!.state.preSelection?.getFullYear()).toBe(2000);
2498+
expect(instance!.state.preSelection?.getMonth()).toBe(5); // June preserved from refDate
2499+
});
2500+
it("should update calendar view when typing a partial date (month and year)", () => {
2501+
let instance: DatePicker | null = null;
2502+
2503+
render(
2504+
<DatePicker
2505+
ref={(node) => {
2506+
instance = node;
2507+
}}
2508+
selected={newDate("2024-06-15")}
2509+
onChange={jest.fn()}
2510+
dateFormat="MM/dd/yyyy"
2511+
open
2512+
/>,
2513+
);
2514+
expect(instance).toBeTruthy();
2515+
2516+
// Type a partial date - month and year
2517+
fireEvent.change(instance!.input!, {
2518+
target: {
2519+
value: "03/2014",
2520+
},
2521+
});
2522+
2523+
// Calendar should navigate to March 2014
2524+
expect(instance!.state.preSelection?.getFullYear()).toBe(2014);
2525+
expect(instance!.state.preSelection?.getMonth()).toBe(2); // March (0-indexed)
2526+
});
2527+
it("should not update calendar view when typing text without a valid year", () => {
2528+
let instance: DatePicker | null = null;
2529+
2530+
render(
2531+
<DatePicker
2532+
ref={(node) => {
2533+
instance = node;
2534+
}}
2535+
selected={newDate("2024-06-15")}
2536+
onChange={jest.fn()}
2537+
dateFormat="MM/dd/yyyy"
2538+
open
2539+
/>,
2540+
);
2541+
expect(instance).toBeTruthy();
2542+
2543+
// Verify initial calendar shows 2024
2544+
expect(instance!.state.preSelection?.getFullYear()).toBe(2024);
2545+
expect(instance!.state.preSelection?.getMonth()).toBe(5); // June (0-indexed)
2546+
2547+
// Type text without a valid 4-digit year
2548+
fireEvent.change(instance!.input!, {
2549+
target: {
2550+
value: "03/",
2551+
},
2552+
});
2553+
2554+
// Calendar should remain on the original date since no valid year was found
2555+
expect(instance!.state.preSelection?.getFullYear()).toBe(2024);
2556+
expect(instance!.state.preSelection?.getMonth()).toBe(5); // June (0-indexed)
2557+
});
24692558
it("should correctly update the input when the value prop changes", () => {
24702559
let instance: DatePicker | null = null;
24712560
const { rerender } = render(

0 commit comments

Comments
 (0)