Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
f492f9d
Widen Rules min/max types from number to number | string
jorgefilipecosta Apr 9, 2026
770694a
Accept string type in min/max normalization guard
jorgefilipecosta Apr 9, 2026
ec1b967
Add isValidMinDate validator for date fields
jorgefilipecosta Apr 9, 2026
077f28e
Add isValidMaxDate validator for date fields
jorgefilipecosta Apr 9, 2026
5c61096
Register min/max validators in date field type
jorgefilipecosta Apr 9, 2026
e17ed00
Register min/max validators in datetime field type
jorgefilipecosta Apr 9, 2026
1fe508d
Wire min/max constraints into date edit control
jorgefilipecosta Apr 9, 2026
9d5fedd
Wire min/max constraints into datetime edit control
jorgefilipecosta Apr 9, 2026
771108d
Add date min/max examples to validation story
jorgefilipecosta Apr 9, 2026
25a1146
Update story date range examples to use year 2026
jorgefilipecosta Apr 9, 2026
237fe61
Add CHANGELOG entry for date min/max support
jorgefilipecosta Apr 9, 2026
50c89a0
Fix calendar disabled state when field has min/max constraints
jorgefilipecosta Apr 9, 2026
bcdb109
Fix indentation in datetime story field
jorgefilipecosta Apr 9, 2026
faa0186
Remove unnecessary optional chaining on isValid
jorgefilipecosta Apr 9, 2026
8aaa9e6
Extract useDisabledDateMatchers shared hook
jorgefilipecosta Apr 9, 2026
9c39fcb
Fix date validators for array values (date ranges)
jorgefilipecosta Apr 9, 2026
134541c
Narrow Field type to prevent string min/max on numeric fields
jorgefilipecosta Apr 9, 2026
dfd1c27
Add tests for date min/max and date range validation
jorgefilipecosta Apr 9, 2026
2614c6b
lint fix
jorgefilipecosta Apr 9, 2026
9da6a4f
Fix empty array validation and remove non-null assertions
jorgefilipecosta Apr 9, 2026
8d3215f
Add test coverage for datetime min/max, combined range, and empty arrays
jorgefilipecosta Apr 9, 2026
2c0ebc1
enhacements/clean up
jorgefilipecosta Apr 9, 2026
3782fea
clean up 2
jorgefilipecosta Apr 9, 2026
dcac4bf
type fix
jorgefilipecosta Apr 9, 2026
6dccb0a
type fix
jorgefilipecosta Apr 9, 2026
0738b1d
another type fix
jorgefilipecosta Apr 9, 2026
6126735
lint fix
jorgefilipecosta Apr 9, 2026
2cc7d03
Simplify types
oandregal Apr 14, 2026
a76b743
Remove extraneous reference type
oandregal Apr 14, 2026
ec68d89
lint fix
jorgefilipecosta Apr 14, 2026
e763a03
fix test failure
jorgefilipecosta Apr 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/dataviews/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Enhancements

- DataForm: Add `min`/`max` date range validation support for `date` and `datetime` fields. [#77201](https://github.com/WordPress/gutenberg/pull/77201)
- DataForm: support `isDisabled` field property. [#77090](https://github.com/WordPress/gutenberg/pull/77090)
- DataViews/DataViewsPicker: simplify `defaultLayouts` property. [#77232](https://github.com/WordPress/gutenberg/pull/77232)
- DataForm: Show tooltip in edit button in `panel` layout. [#77024](https://github.com/WordPress/gutenberg/pull/77024)
Expand Down
18 changes: 16 additions & 2 deletions packages/dataviews/src/components/dataform-controls/date.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { Stack } from '@wordpress/ui';
* Internal dependencies
*/
import RelativeDateControl from './utils/relative-date-control';
import useDisabledDateMatchers from './utils/use-disabled-date-matchers';
import {
OPERATOR_IN_THE_PAST,
OPERATOR_OVER,
Expand Down Expand Up @@ -318,6 +319,9 @@ function CalendarDateControl< Item >( {
const [ isTouched, setIsTouched ] = useState( false );
const validityTargetRef = useRef< HTMLInputElement >( null );

const { minConstraint, maxConstraint, disabledMatchers } =
useDisabledDateMatchers( isValid, parseDate );

const onChangeCallback = useCallback(
( newValue: string | undefined ) =>
onChange( setValue( { item: data, value: newValue } ) ),
Expand Down Expand Up @@ -440,6 +444,8 @@ function CalendarDateControl< Item >( {
onChange={ handleManualDateChange }
required={ !! field.isValid?.required }
disabled={ disabled }
min={ minConstraint }
max={ maxConstraint }
/>

{ /* Calendar widget */ }
Expand All @@ -453,7 +459,7 @@ function CalendarDateControl< Item >( {
onMonthChange={ setCalendarMonth }
timeZone={ timezoneString || undefined }
weekStartsOn={ weekStartsOn }
disabled={ disabled }
disabled={ disabled || disabledMatchers }
disableNavigation={ disabled }
/>
</Stack>
Expand All @@ -476,6 +482,7 @@ function CalendarDateRangeControl< Item >( {
description,
getValue,
setValue,
isValid,
format: fieldFormat,
} = field;
const disabled = field.isDisabled( { item: data, field } );
Expand All @@ -493,6 +500,9 @@ function CalendarDateRangeControl< Item >( {
( fieldFormat as FormatDate ).weekStartsOn ??
getSettings().l10n.startOfWeek;

const { minConstraint, maxConstraint, disabledMatchers } =
useDisabledDateMatchers( isValid, parseDate );

const onChangeCallback = useCallback(
( newValue: DateRange ) => {
onChange(
Expand Down Expand Up @@ -674,6 +684,8 @@ function CalendarDateRangeControl< Item >( {
}
required={ !! field.isValid?.required }
disabled={ disabled }
min={ minConstraint }
max={ maxConstraint }
/>
<InputControl
__next40pxDefaultSize
Expand All @@ -687,6 +699,8 @@ function CalendarDateRangeControl< Item >( {
}
required={ !! field.isValid?.required }
disabled={ disabled }
min={ minConstraint }
max={ maxConstraint }
/>
</Stack>

Expand All @@ -698,7 +712,7 @@ function CalendarDateRangeControl< Item >( {
onMonthChange={ setCalendarMonth }
timeZone={ timezone.string || undefined }
weekStartsOn={ weekStartsOn }
disabled={ disabled }
disabled={ disabled || disabledMatchers }
/>
</Stack>
</BaseControl>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { Stack } from '@wordpress/ui';
import type { DataFormControlProps, FormatDatetime } from '../../types';
import { OPERATOR_IN_THE_PAST, OPERATOR_OVER } from '../../constants';
import RelativeDateControl from './utils/relative-date-control';
import useDisabledDateMatchers from './utils/use-disabled-date-matchers';
import getCustomValidity from './utils/get-custom-validity';
import parseDateTime from '../../field-types/utils/parse-date-time';
import { unlock } from '../../lock-unlock';
Expand Down Expand Up @@ -55,6 +56,9 @@ function CalendarDateTimeControl< Item >( {
useRef< ReturnType< typeof setTimeout > >( undefined );
const previousFocusRef = useRef< Element | null >( null );

const { minConstraint, maxConstraint, disabledMatchers } =
useDisabledDateMatchers( isValid, parseDateTime );

const onChangeCallback = useCallback(
( newValue: string | undefined ) =>
onChange( setValue( { item: data, value: newValue } ) ),
Expand Down Expand Up @@ -181,6 +185,16 @@ function CalendarDateTimeControl< Item >( {
value={ formatDateTime( value ) }
onChange={ handleManualDateTimeChange }
disabled={ disabled }
min={
minConstraint
? formatDateTime( minConstraint )
: undefined
}
max={
maxConstraint
? formatDateTime( maxConstraint )
: undefined
}
/>
{ /* Calendar widget */ }
{ ! compact && (
Expand All @@ -196,7 +210,7 @@ function CalendarDateTimeControl< Item >( {
onMonthChange={ setCalendarMonth }
timeZone={ timezoneString || undefined }
weekStartsOn={ weekStartsOn }
disabled={ disabled }
disabled={ disabled || disabledMatchers }
/>
) }
</Stack>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* WordPress dependencies
*/
import { useMemo } from '@wordpress/element';

/**
* Internal dependencies
*/
import type { NormalizedRules } from '../../../types';

type DateMatcher = { before: Date } | { after: Date };

export default function useDisabledDateMatchers< Item >(
isValid: NormalizedRules< Item >,
parseDateFn: ( dateString?: string ) => Date | null
): {
minConstraint: string | undefined;
maxConstraint: string | undefined;
disabledMatchers: DateMatcher[] | undefined;
} {
const minConstraint =
typeof isValid.min?.constraint === 'string'
? isValid.min.constraint
: undefined;
const maxConstraint =
typeof isValid.max?.constraint === 'string'
? isValid.max.constraint
: undefined;

const disabledMatchers = useMemo( () => {
const matchers: DateMatcher[] = [];
if ( minConstraint ) {
const minDate = parseDateFn( minConstraint );
if ( minDate ) {
matchers.push( { before: minDate } );
}
}
if ( maxConstraint ) {
const maxDate = parseDateFn( maxConstraint );
if ( maxDate ) {
matchers.push( { after: maxDate } );
}
}
return matchers.length > 0 ? matchers : undefined;
}, [ minConstraint, maxConstraint, parseDateFn ] );

return { minConstraint, maxConstraint, disabledMatchers };
}
15 changes: 15 additions & 0 deletions packages/dataviews/src/dataform/stories/validation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -849,31 +849,46 @@ const ValidationComponent = ( {
id: 'date',
type: 'date',
label: 'Date',
description: minMax
? 'Must be between Apr 1 and Apr 20, 2026'
: undefined,
isValid: {
required,
elements: elements !== 'none' ? true : false,
custom: maybeCustomRule( customDateRule ),
min: minMax ? '2026-04-01' : undefined,
max: minMax ? '2026-04-20' : undefined,
},
},
{
id: 'dateRange',
type: 'date',
label: 'Date range',
Edit: DateRangeEdit,
description: minMax
? 'Must be between Apr 1 and Apr 20, 2026'
: undefined,
isValid: {
required,
elements: elements !== 'none' ? true : false,
custom: maybeCustomRule( customDateRangeRule ),
min: minMax ? '2026-04-01' : undefined,
max: minMax ? '2026-04-20' : undefined,
},
},
{
id: 'datetime',
type: 'datetime',
label: 'Date time',
description: minMax
? 'Must be between Apr 1 and Apr 20, 2026'
: undefined,
isValid: {
required,
elements: elements !== 'none' ? true : false,
custom: maybeCustomRule( customDateTimeRule ),
min: minMax ? '2026-04-01T00:00:00.000Z' : undefined,
max: minMax ? '2026-04-20T23:59:59.000Z' : undefined,
},
},
];
Expand Down
3 changes: 3 additions & 0 deletions packages/dataviews/src/field-types/date.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
OPERATOR_BETWEEN,
} from '../constants';
import isValidRequired from './utils/is-valid-required';
import { isValidMaxDate, isValidMinDate } from './utils/is-valid-date-boundary';
import render from './utils/render-default';

const format = {
Expand Down Expand Up @@ -91,5 +92,7 @@ export default {
validate: {
required: isValidRequired,
elements: isValidElements,
min: isValidMinDate,
max: isValidMaxDate,
},
} satisfies FieldType< any >;
3 changes: 3 additions & 0 deletions packages/dataviews/src/field-types/datetime.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
OPERATOR_OVER,
} from '../constants';
import isValidRequired from './utils/is-valid-required';
import { isValidMaxDate, isValidMinDate } from './utils/is-valid-date-boundary';
import render from './utils/render-default';

const format = {
Expand Down Expand Up @@ -88,5 +89,7 @@ export default {
validate: {
required: isValidRequired,
elements: isValidElements,
min: isValidMinDate,
max: isValidMaxDate,
},
} satisfies FieldType< any >;
44 changes: 44 additions & 0 deletions packages/dataviews/src/field-types/test/normalize-fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,50 @@ describe( 'normalizeFields: default getValue', () => {
} );
} );

describe( 'validation normalization', () => {
it( 'ignores string min/max rules on numeric fields', () => {
const fields: Field< {} >[] = [
{
id: 'price',
type: 'number',
isValid: {
min: '1',
max: '10',
},
},
];
const normalizedFields = normalizeFields( fields );
expect( normalizedFields[ 0 ].isValid.min ).toBeUndefined();
expect( normalizedFields[ 0 ].isValid.max ).toBeUndefined();
} );

it( 'ignores numeric min/max rules on date-like fields', () => {
const fields: Field< {} >[] = [
{
id: 'publishDate',
type: 'date',
isValid: {
min: 1,
max: 10,
},
},
{
id: 'publishedAt',
type: 'datetime',
isValid: {
min: 1,
max: 10,
},
},
];
const normalizedFields = normalizeFields( fields );
expect( normalizedFields[ 0 ].isValid.min ).toBeUndefined();
expect( normalizedFields[ 0 ].isValid.max ).toBeUndefined();
expect( normalizedFields[ 1 ].isValid.min ).toBeUndefined();
expect( normalizedFields[ 1 ].isValid.max ).toBeUndefined();
} );
} );

describe( 'format normalization', () => {
it( 'applies default format when not provided for date fields', () => {
const fields: Field< {} >[] = [
Expand Down
Loading
Loading