Skip to content

fix: Constrain day on blur #8385

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ export class BuddhistCalendar extends GregorianCalendar {
getDaysInMonth(date: AnyCalendarDate): number {
return super.getDaysInMonth(toGregorian(date));
}

balanceDate(): void {}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ export class EthiopicCalendar implements Calendar {
return getDaysInMonth(date.year, date.month);
}

getMaxDays(): number {
return 30
}

getMonthsInYear(): number {
return 13;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ export class GregorianCalendar implements Calendar {
return 12;
}

getMaxDays(): number {
return 31
}

getDaysInYear(date: AnyCalendarDate): number {
return isLeapYear(date.year) ? 366 : 365;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,10 @@ export class HebrewCalendar implements Calendar {
return getDaysInMonth(date.year, date.month);
}

getMaxDays(): number {
return 30;
}

getMonthsInYear(date: AnyCalendarDate): number {
return isLeapYear(date.year) ? 13 : 12;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ export class IslamicCivilCalendar implements Calendar {
return length;
}

getMaxDays(): number {
return 30
}

getMonthsInYear(): number {
return 12;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ export class PersianCalendar implements Calendar {
return isLeapYear ? 30 : 29;
}

getMaxDays(): number {
return 31
}

getEras(): string[] {
return ['AP'];
}
Expand Down
4 changes: 2 additions & 2 deletions packages/@internationalized/date/src/manipulation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ function balanceDay(date: Mutable<AnyCalendarDate>) {

function constrainMonthDay(date: Mutable<AnyCalendarDate>) {
date.month = Math.max(1, Math.min(date.calendar.getMonthsInYear(date), date.month));
date.day = Math.max(1, Math.min(date.calendar.getDaysInMonth(date), date.day));
date.day = Math.max(1, Math.min(date.calendar.getMaxDays(), date.day));
}

export function constrain(date: Mutable<AnyCalendarDate>): void {
Expand Down Expand Up @@ -284,7 +284,7 @@ export function cycleDate(value: CalendarDate | CalendarDateTime, field: DateFie
mutable.month = cycleValue(value.month, amount, 1, value.calendar.getMonthsInYear(value), options?.round);
break;
case 'day':
mutable.day = cycleValue(value.day, amount, 1, value.calendar.getDaysInMonth(value), options?.round);
mutable.day = cycleValue(value.day, amount, 1, value.calendar.getMaxDays(), options?.round);
break;
default:
throw new Error('Unsupported field ' + field);
Expand Down
2 changes: 2 additions & 0 deletions packages/@internationalized/date/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ export interface Calendar {
getDaysInMonth(date: AnyCalendarDate): number,
/** Returns the number of months in the year of the given date. */
getMonthsInYear(date: AnyCalendarDate): number,
/** Returns the maximum day. */
getMaxDays(): number,
/** Returns the number of years in the era of the given date. */
getYearsInEra(date: AnyCalendarDate): number,
/** Returns a list of era identifiers for the calendar. */
Expand Down
4 changes: 2 additions & 2 deletions packages/@react-aria/datepicker/src/useDateField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@ export function useDateField<T extends DateValue>(props: AriaDateFieldOptions<T>
},
onBlurWithin: (e) => {
state.confirmPlaceholder();
if (state.value !== valueOnFocus.current) {
if (state.isValueChanged) {
state.commitValidation();
}
};
props.onBlur?.(e);
},
onFocusWithinChange: props.onFocusChange
Expand Down
35 changes: 22 additions & 13 deletions packages/@react-spectrum/datepicker/test/DateField.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* governing permissions and limitations under the License.
*/

import {act, pointerMap, render as render_, within} from '@react-spectrum/test-utils-internal';
import {act, pointerMap, fireEvent, render as render_, within} from '@react-spectrum/test-utils-internal';
import {Button} from '@react-spectrum/button';
import {CalendarDate, CalendarDateTime, ZonedDateTime} from '@internationalized/date';
import {DateField} from '../';
Expand Down Expand Up @@ -236,6 +236,7 @@ describe('DateField', function () {
);
await user.tab();
await user.keyboard('01011980');
await user.tab();
expect(tree.getByText('Date unavailable.')).toBeInTheDocument();
});

Expand Down Expand Up @@ -369,6 +370,7 @@ describe('DateField', function () {
expect(input).toHaveAttribute('name', 'date');
await user.tab();
await user.keyboard('{ArrowUp}');
await user.tab({shift: true});
expect(getDescription()).toBe('Selected Date: March 3, 2020');
expect(input).toHaveValue('2020-03-03');

Expand Down Expand Up @@ -434,30 +436,36 @@ describe('DateField', function () {
expect(document.activeElement).toBe(within(group).getAllByRole('spinbutton')[0]);

await user.keyboard('[Tab][Tab][ArrowUp]');

expect(getDescription()).toContain('Value must be 2/3/2020 or later.');
expect(input.validity.valid).toBe(true);
expect(input.validity.valid).toBe(false);

await user.tab();

expect(getDescription()).not.toContain('Value must be 2/3/2020 or later.');
expect(input.validity.valid).toBe(true);

await user.tab({shift: true});
await user.keyboard('2025');

expect(getDescription()).not.toContain('Value must be 2/3/2024 or earlier.');
expect(input.validity.valid).toBe(false);
expect(input.validity.valid).toBe(true);

await user.tab();
expect(getDescription()).toContain('Value must be 2/3/2024 or earlier.');
expect(input.validity.valid).toBe(false);

act(() => {getByTestId('form').checkValidity();});
expect(getDescription()).toContain('Value must be 2/3/2024 or earlier.');
expect(document.activeElement).toBe(within(group).getAllByRole('spinbutton')[0]);

await user.keyboard('[Tab][Tab][ArrowDown]');
expect(getDescription()).toContain('Value must be 2/3/2024 or earlier.');
expect(input.validity.valid).toBe(true);
expect(input.validity.valid).toBe(false);
await user.tab();

expect(getDescription()).not.toContain('Value must be 2/3/2024 or earlier.');
expect(input.validity.valid).toBe(true);
});

it('supports validate function', async () => {
Expand All @@ -480,12 +488,11 @@ describe('DateField', function () {
expect(group).toHaveAttribute('aria-describedby');
expect(getDescription()).toContain('Invalid value');
expect(document.activeElement).toBe(within(group).getAllByRole('spinbutton')[0]);

await user.keyboard('[ArrowRight][ArrowRight]2024');

expect(getDescription()).toContain('Invalid value');
expect(input.validity.valid).toBe(true);

expect(input.validity.valid).toBe(false);
await user.tab();

expect(getDescription()).not.toContain('Invalid value');
Expand Down Expand Up @@ -605,10 +612,12 @@ describe('DateField', function () {
await user.keyboard('232023');

expect(group).toHaveAttribute('aria-describedby');
expect(input.validity.valid).toBe(true);
expect(input.validity.valid).toBe(false);

await user.tab();
expect(getDescription()).not.toContain('Constraints not satisfied');
expect(group).toHaveAttribute('aria-describedby');
expect(input.validity.valid).toBe(true);
});
});

Expand All @@ -626,13 +635,13 @@ describe('DateField', function () {
let getDescription = () => group.getAttribute('aria-describedby').split(' ').map(d => document.getElementById(d).textContent).join(' ');
expect(getDescription()).toContain('Value must be 2/3/2020 or later.');

await user.keyboard('[Tab][Tab][Tab][ArrowUp]');
await user.keyboard('[Tab][Tab][Tab][ArrowUp][Tab]');
expect(getDescription()).not.toContain('Value must be 2/3/2020 or later.');

await user.keyboard('[ArrowUp][ArrowUp][ArrowUp][ArrowUp][ArrowUp]');
await user.keyboard('[Tab][Tab][Tab][ArrowUp][ArrowUp][ArrowUp][ArrowUp][ArrowUp][Tab]');
expect(getDescription()).toContain('Value must be 2/3/2024 or earlier.');

await user.keyboard('[ArrowDown]');
await user.keyboard('[Tab][Tab][Tab][ArrowDown][Tab]');
expect(getDescription()).not.toContain('Value must be 2/3/2024 or earlier.');
});

Expand All @@ -650,7 +659,7 @@ describe('DateField', function () {
let getDescription = () => group.getAttribute('aria-describedby').split(' ').map(d => document.getElementById(d).textContent).join(' ');
expect(getDescription()).toContain('Invalid value');

await user.keyboard('[Tab][ArrowRight][ArrowRight]2024');
await user.keyboard('[Tab][ArrowRight][ArrowRight]2024[Tab]');
expect(getDescription()).not.toContain('Invalid value');
});

Expand Down
Loading