diff --git a/packages/@adobe/react-spectrum/test/datepicker/DateField.test.js b/packages/@adobe/react-spectrum/test/datepicker/DateField.test.js
index 8949e1cb327..91f0ae4cb53 100644
--- a/packages/@adobe/react-spectrum/test/datepicker/DateField.test.js
+++ b/packages/@adobe/react-spectrum/test/datepicker/DateField.test.js
@@ -764,6 +764,142 @@ describe('DateField', function () {
await user.tab();
expect(getDescription()).not.toContain('Constraints not satisfied');
});
+
+ it('should signal valueMissing when a complete date is made partial by clearing a segment (Bug #9624)', async () => {
+ // Regression (per devongovett's direction in #9624): a partially-filled required
+ // DateField should be marked invalid on blur. onChange(null) is deliberately NOT
+ // fired for partial values; getValidationResult compensates by surfacing the
+ // localized "Please enter a value." message via builtinValidation.
+ let {getByRole} = render(
+
+
+
+ );
+
+ let group = getByRole('group');
+ let input = document.querySelector('input[name=date]');
+
+ // Fill a complete valid date: 4/28/2026
+ await user.tab();
+ await user.keyboard('4');
+ await user.keyboard('28');
+ await user.keyboard('2026');
+ expect(input.validity.valid).toBe(true);
+
+ // Refocus the month and clear it
+ let segments = within(group).getAllByRole('spinbutton');
+ act(() => {
+ segments[0].focus();
+ });
+ await user.keyboard('{Backspace}');
+ expect(segments[0]).toHaveAttribute('aria-valuetext', 'Empty');
+
+ // Tab forward past day and year to exit the group
+ await user.tab();
+ await user.tab();
+ await user.tab();
+
+ // The hidden input should reflect missing value and validation should surface
+ expect(input.validity.valid).toBe(false);
+ let getDescriptionAfter = () =>
+ (group.getAttribute('aria-describedby') || '')
+ .split(' ')
+ .map(d => document.getElementById(d)?.textContent || '')
+ .join(' ');
+ expect(getDescriptionAfter()).toContain('Please enter a value.');
+ });
+
+ it('should clear the validation error after a partial field is completed (Bug #9624 round-trip)', async () => {
+ // Locks the lifecycle: complete -> clear segment (error) -> re-complete with a new
+ // valid value (error clears). Verifies the fix does not leave the field stuck invalid.
+ let {getByRole} = render(
+
+
+
+ );
+
+ let group = getByRole('group');
+ let input = document.querySelector('input[name=date]');
+
+ await user.tab();
+ await user.keyboard('4');
+ await user.keyboard('28');
+ await user.keyboard('2026');
+
+ let segments = within(group).getAllByRole('spinbutton');
+ act(() => {
+ segments[0].focus();
+ });
+ await user.keyboard('{Backspace}');
+ await user.tab();
+ await user.tab();
+ await user.tab();
+
+ let getDescription = () =>
+ (group.getAttribute('aria-describedby') || '')
+ .split(' ')
+ .map(d => document.getElementById(d)?.textContent || '')
+ .join(' ');
+ expect(getDescription()).toContain('Please enter a value.');
+
+ // Re-focus month and complete the date with a different valid value
+ act(() => {
+ segments[0].focus();
+ });
+ await user.keyboard('5');
+ await user.tab();
+ await user.tab();
+ await user.tab();
+
+ expect(input.validity.valid).toBe(true);
+ expect(getDescription()).not.toContain('Please enter a value.');
+ });
+
+ it('should signal validation for partial values regardless of isRequired (Bug #9958)', async () => {
+ // Per LFDanLu: "invalidate the field if the date is incomplete in general."
+ // The earlier scope-guard test asserted the opposite; updated to lock the new
+ // behavior — partial values are flagged invalid even without isRequired so that
+ // min/max/unavailable/validate configurations also block submission.
+ let {getByRole} = render(
+
+
+
+ );
+
+ let group = getByRole('group');
+ let input = document.querySelector('input[name=date]');
+ let segments = within(group).getAllByRole('spinbutton');
+
+ act(() => {
+ segments[0].focus();
+ });
+ await user.keyboard('{Backspace}');
+ await user.keyboard('{Backspace}');
+ expect(segments[0]).toHaveAttribute('aria-valuetext', 'Empty');
+
+ await user.tab();
+ await user.tab();
+ await user.tab();
+
+ expect(input.validity.valid).toBe(false);
+ let description = (group.getAttribute('aria-describedby') || '')
+ .split(' ')
+ .map(d => document.getElementById(d)?.textContent || '')
+ .join(' ');
+ expect(description).toContain('Please enter a value.');
+ });
});
describe('validationBehavior=aria', () => {
@@ -850,6 +986,38 @@ describe('DateField', function () {
await user.keyboard('[Tab][ArrowRight][ArrowRight]2024[Tab]');
expect(getDescription()).not.toContain('Invalid value');
});
+
+ it('should clear the hidden input value when partial in aria mode (Bug #9624)', async () => {
+ // Aria-mode counterpart to the native-mode #9624 regression: the hidden input must
+ // reflect the partial display state regardless of validationBehavior so any consumer
+ // (e.g. FormData) sees the missing value.
+ let {getByRole} = render(
+
+
+
+ );
+
+ let group = getByRole('group');
+ let input = document.querySelector('input[name=date]');
+ expect(input).toHaveValue('2026-04-28');
+
+ let segments = within(group).getAllByRole('spinbutton');
+ act(() => {
+ segments[0].focus();
+ });
+ await user.keyboard('{Backspace}');
+ expect(segments[0]).toHaveAttribute('aria-valuetext', 'Empty');
+
+ expect(input).toHaveValue('');
+ });
});
});
});
diff --git a/packages/@adobe/react-spectrum/test/datepicker/DatePicker.test.js b/packages/@adobe/react-spectrum/test/datepicker/DatePicker.test.js
index bfbbb5eba8e..e597cfe4338 100644
--- a/packages/@adobe/react-spectrum/test/datepicker/DatePicker.test.js
+++ b/packages/@adobe/react-spectrum/test/datepicker/DatePicker.test.js
@@ -3149,6 +3149,247 @@ describe('DatePicker', function () {
expect(getDescription()).not.toContain('Constraints not satisfied');
});
+ it('should signal validation when a complete date is made partial by clearing the month segment (Bug #9958)', async () => {
+ // Regression: clearing the month segment of a previously-complete date does not
+ // change the committed state.value. Without isRequired alone the form had no
+ // signal to block submit. Now getValidationResult treats partial display state as
+ // invalid via the lifted-up isValuePartial flag in useDatePickerState, surfacing
+ // a localized error message.
+ let {getByRole} = render(
+
+
+
+ );
+
+ let group = getByRole('group');
+ let input = document.querySelector('input[name=date]');
+
+ // Fill a complete valid date: 4/28/2026
+ await user.tab();
+ await user.keyboard('4');
+ await user.keyboard('28');
+ await user.keyboard('2026');
+ expect(input.validity.valid).toBe(true);
+
+ // Refocus the month segment and clear it (single Backspace for single-digit '4')
+ let segments = within(group).getAllByRole('spinbutton');
+ act(() => {
+ segments[0].focus();
+ });
+ await user.keyboard('{Backspace}');
+ expect(segments[0]).toHaveAttribute('aria-valuetext', 'Empty');
+
+ // Tab forward out of the group (month -> day -> year -> calendar trigger)
+ await user.tab();
+ await user.tab();
+ await user.tab();
+
+ // Field is now partial — expect validation error surfaced
+ let getDescription = () =>
+ (group.getAttribute('aria-describedby') || '')
+ .split(' ')
+ .map(d => document.getElementById(d)?.textContent || '')
+ .join(' ');
+ expect(input.validity.valid).toBe(false);
+ expect(getDescription()).toContain('Please enter a value.');
+ });
+
+ it('should signal validation when a date with minValue is made partial without isRequired (Bug #9958)', async () => {
+ // The exact scenario the user reported: DatePicker forms in the docs using only
+ // minValue/maxValue / isDateUnavailable / validate (no isRequired) — clearing month
+ // or day must still block submission and surface an error.
+ let {getByRole} = render(
+
+
+
+ );
+
+ let group = getByRole('group');
+ let input = document.querySelector('input[name=date]');
+ let segments = within(group).getAllByRole('spinbutton');
+
+ // Clear the month segment (single-digit '4' goes straight to null).
+ act(() => {
+ segments[0].focus();
+ });
+ await user.keyboard('{Backspace}');
+ expect(segments[0]).toHaveAttribute('aria-valuetext', 'Empty');
+
+ await user.tab();
+ await user.tab();
+ await user.tab();
+
+ let description = (group.getAttribute('aria-describedby') || '')
+ .split(' ')
+ .map(d => document.getElementById(d)?.textContent || '')
+ .join(' ');
+
+ expect(input.validity.valid).toBe(false);
+ expect(description).toContain('Please enter a value.');
+ });
+
+ it('should surface the descriptive constraint error (not the generic incomplete message) when a min-violating value is made partial (Bug #9958)', async () => {
+ // A partial value that still violates a constraint (minValue/maxValue/unavailableDate)
+ // surfaces the descriptive error ("Value must be … or later.") rather than the generic
+ // fallback ("Please enter a value."). The generic fallback only appears when the partial
+ // value has no constraint violation — e.g. a field with no min/max where the user clears
+ // a segment.
+ let {getByRole} = render(
+
+
+
+ );
+
+ let group = getByRole('group');
+ let input = document.querySelector('input[name=date]');
+ let segments = within(group).getAllByRole('spinbutton');
+
+ act(() => {
+ segments[0].focus();
+ });
+ await user.keyboard('{Backspace}');
+ expect(segments[0]).toHaveAttribute('aria-valuetext', 'Empty');
+
+ await user.tab();
+ await user.tab();
+ await user.tab();
+
+ let description = (group.getAttribute('aria-describedby') || '')
+ .split(' ')
+ .map(d => document.getElementById(d)?.textContent || '')
+ .join(' ');
+
+ expect(input.validity.valid).toBe(false);
+ expect(description).toContain('Value must be 1/1/2030 or later.');
+ expect(description).not.toContain('Please enter a value.');
+ });
+
+ it('should clear the partial-value error on the first calendar selection that completes the date (Bug #9958 follow-up)', async () => {
+ // Repro of the "takes two interactions" bug: after a partial value surfaces the error,
+ // selecting a complete valid date from the calendar must clear it on the FIRST selection.
+ // The parent's isValuePartial is lifted from the field via a useEffect (one render
+ // behind), but selectDate commits validation synchronously — so the stale partial error
+ // got committed and stuck until a second interaction.
+ let {getByRole, getAllByRole} = render(
+
+
+
+ );
+
+ let group = getByRole('group');
+ let input = document.querySelector('input[name=date]');
+ let segments = within(group).getAllByRole('spinbutton');
+
+ // Clear the month segment, then blur the field -> partial error appears.
+ act(() => {
+ segments[0].focus();
+ });
+ await user.keyboard('{Backspace}');
+ expect(segments[0]).toHaveAttribute('aria-valuetext', 'Empty');
+ await user.tab();
+ await user.tab();
+ await user.tab();
+
+ let getDescription = () =>
+ (group.getAttribute('aria-describedby') || '')
+ .split(' ')
+ .map(d => document.getElementById(d)?.textContent || '')
+ .join(' ');
+ expect(getDescription()).toContain('Please enter a value.');
+
+ // Open the calendar and select a complete, valid date in a single click.
+ await user.click(getByRole('button'));
+ let cells = getAllByRole('gridcell');
+ let selected = cells.find(cell => cell.getAttribute('aria-selected') === 'true');
+ await user.click(selected.nextSibling.children[0]);
+
+ // The value is now complete and valid -> the error must be gone after ONE selection.
+ expect(input.validity.valid).toBe(true);
+ expect(getDescription()).not.toContain('Please enter a value.');
+ });
+
+ it('should signal validation when a time segment is cleared on a CalendarDateTime value (Bug #9801)', async () => {
+ // Issue #9801 is TimeField — the same IncompleteDate partial-validation path applies
+ // when a DatePicker has CalendarDateTime granularity and a time segment is cleared.
+ // The committed value is unchanged but the display buffer is partial; the field must
+ // be invalid and surface "Please enter a value." until the segment is refilled.
+ let {getByRole} = render(
+
+
+
+ );
+
+ let group = getByRole('group');
+ let input = document.querySelector('input[name=date]');
+ let segments = within(group).getAllByRole('spinbutton');
+
+ // Confirm the full value is valid.
+ expect(input.validity.valid).toBe(true);
+
+ // Clear the hour segment (10 → two Backspaces needed for two digits).
+ act(() => {
+ segments[3].focus();
+ });
+ await user.keyboard('{Backspace}{Backspace}');
+ expect(segments[3]).toHaveAttribute('aria-valuetext', 'Empty');
+
+ // Tab out of the group so blur-triggered validation runs.
+ await user.tab();
+ await user.tab();
+ await user.tab();
+ await user.tab();
+
+ let getDescription = () =>
+ (group.getAttribute('aria-describedby') || '')
+ .split(' ')
+ .map(d => document.getElementById(d)?.textContent || '')
+ .join(' ');
+ expect(input.validity.valid).toBe(false);
+ expect(getDescription()).toContain('Please enter a value.');
+
+ // Refill the hour segment and confirm the error clears.
+ act(() => {
+ segments[3].focus();
+ });
+ await user.keyboard('10');
+ await user.tab();
+ expect(input.validity.valid).toBe(true);
+ });
+
it('supports minValue and maxValue', async () => {
let {getByRole, getByTestId} = render(
diff --git a/packages/@adobe/react-spectrum/test/datepicker/DateRangePicker.test.js b/packages/@adobe/react-spectrum/test/datepicker/DateRangePicker.test.js
index 13e69ebfa9c..297c646026a 100644
--- a/packages/@adobe/react-spectrum/test/datepicker/DateRangePicker.test.js
+++ b/packages/@adobe/react-spectrum/test/datepicker/DateRangePicker.test.js
@@ -1924,6 +1924,53 @@ describe('DateRangePicker', function () {
expect(getDescription()).not.toContain('Constraints not satisfied');
});
+ it('should signal validation when an endpoint date is made partial by clearing a segment (Bug #9958 followup)', async () => {
+ // Locks transitive coverage of DateRangePicker: each endpoint has its own
+ // DateFieldState with isValuePartial. Clearing a segment of either endpoint should
+ // fire validation on blur.
+ let {getByRole} = render(
+
+
+
+ );
+
+ let group = getByRole('group');
+ let segments = within(group).getAllByRole('spinbutton');
+ act(() => {
+ segments[0].focus();
+ });
+ await user.keyboard('{Backspace}');
+ expect(segments[0]).toHaveAttribute('aria-valuetext', 'Empty');
+
+ // Tab past remaining start segments, end segments, and calendar trigger
+ await user.tab();
+ await user.tab();
+ await user.tab();
+ await user.tab();
+ await user.tab();
+ await user.tab();
+ await user.tab();
+
+ let getDescription = () =>
+ (group.getAttribute('aria-describedby') || '')
+ .split(' ')
+ .map(d => document.getElementById(d)?.textContent || '')
+ .join(' ');
+ expect(getDescription()).toContain('Please enter a value.');
+ });
+
it('supports minValue and maxValue', async () => {
let {getByRole, getByTestId} = render(
@@ -2214,6 +2261,66 @@ describe('DateRangePicker', function () {
expect(input.validity.valid).toBe(true);
expect(getDescription()).not.toContain('Constraints not satisfied');
});
+
+ it('should clear the partial-value error on the first calendar selection that completes the range (Bug #9958 follow-up)', async () => {
+ // Repro of the "takes two interactions" bug for DateRangePicker: after a partial start
+ // endpoint surfaces "Please enter a value.", selecting a complete range from the calendar
+ // must clear it on the FIRST selection. Both setStartIsValuePartial and
+ // setEndIsValuePartial must be reset synchronously before commitValidation().
+ let {getByRole} = render(
+
+
+
+ );
+
+ let group = getByRole('group');
+ let startInput = document.querySelector('input[name=start]');
+ let segments = within(group).getAllByRole('spinbutton');
+
+ // Clear the start month segment, then blur the field -> partial error appears.
+ act(() => {
+ segments[0].focus();
+ });
+ await user.keyboard('{Backspace}');
+ expect(segments[0]).toHaveAttribute('aria-valuetext', 'Empty');
+ // Tab past remaining start segments, end segments, and calendar trigger button.
+ await user.tab();
+ await user.tab();
+ await user.tab();
+ await user.tab();
+ await user.tab();
+ await user.tab();
+ await user.tab();
+
+ let getDescription = () =>
+ (group.getAttribute('aria-describedby') || '')
+ .split(' ')
+ .map(d => document.getElementById(d)?.textContent || '')
+ .join(' ');
+ expect(getDescription()).toContain('Please enter a value.');
+
+ // Open the calendar and select a complete range with two clicks on the focused cell.
+ // The calendar opens with focus on the highlighted start date; clicking it sets the
+ // range start, clicking the same focused cell again sets the range end.
+ await user.click(getByRole('button'));
+ await user.click(document.activeElement);
+ await user.click(document.activeElement);
+
+ // The range is now complete and valid -> error must be gone after ONE selection sequence.
+ expect(startInput.validity.valid).toBe(true);
+ expect(getDescription()).not.toContain('Please enter a value.');
+ });
});
describe('validationBehavior=aria', () => {
diff --git a/packages/@adobe/react-spectrum/test/datepicker/TimeField.test.js b/packages/@adobe/react-spectrum/test/datepicker/TimeField.test.js
index 3bc3ff3fbbc..fd609231ed3 100644
--- a/packages/@adobe/react-spectrum/test/datepicker/TimeField.test.js
+++ b/packages/@adobe/react-spectrum/test/datepicker/TimeField.test.js
@@ -419,6 +419,38 @@ describe('TimeField', function () {
expect(input).toHaveValue('08:30:00');
});
+ it('should clear the hidden input value when a segment is cleared making the field partial (Bug #9801)', async () => {
+ // Regression: clearing one TimeField segment leaves the committed state.value
+ // unchanged, but the field is now visually incomplete. The hidden input — read by
+ // forms on submit — should reflect the missing value as '', not the stale committed
+ // time. Root cause: useDateField.ts uses state.timeValue?.toString() which is
+ // derived from the committed value, not from displayValue.
+ function Test() {
+ return (
+
+ );
+ }
+
+ let {getAllByRole} = render();
+ let input = document.querySelector('input[name=time]');
+ let segments = getAllByRole('spinbutton');
+
+ expect(input).toHaveValue('13:30:00');
+
+ // Clear the hour segment (display shows '1 PM' for 13 — single digit → Empty in one Backspace)
+ act(() => {
+ segments[0].focus();
+ });
+ fireEvent.keyDown(document.activeElement, {key: 'Backspace'});
+ fireEvent.keyUp(document.activeElement, {key: 'Backspace'});
+ expect(segments[0]).toHaveAttribute('aria-valuetext', 'Empty');
+
+ // Field is partial — hidden input should not carry the stale committed time
+ expect(input).toHaveValue('');
+ });
+
if (parseInt(React.version, 10) >= 19) {
it('resets to defaultValue when submitting form action', async () => {
function Test() {
diff --git a/packages/react-aria/src/datepicker/useDateField.ts b/packages/react-aria/src/datepicker/useDateField.ts
index 11a23852686..fcb45ebe16b 100644
--- a/packages/react-aria/src/datepicker/useDateField.ts
+++ b/packages/react-aria/src/datepicker/useDateField.ts
@@ -26,6 +26,7 @@ import {filterDOMProps} from '../utils/filterDOMProps';
import {InputHTMLAttributes, useEffect, useMemo, useRef} from 'react';
import intlMessages from '../../intl/datepicker/*.json';
import {mergeProps} from '../utils/mergeProps';
+import {privateSetIsValuePartialProp} from 'react-stately/private/form/useFormValidationState';
import {TimeFieldState, TimePickerProps, TimeValue} from 'react-stately/useTimeFieldState';
import {useDatePickerGroup} from './useDatePickerGroup';
// @ts-ignore
@@ -110,7 +111,8 @@ export function useDateField(
},
onBlurWithin: e => {
state.confirmPlaceholder();
- if (state.value !== valueOnFocus.current) {
+ // Also fire for partial display values: `value` is never updated for those by design.
+ if (state.value !== valueOnFocus.current || state.isValuePartial) {
state.commitValidation();
}
props.onBlur?.(e);
@@ -188,11 +190,25 @@ export function useDateField(
props.inputRef
);
+ // When wrapped by DatePicker / DateRangePicker, the picker owns the validation pipeline
+ // (via privateValidationStateProp). Push our local partial state up so the picker's
+ // getValidationResult sees it; standalone fields handle this in useDateFieldState directly.
+ let setParentIsValuePartial = (props as any)[privateSetIsValuePartialProp] as
+ | ((isPartial: boolean) => void)
+ | undefined;
+ useEffect(() => {
+ if (setParentIsValuePartial) {
+ setParentIsValuePartial(state.isValuePartial);
+ return () => setParentIsValuePartial(false);
+ }
+ }, [setParentIsValuePartial, state.isValuePartial]);
+
+ // Empty when partial so the native `required` constraint sees a missing value.
let inputProps: InputHTMLAttributes = {
type: 'hidden',
name: props.name,
form: props.form,
- value: state.value?.toString() || '',
+ value: state.isValuePartial ? '' : state.value?.toString() || '',
disabled: props.isDisabled
};
@@ -257,6 +273,7 @@ export function useTimeField(
ref: RefObject
): DateFieldAria {
let res = useDateField(props, state, ref);
- res.inputProps.value = state.timeValue?.toString() || '';
+ // Same partial-state guard as the DateField hidden input.
+ res.inputProps.value = state.isValuePartial ? '' : state.timeValue?.toString() || '';
return res;
}
diff --git a/packages/react-aria/src/datepicker/useDatePicker.ts b/packages/react-aria/src/datepicker/useDatePicker.ts
index d2dc765104f..98aa573f28b 100644
--- a/packages/react-aria/src/datepicker/useDatePicker.ts
+++ b/packages/react-aria/src/datepicker/useDatePicker.ts
@@ -30,7 +30,10 @@ import {filterDOMProps} from '../utils/filterDOMProps';
import intlMessages from '../../intl/datepicker/*.json';
import {mergeProps} from '../utils/mergeProps';
import {nodeContains} from '../utils/shadowdom/DOMFunctions';
-import {privateValidationStateProp} from 'react-stately/private/form/useFormValidationState';
+import {
+ privateSetIsValuePartialProp,
+ privateValidationStateProp
+} from 'react-stately/private/form/useFormValidationState';
// @ts-ignore
import {roleSymbol} from './useDateField';
import {useDatePickerGroup} from './useDatePickerGroup';
@@ -179,6 +182,9 @@ export function useDatePicker(
validationBehavior: props.validationBehavior,
// DatePicker owns the validation state for the date field.
[privateValidationStateProp]: state,
+ // Forwarded so the field can push its partial-edit state up into the picker's
+ // validation pipeline (so partial values invalidate even without isRequired).
+ [privateSetIsValuePartialProp]: (state as any)[privateSetIsValuePartialProp],
autoFocus: props.autoFocus,
name: props.name,
form: props.form
diff --git a/packages/react-aria/src/datepicker/useDateRangePicker.ts b/packages/react-aria/src/datepicker/useDateRangePicker.ts
index 239b4366a3f..a37411b0b4b 100644
--- a/packages/react-aria/src/datepicker/useDateRangePicker.ts
+++ b/packages/react-aria/src/datepicker/useDateRangePicker.ts
@@ -34,6 +34,7 @@ import {
import {
DEFAULT_VALIDATION_RESULT,
mergeValidation,
+ privateSetIsValuePartialProp,
privateValidationStateProp
} from 'react-stately/private/form/useFormValidationState';
import {filterDOMProps} from '../utils/filterDOMProps';
@@ -239,7 +240,9 @@ export function useDateRangePicker(
},
resetValidation: state.resetValidation,
commitValidation: state.commitValidation
- }
+ },
+ // Push the start field's partial-edit state up into the range's validation pipeline.
+ [privateSetIsValuePartialProp]: (state as any)[`${privateSetIsValuePartialProp}-start`]
},
endFieldProps: {
...endFieldProps,
@@ -258,7 +261,9 @@ export function useDateRangePicker(
},
resetValidation: state.resetValidation,
commitValidation: state.commitValidation
- }
+ },
+ // Push the end field's partial-edit state up into the range's validation pipeline.
+ [privateSetIsValuePartialProp]: (state as any)[`${privateSetIsValuePartialProp}-end`]
},
descriptionProps,
errorMessageProps,
diff --git a/packages/react-stately/exports/private/form/useFormValidationState.ts b/packages/react-stately/exports/private/form/useFormValidationState.ts
index 4d99ecefe83..3fd0ce74ec7 100644
--- a/packages/react-stately/exports/private/form/useFormValidationState.ts
+++ b/packages/react-stately/exports/private/form/useFormValidationState.ts
@@ -2,6 +2,7 @@ export {
FormValidationContext,
useFormValidationState,
privateValidationStateProp,
+ privateSetIsValuePartialProp,
type FormValidationState,
DEFAULT_VALIDATION_RESULT,
mergeValidation,
diff --git a/packages/react-stately/intl/datepicker/ar-AE.json b/packages/react-stately/intl/datepicker/ar-AE.json
index 3b0f019b1da..9c5532f5c6a 100644
--- a/packages/react-stately/intl/datepicker/ar-AE.json
+++ b/packages/react-stately/intl/datepicker/ar-AE.json
@@ -2,5 +2,6 @@
"rangeOverflow": "يجب أن تكون القيمة {maxValue} أو قبل ذلك.",
"rangeReversed": "تاريخ البدء يجب أن يكون قبل تاريخ الانتهاء.",
"rangeUnderflow": "يجب أن تكون القيمة {minValue} أو بعد ذلك.",
- "unavailableDate": "البيانات المحددة غير متاحة."
+ "unavailableDate": "البيانات المحددة غير متاحة.",
+ "incompleteValue": "Please enter a value."
}
diff --git a/packages/react-stately/intl/datepicker/bg-BG.json b/packages/react-stately/intl/datepicker/bg-BG.json
index 13e7af5290f..fbdd59e56ae 100644
--- a/packages/react-stately/intl/datepicker/bg-BG.json
+++ b/packages/react-stately/intl/datepicker/bg-BG.json
@@ -2,5 +2,6 @@
"rangeOverflow": "Стойността трябва да е {maxValue} или по-ранна.",
"rangeReversed": "Началната дата трябва да е преди крайната.",
"rangeUnderflow": "Стойността трябва да е {minValue} или по-късно.",
- "unavailableDate": "Избраната дата не е налична."
+ "unavailableDate": "Избраната дата не е налична.",
+ "incompleteValue": "Please enter a value."
}
diff --git a/packages/react-stately/intl/datepicker/cs-CZ.json b/packages/react-stately/intl/datepicker/cs-CZ.json
index 09c6c0dd327..44f574353d3 100644
--- a/packages/react-stately/intl/datepicker/cs-CZ.json
+++ b/packages/react-stately/intl/datepicker/cs-CZ.json
@@ -2,5 +2,6 @@
"rangeOverflow": "Hodnota musí být {maxValue} nebo dřívější.",
"rangeReversed": "Datum zahájení musí předcházet datu ukončení.",
"rangeUnderflow": "Hodnota musí být {minValue} nebo pozdější.",
- "unavailableDate": "Vybrané datum není k dispozici."
+ "unavailableDate": "Vybrané datum není k dispozici.",
+ "incompleteValue": "Please enter a value."
}
diff --git a/packages/react-stately/intl/datepicker/da-DK.json b/packages/react-stately/intl/datepicker/da-DK.json
index 734f245e5e0..6991a7c676f 100644
--- a/packages/react-stately/intl/datepicker/da-DK.json
+++ b/packages/react-stately/intl/datepicker/da-DK.json
@@ -2,5 +2,6 @@
"rangeOverflow": "Værdien skal være {maxValue} eller tidligere.",
"rangeReversed": "Startdatoen skal være før slutdatoen.",
"rangeUnderflow": "Værdien skal være {minValue} eller nyere.",
- "unavailableDate": "Den valgte dato er ikke tilgængelig."
+ "unavailableDate": "Den valgte dato er ikke tilgængelig.",
+ "incompleteValue": "Please enter a value."
}
diff --git a/packages/react-stately/intl/datepicker/de-DE.json b/packages/react-stately/intl/datepicker/de-DE.json
index a071ad8c4f1..5b1afffc9b9 100644
--- a/packages/react-stately/intl/datepicker/de-DE.json
+++ b/packages/react-stately/intl/datepicker/de-DE.json
@@ -2,5 +2,6 @@
"rangeOverflow": "Der Wert muss {maxValue} oder früher sein.",
"rangeReversed": "Das Startdatum muss vor dem Enddatum liegen.",
"rangeUnderflow": "Der Wert muss {minValue} oder später sein.",
- "unavailableDate": "Das ausgewählte Datum ist nicht verfügbar."
+ "unavailableDate": "Das ausgewählte Datum ist nicht verfügbar.",
+ "incompleteValue": "Please enter a value."
}
diff --git a/packages/react-stately/intl/datepicker/el-GR.json b/packages/react-stately/intl/datepicker/el-GR.json
index d93e837a685..60e6939f44c 100644
--- a/packages/react-stately/intl/datepicker/el-GR.json
+++ b/packages/react-stately/intl/datepicker/el-GR.json
@@ -2,5 +2,6 @@
"rangeOverflow": "Η τιμή πρέπει να είναι {maxValue} ή παλαιότερη.",
"rangeReversed": "Η ημερομηνία έναρξης πρέπει να είναι πριν από την ημερομηνία λήξης.",
"rangeUnderflow": "Η τιμή πρέπει να είναι {minValue} ή μεταγενέστερη.",
- "unavailableDate": "Η επιλεγμένη ημερομηνία δεν είναι διαθέσιμη."
+ "unavailableDate": "Η επιλεγμένη ημερομηνία δεν είναι διαθέσιμη.",
+ "incompleteValue": "Please enter a value."
}
diff --git a/packages/react-stately/intl/datepicker/en-US.json b/packages/react-stately/intl/datepicker/en-US.json
index a2985739ad2..3e2d1bee190 100644
--- a/packages/react-stately/intl/datepicker/en-US.json
+++ b/packages/react-stately/intl/datepicker/en-US.json
@@ -1,4 +1,5 @@
{
+ "incompleteValue": "Please enter a value.",
"rangeUnderflow": "Value must be {minValue} or later.",
"rangeOverflow": "Value must be {maxValue} or earlier.",
"rangeReversed": "Start date must be before end date.",
diff --git a/packages/react-stately/intl/datepicker/es-ES.json b/packages/react-stately/intl/datepicker/es-ES.json
index a8ff4756927..0763acd3855 100644
--- a/packages/react-stately/intl/datepicker/es-ES.json
+++ b/packages/react-stately/intl/datepicker/es-ES.json
@@ -2,5 +2,6 @@
"rangeOverflow": "El valor debe ser {maxValue} o anterior.",
"rangeReversed": "La fecha de inicio debe ser anterior a la fecha de finalización.",
"rangeUnderflow": "El valor debe ser {minValue} o posterior.",
- "unavailableDate": "Fecha seleccionada no disponible."
+ "unavailableDate": "Fecha seleccionada no disponible.",
+ "incompleteValue": "Please enter a value."
}
diff --git a/packages/react-stately/intl/datepicker/et-EE.json b/packages/react-stately/intl/datepicker/et-EE.json
index 84000193071..3dcddfab13d 100644
--- a/packages/react-stately/intl/datepicker/et-EE.json
+++ b/packages/react-stately/intl/datepicker/et-EE.json
@@ -2,5 +2,6 @@
"rangeOverflow": "Väärtus peab olema {maxValue} või varasem.",
"rangeReversed": "Alguskuupäev peab olema enne lõppkuupäeva.",
"rangeUnderflow": "Väärtus peab olema {minValue} või hilisem.",
- "unavailableDate": "Valitud kuupäev pole saadaval."
+ "unavailableDate": "Valitud kuupäev pole saadaval.",
+ "incompleteValue": "Please enter a value."
}
diff --git a/packages/react-stately/intl/datepicker/fi-FI.json b/packages/react-stately/intl/datepicker/fi-FI.json
index a31f31e9e10..9238d395e8c 100644
--- a/packages/react-stately/intl/datepicker/fi-FI.json
+++ b/packages/react-stately/intl/datepicker/fi-FI.json
@@ -2,5 +2,6 @@
"rangeOverflow": "Arvon on oltava {maxValue} tai sitä aikaisempi.",
"rangeReversed": "Aloituspäivän on oltava ennen lopetuspäivää.",
"rangeUnderflow": "Arvon on oltava {minValue} tai sitä myöhäisempi.",
- "unavailableDate": "Valittu päivämäärä ei ole käytettävissä."
+ "unavailableDate": "Valittu päivämäärä ei ole käytettävissä.",
+ "incompleteValue": "Please enter a value."
}
diff --git a/packages/react-stately/intl/datepicker/fr-FR.json b/packages/react-stately/intl/datepicker/fr-FR.json
index 70d6e4a0aff..cd1485760dd 100644
--- a/packages/react-stately/intl/datepicker/fr-FR.json
+++ b/packages/react-stately/intl/datepicker/fr-FR.json
@@ -2,5 +2,6 @@
"rangeOverflow": "La valeur doit être {maxValue} ou antérieure.",
"rangeReversed": "La date de début doit être antérieure à la date de fin.",
"rangeUnderflow": "La valeur doit être {minValue} ou ultérieure.",
- "unavailableDate": "La date sélectionnée n’est pas disponible."
+ "unavailableDate": "La date sélectionnée n’est pas disponible.",
+ "incompleteValue": "Please enter a value."
}
diff --git a/packages/react-stately/intl/datepicker/he-IL.json b/packages/react-stately/intl/datepicker/he-IL.json
index e28b795968e..eb16b62069b 100644
--- a/packages/react-stately/intl/datepicker/he-IL.json
+++ b/packages/react-stately/intl/datepicker/he-IL.json
@@ -2,5 +2,6 @@
"rangeOverflow": "הערך חייב להיות {maxValue} או מוקדם יותר.",
"rangeReversed": "תאריך ההתחלה חייב להיות לפני תאריך הסיום.",
"rangeUnderflow": "הערך חייב להיות {minValue} או מאוחר יותר.",
- "unavailableDate": "התאריך הנבחר אינו זמין."
+ "unavailableDate": "התאריך הנבחר אינו זמין.",
+ "incompleteValue": "Please enter a value."
}
diff --git a/packages/react-stately/intl/datepicker/hr-HR.json b/packages/react-stately/intl/datepicker/hr-HR.json
index 124c8c37347..e05385388ca 100644
--- a/packages/react-stately/intl/datepicker/hr-HR.json
+++ b/packages/react-stately/intl/datepicker/hr-HR.json
@@ -2,5 +2,6 @@
"rangeOverflow": "Vrijednost mora biti {maxValue} ili ranije.",
"rangeReversed": "Datum početka mora biti prije datuma završetka.",
"rangeUnderflow": "Vrijednost mora biti {minValue} ili kasnije.",
- "unavailableDate": "Odabrani datum nije dostupan."
+ "unavailableDate": "Odabrani datum nije dostupan.",
+ "incompleteValue": "Please enter a value."
}
diff --git a/packages/react-stately/intl/datepicker/hu-HU.json b/packages/react-stately/intl/datepicker/hu-HU.json
index 657ba120ace..e0e49e519b3 100644
--- a/packages/react-stately/intl/datepicker/hu-HU.json
+++ b/packages/react-stately/intl/datepicker/hu-HU.json
@@ -2,5 +2,6 @@
"rangeOverflow": "Az értéknek {maxValue} vagy korábbinak kell lennie.",
"rangeReversed": "A kezdő dátumnak a befejező dátumnál korábbinak kell lennie.",
"rangeUnderflow": "Az értéknek {minValue} vagy későbbinek kell lennie.",
- "unavailableDate": "A kiválasztott dátum nem érhető el."
+ "unavailableDate": "A kiválasztott dátum nem érhető el.",
+ "incompleteValue": "Please enter a value."
}
diff --git a/packages/react-stately/intl/datepicker/it-IT.json b/packages/react-stately/intl/datepicker/it-IT.json
index 0f5181756d5..0de7d93d43e 100644
--- a/packages/react-stately/intl/datepicker/it-IT.json
+++ b/packages/react-stately/intl/datepicker/it-IT.json
@@ -2,5 +2,6 @@
"rangeOverflow": "Il valore deve essere {maxValue} o precedente.",
"rangeReversed": "La data di inizio deve essere antecedente alla data di fine.",
"rangeUnderflow": "Il valore deve essere {minValue} o successivo.",
- "unavailableDate": "Data selezionata non disponibile."
+ "unavailableDate": "Data selezionata non disponibile.",
+ "incompleteValue": "Please enter a value."
}
diff --git a/packages/react-stately/intl/datepicker/ja-JP.json b/packages/react-stately/intl/datepicker/ja-JP.json
index a015bd99619..efa99734ba0 100644
--- a/packages/react-stately/intl/datepicker/ja-JP.json
+++ b/packages/react-stately/intl/datepicker/ja-JP.json
@@ -2,5 +2,6 @@
"rangeOverflow": "値は {maxValue} 以下にする必要があります。",
"rangeReversed": "開始日は終了日より前にする必要があります。",
"rangeUnderflow": "値は {minValue} 以上にする必要があります。",
- "unavailableDate": "選択した日付は使用できません。"
+ "unavailableDate": "選択した日付は使用できません。",
+ "incompleteValue": "Please enter a value."
}
diff --git a/packages/react-stately/intl/datepicker/ko-KR.json b/packages/react-stately/intl/datepicker/ko-KR.json
index 4b813ba32d0..d93936274ed 100644
--- a/packages/react-stately/intl/datepicker/ko-KR.json
+++ b/packages/react-stately/intl/datepicker/ko-KR.json
@@ -2,5 +2,6 @@
"rangeOverflow": "값은 {maxValue} 이전이어야 합니다.",
"rangeReversed": "시작일은 종료일 이전이어야 합니다.",
"rangeUnderflow": "값은 {minValue} 이후여야 합니다.",
- "unavailableDate": "선택한 날짜를 사용할 수 없습니다."
+ "unavailableDate": "선택한 날짜를 사용할 수 없습니다.",
+ "incompleteValue": "Please enter a value."
}
diff --git a/packages/react-stately/intl/datepicker/lt-LT.json b/packages/react-stately/intl/datepicker/lt-LT.json
index ea4f911c500..b982689cef9 100644
--- a/packages/react-stately/intl/datepicker/lt-LT.json
+++ b/packages/react-stately/intl/datepicker/lt-LT.json
@@ -2,5 +2,6 @@
"rangeOverflow": "Reikšmė turi būti {maxValue} arba ankstesnė.",
"rangeReversed": "Pradžios data turi būti ankstesnė nei pabaigos data.",
"rangeUnderflow": "Reikšmė turi būti {minValue} arba naujesnė.",
- "unavailableDate": "Pasirinkta data nepasiekiama."
+ "unavailableDate": "Pasirinkta data nepasiekiama.",
+ "incompleteValue": "Please enter a value."
}
diff --git a/packages/react-stately/intl/datepicker/lv-LV.json b/packages/react-stately/intl/datepicker/lv-LV.json
index 99aa0615654..cb6bde7608a 100644
--- a/packages/react-stately/intl/datepicker/lv-LV.json
+++ b/packages/react-stately/intl/datepicker/lv-LV.json
@@ -2,5 +2,6 @@
"rangeOverflow": "Vērtībai ir jābūt {maxValue} vai agrākai.",
"rangeReversed": "Sākuma datumam ir jābūt pirms beigu datuma.",
"rangeUnderflow": "Vērtībai ir jābūt {minValue} vai vēlākai.",
- "unavailableDate": "Atlasītais datums nav pieejams."
+ "unavailableDate": "Atlasītais datums nav pieejams.",
+ "incompleteValue": "Please enter a value."
}
diff --git a/packages/react-stately/intl/datepicker/nb-NO.json b/packages/react-stately/intl/datepicker/nb-NO.json
index b721b2964aa..1509839963d 100644
--- a/packages/react-stately/intl/datepicker/nb-NO.json
+++ b/packages/react-stately/intl/datepicker/nb-NO.json
@@ -2,5 +2,6 @@
"rangeOverflow": "Verdien må være {maxValue} eller tidligere.",
"rangeReversed": "Startdatoen må være før sluttdatoen.",
"rangeUnderflow": "Verdien må være {minValue} eller senere.",
- "unavailableDate": "Valgt dato utilgjengelig."
+ "unavailableDate": "Valgt dato utilgjengelig.",
+ "incompleteValue": "Please enter a value."
}
diff --git a/packages/react-stately/intl/datepicker/nl-NL.json b/packages/react-stately/intl/datepicker/nl-NL.json
index 7d2ea1479d3..51d7cb66028 100644
--- a/packages/react-stately/intl/datepicker/nl-NL.json
+++ b/packages/react-stately/intl/datepicker/nl-NL.json
@@ -2,5 +2,6 @@
"rangeOverflow": "Waarde moet {maxValue} of eerder zijn.",
"rangeReversed": "De startdatum moet voor de einddatum liggen.",
"rangeUnderflow": "Waarde moet {minValue} of later zijn.",
- "unavailableDate": "Geselecteerde datum niet beschikbaar."
+ "unavailableDate": "Geselecteerde datum niet beschikbaar.",
+ "incompleteValue": "Please enter a value."
}
diff --git a/packages/react-stately/intl/datepicker/pl-PL.json b/packages/react-stately/intl/datepicker/pl-PL.json
index 964c502dfb4..fe5ba93b635 100644
--- a/packages/react-stately/intl/datepicker/pl-PL.json
+++ b/packages/react-stately/intl/datepicker/pl-PL.json
@@ -2,5 +2,6 @@
"rangeOverflow": "Wartość musi mieć wartość {maxValue} lub wcześniejszą.",
"rangeReversed": "Data rozpoczęcia musi być wcześniejsza niż data zakończenia.",
"rangeUnderflow": "Wartość musi mieć wartość {minValue} lub późniejszą.",
- "unavailableDate": "Wybrana data jest niedostępna."
+ "unavailableDate": "Wybrana data jest niedostępna.",
+ "incompleteValue": "Please enter a value."
}
diff --git a/packages/react-stately/intl/datepicker/pt-BR.json b/packages/react-stately/intl/datepicker/pt-BR.json
index a010d9b8a30..f0998c3b19d 100644
--- a/packages/react-stately/intl/datepicker/pt-BR.json
+++ b/packages/react-stately/intl/datepicker/pt-BR.json
@@ -2,5 +2,6 @@
"rangeOverflow": "O valor deve ser {maxValue} ou anterior.",
"rangeReversed": "A data inicial deve ser anterior à data final.",
"rangeUnderflow": "O valor deve ser {minValue} ou posterior.",
- "unavailableDate": "Data selecionada indisponível."
+ "unavailableDate": "Data selecionada indisponível.",
+ "incompleteValue": "Please enter a value."
}
diff --git a/packages/react-stately/intl/datepicker/pt-PT.json b/packages/react-stately/intl/datepicker/pt-PT.json
index 09e60a5883c..8eefc585858 100644
--- a/packages/react-stately/intl/datepicker/pt-PT.json
+++ b/packages/react-stately/intl/datepicker/pt-PT.json
@@ -2,5 +2,6 @@
"rangeOverflow": "O valor tem de ser {maxValue} ou anterior.",
"rangeReversed": "A data de início deve ser anterior à data de fim.",
"rangeUnderflow": "O valor tem de ser {minValue} ou posterior.",
- "unavailableDate": "Data selecionada indisponível."
+ "unavailableDate": "Data selecionada indisponível.",
+ "incompleteValue": "Please enter a value."
}
diff --git a/packages/react-stately/intl/datepicker/ro-RO.json b/packages/react-stately/intl/datepicker/ro-RO.json
index 13e1d3d61f8..1f634325cec 100644
--- a/packages/react-stately/intl/datepicker/ro-RO.json
+++ b/packages/react-stately/intl/datepicker/ro-RO.json
@@ -2,5 +2,6 @@
"rangeOverflow": "Valoarea trebuie să fie {maxValue} sau anterioară.",
"rangeReversed": "Data de început trebuie să fie anterioară datei de sfârșit.",
"rangeUnderflow": "Valoarea trebuie să fie {minValue} sau ulterioară.",
- "unavailableDate": "Data selectată nu este disponibilă."
+ "unavailableDate": "Data selectată nu este disponibilă.",
+ "incompleteValue": "Please enter a value."
}
diff --git a/packages/react-stately/intl/datepicker/ru-RU.json b/packages/react-stately/intl/datepicker/ru-RU.json
index 574a82ce173..b3a3c841dda 100644
--- a/packages/react-stately/intl/datepicker/ru-RU.json
+++ b/packages/react-stately/intl/datepicker/ru-RU.json
@@ -2,5 +2,6 @@
"rangeOverflow": "Значение должно быть не позже {maxValue}.",
"rangeReversed": "Дата начала должна предшествовать дате окончания.",
"rangeUnderflow": "Значение должно быть не раньше {minValue}.",
- "unavailableDate": "Выбранная дата недоступна."
+ "unavailableDate": "Выбранная дата недоступна.",
+ "incompleteValue": "Please enter a value."
}
diff --git a/packages/react-stately/intl/datepicker/sk-SK.json b/packages/react-stately/intl/datepicker/sk-SK.json
index 81aa69ba24b..741efacad94 100644
--- a/packages/react-stately/intl/datepicker/sk-SK.json
+++ b/packages/react-stately/intl/datepicker/sk-SK.json
@@ -2,5 +2,6 @@
"rangeOverflow": "Hodnota musí byť {maxValue} alebo skoršia.",
"rangeReversed": "Dátum začiatku musí byť skorší ako dátum konca.",
"rangeUnderflow": "Hodnota musí byť {minValue} alebo neskoršia.",
- "unavailableDate": "Vybratý dátum je nedostupný."
+ "unavailableDate": "Vybratý dátum je nedostupný.",
+ "incompleteValue": "Please enter a value."
}
diff --git a/packages/react-stately/intl/datepicker/sl-SI.json b/packages/react-stately/intl/datepicker/sl-SI.json
index fdd641d5d7f..491f1fdc456 100644
--- a/packages/react-stately/intl/datepicker/sl-SI.json
+++ b/packages/react-stately/intl/datepicker/sl-SI.json
@@ -2,5 +2,6 @@
"rangeOverflow": "Vrednost mora biti {maxValue} ali starejša.",
"rangeReversed": "Začetni datum mora biti pred končnim datumom.",
"rangeUnderflow": "Vrednost mora biti {minValue} ali novejša.",
- "unavailableDate": "Izbrani datum ni na voljo."
+ "unavailableDate": "Izbrani datum ni na voljo.",
+ "incompleteValue": "Please enter a value."
}
diff --git a/packages/react-stately/intl/datepicker/sr-SP.json b/packages/react-stately/intl/datepicker/sr-SP.json
index 57bf1027456..e03eb773917 100644
--- a/packages/react-stately/intl/datepicker/sr-SP.json
+++ b/packages/react-stately/intl/datepicker/sr-SP.json
@@ -2,5 +2,6 @@
"rangeOverflow": "Vrednost mora da bude {maxValue} ili starija.",
"rangeReversed": "Datum početka mora biti pre datuma završetka.",
"rangeUnderflow": "Vrednost mora da bude {minValue} ili novija.",
- "unavailableDate": "Izabrani datum nije dostupan."
+ "unavailableDate": "Izabrani datum nije dostupan.",
+ "incompleteValue": "Please enter a value."
}
diff --git a/packages/react-stately/intl/datepicker/sv-SE.json b/packages/react-stately/intl/datepicker/sv-SE.json
index b32d8b705f5..5320c0216c1 100644
--- a/packages/react-stately/intl/datepicker/sv-SE.json
+++ b/packages/react-stately/intl/datepicker/sv-SE.json
@@ -2,5 +2,6 @@
"rangeOverflow": "Värdet måste vara {maxValue} eller tidigare.",
"rangeReversed": "Startdatumet måste vara före slutdatumet.",
"rangeUnderflow": "Värdet måste vara {minValue} eller senare.",
- "unavailableDate": "Det valda datumet är inte tillgängligt."
+ "unavailableDate": "Det valda datumet är inte tillgängligt.",
+ "incompleteValue": "Please enter a value."
}
diff --git a/packages/react-stately/intl/datepicker/tr-TR.json b/packages/react-stately/intl/datepicker/tr-TR.json
index dd86809610d..0c6b0259d80 100644
--- a/packages/react-stately/intl/datepicker/tr-TR.json
+++ b/packages/react-stately/intl/datepicker/tr-TR.json
@@ -2,5 +2,6 @@
"rangeOverflow": "Değer, {maxValue} veya öncesi olmalıdır.",
"rangeReversed": "Başlangıç tarihi bitiş tarihinden önce olmalıdır.",
"rangeUnderflow": "Değer, {minValue} veya sonrası olmalıdır.",
- "unavailableDate": "Seçilen tarih kullanılamıyor."
+ "unavailableDate": "Seçilen tarih kullanılamıyor.",
+ "incompleteValue": "Please enter a value."
}
diff --git a/packages/react-stately/intl/datepicker/uk-UA.json b/packages/react-stately/intl/datepicker/uk-UA.json
index d5e12390bfd..950f0f649d3 100644
--- a/packages/react-stately/intl/datepicker/uk-UA.json
+++ b/packages/react-stately/intl/datepicker/uk-UA.json
@@ -2,5 +2,6 @@
"rangeOverflow": "Значення має бути не пізніше {maxValue}.",
"rangeReversed": "Дата початку має передувати даті завершення.",
"rangeUnderflow": "Значення має бути не раніше {minValue}.",
- "unavailableDate": "Вибрана дата недоступна."
+ "unavailableDate": "Вибрана дата недоступна.",
+ "incompleteValue": "Please enter a value."
}
diff --git a/packages/react-stately/intl/datepicker/zh-CN.json b/packages/react-stately/intl/datepicker/zh-CN.json
index 4879c113fcd..6e53dfa0f14 100644
--- a/packages/react-stately/intl/datepicker/zh-CN.json
+++ b/packages/react-stately/intl/datepicker/zh-CN.json
@@ -2,5 +2,6 @@
"rangeOverflow": "值必须是 {maxValue} 或更早日期。",
"rangeReversed": "开始日期必须早于结束日期。",
"rangeUnderflow": "值必须是 {minValue} 或更晚日期。",
- "unavailableDate": "所选日期不可用。"
+ "unavailableDate": "所选日期不可用。",
+ "incompleteValue": "Please enter a value."
}
diff --git a/packages/react-stately/intl/datepicker/zh-TW.json b/packages/react-stately/intl/datepicker/zh-TW.json
index 61b0e36556a..b7ba589cf11 100644
--- a/packages/react-stately/intl/datepicker/zh-TW.json
+++ b/packages/react-stately/intl/datepicker/zh-TW.json
@@ -2,5 +2,6 @@
"rangeOverflow": "值必須是 {maxValue} 或更早。",
"rangeReversed": "開始日期必須在結束日期之前。",
"rangeUnderflow": "值必須是 {minValue} 或更晚。",
- "unavailableDate": "所選日期無法使用。"
+ "unavailableDate": "所選日期無法使用。",
+ "incompleteValue": "Please enter a value."
}
diff --git a/packages/react-stately/src/datepicker/useDateFieldState.ts b/packages/react-stately/src/datepicker/useDateFieldState.ts
index 2103656abf0..854cf39b00c 100644
--- a/packages/react-stately/src/datepicker/useDateFieldState.ts
+++ b/packages/react-stately/src/datepicker/useDateFieldState.ts
@@ -100,6 +100,13 @@ export interface DateFieldState extends FormValidationState {
isReadOnly: boolean;
/** Whether the field is required. */
isRequired: boolean;
+ /**
+ * Whether the display value is partially filled — some editable segments have values and
+ * some are still placeholders.
+ *
+ * @private
+ */
+ isValuePartial: boolean;
/**
* Increments the given segment. Upon reaching the minimum or maximum value, the value wraps
* around to the opposite limit.
@@ -363,9 +370,13 @@ export function useDateFieldState(
setValue(displayValue.cycle(type, amount, placeholder, displaySegments));
};
+ let isValuePartial =
+ !displayValue.isComplete(displaySegments) && !displayValue.isCleared(displaySegments);
+
let builtinValidation = useMemo(
- () => getValidationResult(value, minValue, maxValue, isDateUnavailable, formatOpts),
- [value, minValue, maxValue, isDateUnavailable, formatOpts]
+ () =>
+ getValidationResult(value, minValue, maxValue, isDateUnavailable, formatOpts, isValuePartial),
+ [value, minValue, maxValue, isDateUnavailable, formatOpts, isValuePartial]
);
let validation = useFormValidationState({
@@ -394,6 +405,7 @@ export function useDateFieldState(
isDisabled,
isReadOnly,
isRequired,
+ isValuePartial,
increment(part) {
adjustSegment(part, 1);
},
diff --git a/packages/react-stately/src/datepicker/useDatePickerState.ts b/packages/react-stately/src/datepicker/useDatePickerState.ts
index f4a38ab56a7..7591c4b7eab 100644
--- a/packages/react-stately/src/datepicker/useDatePickerState.ts
+++ b/packages/react-stately/src/datepicker/useDatePickerState.ts
@@ -25,7 +25,11 @@ import {
getValidationResult,
useDefaultProps
} from './utils';
-import {FormValidationState, useFormValidationState} from '../form/useFormValidationState';
+import {
+ FormValidationState,
+ privateSetIsValuePartialProp,
+ useFormValidationState
+} from '../form/useFormValidationState';
import {OverlayTriggerState, useOverlayTriggerState} from '../overlays/useOverlayTriggerState';
import {useControlledState} from '../utils/useControlledState';
import {useMemo, useState} from 'react';
@@ -95,7 +99,7 @@ export function useDatePickerState(
props: DatePickerStateOptions
): DatePickerState {
let overlayState = useOverlayTriggerState(props);
- let [value, setValue] = useControlledState | null>(
+ let [value, setValueInternal] = useControlledState | null>(
props.value,
props.defaultValue || null,
props.onChange
@@ -144,9 +148,23 @@ export function useDatePickerState(
);
let {minValue, maxValue, isDateUnavailable} = props;
+
+ // Partial-state lifted up from the inner DateField via `privateSetIsValuePartialProp`
+ // on fieldProps. The field calls our setter when its IncompleteDate has some-but-not-all
+ // editable segments filled, so the parent's validation pipeline can surface the error.
+ let [isValuePartial, setIsValuePartial] = useState(false);
+
+ // Wrap the raw setter so any committed value (complete or null) always resets the partial flag.
+ // This prevents stale isValuePartial state when a consumer calls state.setValue() directly.
+ let setValue = (newValue: DateValue | null) => {
+ setIsValuePartial(false);
+ setValueInternal(newValue);
+ };
+
let builtinValidation = useMemo(
- () => getValidationResult(value, minValue, maxValue, isDateUnavailable, formatOpts),
- [value, minValue, maxValue, isDateUnavailable, formatOpts]
+ () =>
+ getValidationResult(value, minValue, maxValue, isDateUnavailable, formatOpts, isValuePartial),
+ [value, minValue, maxValue, isDateUnavailable, formatOpts, isValuePartial]
);
let validation = useFormValidationState({
@@ -199,6 +217,7 @@ export function useDatePickerState(
return {
...validation,
+ [privateSetIsValuePartialProp]: setIsValuePartial,
value,
defaultValue: props.defaultValue ?? initialValue,
setValue,
diff --git a/packages/react-stately/src/datepicker/useDateRangePickerState.ts b/packages/react-stately/src/datepicker/useDateRangePickerState.ts
index a6b93564b09..0dbf6a67674 100644
--- a/packages/react-stately/src/datepicker/useDateRangePickerState.ts
+++ b/packages/react-stately/src/datepicker/useDateRangePickerState.ts
@@ -27,7 +27,11 @@ import {
getRangeValidationResult,
useDefaultProps
} from './utils';
-import {FormValidationState, useFormValidationState} from '../form/useFormValidationState';
+import {
+ FormValidationState,
+ privateSetIsValuePartialProp,
+ useFormValidationState
+} from '../form/useFormValidationState';
import {OverlayTriggerState, useOverlayTriggerState} from '../overlays/useOverlayTriggerState';
import {RangeValue, ValidationState} from '@react-types/shared';
import {useControlledState} from '../utils/useControlledState';
@@ -127,7 +131,12 @@ export function useDateRangePickerState(
let value = controlledValue || placeholderValue;
- let setValue = (newValue: RangeValue | null) => {
+ // Partial-state hoisted before setValue so the wrapper can reference the setters.
+ // Lifted from the inner start/end DateFields via privateSetIsValuePartialProp.
+ let [startIsValuePartial, setStartIsValuePartial] = useState(false);
+ let [endIsValuePartial, setEndIsValuePartial] = useState(false);
+
+ let setValueInternal = (newValue: RangeValue | null) => {
value = newValue || {start: null, end: null};
setPlaceholderValue(value);
if (isCompleteRange(value)) {
@@ -137,6 +146,14 @@ export function useDateRangePickerState(
}
};
+ // Wrap the setter so any committed range (complete or null) always resets both partial flags,
+ // preventing stale isValuePartial state when a consumer calls state.setValue() directly.
+ let setValue = (newValue: RangeValue | null) => {
+ setStartIsValuePartial(false);
+ setEndIsValuePartial(false);
+ setValueInternal(newValue);
+ };
+
let v = value?.start || value?.end || props.placeholderValue || null;
let [granularity, defaultTimeZone] = useDefaultProps(v, props.granularity);
let hasTime = granularity === 'hour' || granularity === 'minute' || granularity === 'second';
@@ -225,6 +242,7 @@ export function useDateRangePickerState(
);
let {minValue, maxValue, isDateUnavailable} = props;
+
let builtinValidation = useMemo(
() =>
getRangeValidationResult(
@@ -232,9 +250,19 @@ export function useDateRangePickerState(
minValue,
maxValue,
isDateUnavailable ? date => isDateUnavailable(date, null) : undefined,
- formatOpts
+ formatOpts,
+ startIsValuePartial,
+ endIsValuePartial
),
- [value, minValue, maxValue, isDateUnavailable, formatOpts]
+ [
+ value,
+ minValue,
+ maxValue,
+ isDateUnavailable,
+ formatOpts,
+ startIsValuePartial,
+ endIsValuePartial
+ ]
);
let validation = useFormValidationState({
@@ -253,6 +281,9 @@ export function useDateRangePickerState(
return {
...validation,
+ // Two setters since the range has two independent fields with separate partial state.
+ [`${privateSetIsValuePartialProp}-start`]: setStartIsValuePartial,
+ [`${privateSetIsValuePartialProp}-end`]: setEndIsValuePartial,
value,
defaultValue: props.defaultValue ?? initialValue,
setValue,
diff --git a/packages/react-stately/src/datepicker/utils.ts b/packages/react-stately/src/datepicker/utils.ts
index 91488711566..69417003bd5 100644
--- a/packages/react-stately/src/datepicker/utils.ts
+++ b/packages/react-stately/src/datepicker/utils.ts
@@ -50,12 +50,17 @@ export function getValidationResult(
minValue: DateValue | null | undefined,
maxValue: DateValue | null | undefined,
isDateUnavailable: ((v: DateValue) => boolean) | undefined,
- options: FormatterOptions
+ options: FormatterOptions,
+ isValuePartial: boolean = false
): ValidationResult {
+ // A partial value blocks submission (isValuePartial -> invalid + valueMissing). Constraint
+ // errors are evaluated against the value as usual, so a partial value shows the same descriptive
+ // messages a complete invalid value would (e.g. "Value must be … or later."). Only when a
+ // partial value violates no constraint does it fall back to the generic incomplete message.
let rangeOverflow = value != null && maxValue != null && value.compare(maxValue) > 0;
let rangeUnderflow = value != null && minValue != null && value.compare(minValue) < 0;
let isUnavailable = (value != null && isDateUnavailable?.(value)) || false;
- let isInvalid = rangeOverflow || rangeUnderflow || isUnavailable;
+ let isInvalid = rangeOverflow || rangeUnderflow || isUnavailable || isValuePartial;
let errors: string[] = [];
if (isInvalid) {
@@ -86,6 +91,10 @@ export function getValidationResult(
if (isUnavailable) {
errors.push(formatter.format('unavailableDate'));
}
+
+ if (isValuePartial && errors.length === 0) {
+ errors.push(formatter.format('incompleteValue'));
+ }
}
return {
@@ -101,7 +110,7 @@ export function getValidationResult(
tooLong: false,
tooShort: false,
typeMismatch: false,
- valueMissing: false,
+ valueMissing: isValuePartial,
valid: !isInvalid
}
};
@@ -112,14 +121,17 @@ export function getRangeValidationResult(
minValue: DateValue | null | undefined,
maxValue: DateValue | null | undefined,
isDateUnavailable: ((v: DateValue) => boolean) | undefined,
- options: FormatterOptions
+ options: FormatterOptions,
+ startIsValuePartial: boolean = false,
+ endIsValuePartial: boolean = false
): ValidationResult {
let startValidation = getValidationResult(
value?.start ?? null,
minValue,
maxValue,
isDateUnavailable,
- options
+ options,
+ startIsValuePartial
);
let endValidation = getValidationResult(
@@ -127,7 +139,8 @@ export function getRangeValidationResult(
minValue,
maxValue,
isDateUnavailable,
- options
+ options,
+ endIsValuePartial
);
let result = mergeValidation(startValidation, endValidation);
diff --git a/packages/react-stately/src/form/useFormValidationState.ts b/packages/react-stately/src/form/useFormValidationState.ts
index 40ce614bcc7..1aface8a112 100644
--- a/packages/react-stately/src/form/useFormValidationState.ts
+++ b/packages/react-stately/src/form/useFormValidationState.ts
@@ -51,6 +51,10 @@ export const FormValidationContext: Context = createContext extends Validation {
builtinValidation?: ValidationResult;
name?: string | string[];
diff --git a/scripts/missingTranslations.js b/scripts/missingTranslations.js
index 96e226841e5..b67289ecf74 100644
--- a/scripts/missingTranslations.js
+++ b/scripts/missingTranslations.js
@@ -1,7 +1,15 @@
const glob = require('glob');
const fs = require('fs');
-for (let dir of glob.sync('packages/**/intl')) {
+// Collect both top-level intl dirs and one level of named subdirs (e.g. intl/datepicker).
+let dirs = [...glob.sync('packages/**/intl'), ...glob.sync('packages/**/intl/*/')].map(d =>
+ d.endsWith('/') ? d.slice(0, -1) : d
+);
+
+for (let dir of dirs) {
+ if (!fs.existsSync(`${dir}/en-US.json`)) {
+ continue;
+ }
let en = JSON.parse(fs.readFileSync(`${dir}/en-US.json`, 'utf8'));
for (let file of glob.sync('*.json', {cwd: dir})) {